diff options
-rw-r--r-- | bin/gfold/src/display/color.rs | 3 | ||||
-rw-r--r-- | lib/libgfold/src/status.rs | 47 |
2 files changed, 45 insertions, 5 deletions
diff --git a/bin/gfold/src/display/color.rs b/bin/gfold/src/display/color.rs index f264cd2..c2eb01f 100644 --- a/bin/gfold/src/display/color.rs +++ b/bin/gfold/src/display/color.rs @@ -28,7 +28,8 @@ impl ColorHarness { pub fn write_status(&self, status: &Status, status_width: usize) -> io::Result<()> { let mut stdout = StandardStream::stdout(self.color_choice); stdout.set_color(ColorSpec::new().set_fg(Some(match status { - Status::Bare | Status::Unknown => Color::Red, + Status::Bare | Status::Diverged | Status::Unknown => Color::Red, + Status::Behind => Color::Yellow, Status::Clean => Color::Green, Status::Unpushed => Color::Blue, Status::Unclean => Color::Yellow, diff --git a/lib/libgfold/src/status.rs b/lib/libgfold/src/status.rs index 7ffd638..4a080c5 100644 --- a/lib/libgfold/src/status.rs +++ b/lib/libgfold/src/status.rs @@ -1,6 +1,6 @@ //! This module contains the [`crate::status::Status`] type. -use git2::{ErrorCode, Reference, Remote, Repository, StatusOptions}; +use git2::{BranchType, ErrorClass, ErrorCode, Reference, Remote, Repository, StatusOptions}; use log::debug; use serde::{Deserialize, Serialize}; use thiserror::Error; @@ -20,8 +20,12 @@ pub type StatusResult<T> = Result<T, StatusError>; pub enum Status { /// Corresponds to a "bare" working tree. Bare, + /// Indicates that there is at least one remote commit not yet merged to local branch. + Behind, /// Corresponds to a "clean" working tree. Clean, + /// Indicates that local and remote both have commits unique to their side. + Diverged, /// Corresponds to an "unclean" working tree. Unclean, /// Provided if the state of the working tree could neither be found nor determined. @@ -35,7 +39,9 @@ impl Status { pub fn as_str(&self) -> &'static str { match self { Self::Bare => "bare", + Self::Behind => "behind", Self::Clean => "clean", + Self::Diverged => "diverged", Self::Unclean => "unclean", Self::Unknown => "unknown", Self::Unpushed => "unpushed", @@ -73,9 +79,13 @@ impl Status { let status = match repo.statuses(Some(&mut opts)) { Ok(v) if v.is_empty() => match &head { Some(head) => match remote_name { - Some(remote_name) => match Self::is_unpushed(repo, head, &remote_name)? { - true => Status::Unpushed, - false => Status::Clean, + Some(remote_name) => match (Self::is_unpushed(repo, head, &remote_name)?, + Self::is_unmerged(repo, head)?) + { + (true, false) => Status::Unpushed, + (false, false) => Status::Clean, + (false, true) => Status::Behind, + (true, true) => Status::Diverged, }, None => Status::Clean, }, @@ -119,6 +129,35 @@ impl Status { ) } + // Checks if remote commit(s) on the tracking branch have not yet been merged locally. + fn is_unmerged( + repo: &Repository, + head: &Reference<'_>, + ) -> Result<bool, git2::Error> { + if head.is_branch() { + let branch = repo.find_branch(head.shorthand() + .ok_or(git2::Error::new( + ErrorCode::User, ErrorClass::Invalid, "Branch name contains invalid UTF-8.") + )?, BranchType::Local + )?; + match branch.upstream() { + Err(_) => { + debug!("assuming merged; could not determine upstream branch"); + Ok(false) + }, + Ok(upstream) => { + let number_unique_commits = repo.graph_ahead_behind( + branch.get().peel_to_commit()?.id(), + upstream.into_reference().peel_to_commit().unwrap().id() + )?; + Ok(number_unique_commits.1 > 0) + }, + } + } else { + Ok(false) + } + } + fn choose_remote_greedily( repository: &Repository, ) -> Result<(Option<Remote<'_>>, Option<String>), git2::Error> { |