git_branch_status/
repository_ext.rs1use std::fs;
16
17use git2::{Error, ErrorCode, Oid, Repository, RepositoryState, StatusOptions};
18
19use crate::branch_status::BranchStatus;
20use crate::status_entry_ext::StatusEntryExt;
21
22pub trait RepositoryExt {
23 fn action(&self) -> Option<&str>;
24 fn branch_name(&self) -> Result<String, Error>;
25 fn branch_status(&self) -> Result<BranchStatus, Error>;
26 fn rebase_i_head_name(&self) -> Result<String, Error>;
27 fn to_short_oid(&self, oid: Oid) -> Result<Option<String>, Error>;
28}
29
30impl RepositoryExt for Repository {
31 fn action(&self) -> Option<&str> {
32 match self.state() {
33 RepositoryState::ApplyMailbox => Some("am"),
34 RepositoryState::ApplyMailboxOrRebase => Some("am/rebase"),
35 RepositoryState::Bisect => Some("bisect"),
36 RepositoryState::CherryPick => Some("cherry"),
37 RepositoryState::CherryPickSequence => Some("cherry-seq"),
38 RepositoryState::Merge => Some("merge"),
39 RepositoryState::Rebase => Some("rebase"),
40 RepositoryState::RebaseInteractive => Some("rebase-i"),
41 RepositoryState::RebaseMerge => Some("rebase-m"),
42 RepositoryState::Revert => Some("revert"),
43 RepositoryState::RevertSequence => Some("revert-seq"),
44 _ => None,
45 }
46 }
47
48 fn branch_name(&self) -> Result<String, Error> {
49 let head = match self.head() {
50 Ok(head) => Some(head),
51 Err(ref e)
52 if e.code() == ErrorCode::UnbornBranch || e.code() == ErrorCode::NotFound =>
53 {
54 None
55 }
56 Err(e) => return Err(e),
57 };
58
59 let detached = self.head_detached()?;
60
61 let branch = if self.state() == RepositoryState::RebaseInteractive {
62 self.rebase_i_head_name()?
63 } else if detached {
64 let oid = head.as_ref().and_then(|h| h.target());
65 let short = match oid.map(|oid| self.to_short_oid(oid)) {
66 Some(Ok(id)) => id,
67 Some(Err(e)) => return Err(e),
68 None => None,
69 };
70
71 short.unwrap_or_else(|| "HEAD (detached)".to_string())
72 } else {
73 head.as_ref()
74 .and_then(|h| h.shorthand().ok())
75 .unwrap_or("HEAD (no branch)")
76 .to_string()
77 };
78
79 match self.action() {
80 Some(action) => Ok(branch + ":" + action),
81 None => Ok(branch),
82 }
83 }
84
85 fn branch_status(&self) -> Result<BranchStatus, Error> {
86 let mut opts = StatusOptions::new();
87 opts.include_untracked(false)
88 .include_ignored(false)
89 .include_unmodified(false)
90 .exclude_submodules(true);
91
92 let stats = self.statuses(Some(&mut opts))?;
93
94 let status = stats.iter().fold(BranchStatus::NotChanged, |acc, s| {
95 if acc < BranchStatus::Conflicted && s.is_conflicted() {
96 BranchStatus::Conflicted
97 } else if acc < BranchStatus::Unstaged && s.is_unstaged() {
98 BranchStatus::Unstaged
99 } else if acc < BranchStatus::Staged && s.is_staged() {
100 BranchStatus::Staged
101 } else {
102 acc
103 }
104 });
105
106 Ok(status)
107 }
108
109 fn rebase_i_head_name(&self) -> Result<String, Error> {
110 let path = self.path().join("rebase-merge").join("head-name");
111
112 let refname = match fs::read_to_string(path) {
113 Ok(content) => content,
114 Err(e) => return Err(Error::from_str(&e.to_string())),
115 };
116
117 match self.find_reference(refname.trim()) {
118 Ok(ref reference) => Ok(reference.shorthand().unwrap_or(&refname).to_string()),
119 Err(e) => Err(e),
120 }
121 }
122
123 fn to_short_oid(&self, oid: Oid) -> Result<Option<String>, Error> {
124 let object = self.find_object(oid, None)?;
125 match object.short_id() {
126 Ok(id) => Ok(id.as_str().map(|i| i.to_string()).ok()),
127 Err(e) => Err(e),
128 }
129 }
130}