summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--bin/gfold/src/display/color.rs3
-rw-r--r--lib/libgfold/src/status.rs47
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> {