summaryrefslogblamecommitdiffstats
path: root/src/shell/git.rs
blob: 48e5eeaca7589ec8d3b74aa70527d41cf505290a (plain) (tree)








































































































































































































                                                                            
#[derive(Debug)]
pub struct Info {
    modified_files: bool,
    staged_files: bool,
    new_files: bool,
    commits: bool,
    active_operation: ActiveOperation,
    branch: Option<String>,
    remote_branch_diff: Option<(usize, usize)>,
}

const MODIFIED: git2::Status = git2::Status::WT_DELETED
    .union(git2::Status::WT_MODIFIED)
    .union(git2::Status::WT_RENAMED)
    .union(git2::Status::WT_TYPECHANGE)
    .union(git2::Status::CONFLICTED);
const STAGED: git2::Status = git2::Status::INDEX_DELETED
    .union(git2::Status::INDEX_MODIFIED)
    .union(git2::Status::INDEX_NEW)
    .union(git2::Status::INDEX_RENAMED)
    .union(git2::Status::INDEX_TYPECHANGE);
const NEW: git2::Status = git2::Status::WT_NEW;

impl Info {
    pub fn new(git: &git2::Repository) -> Self {
        let mut status_options = git2::StatusOptions::new();
        status_options.include_untracked(true);
        status_options.update_index(true);

        let statuses = git.statuses(Some(&mut status_options));

        let mut modified_files = false;
        let mut staged_files = false;
        let mut new_files = false;
        if let Ok(statuses) = statuses {
            for file in statuses.iter() {
                if file.status().intersects(MODIFIED) {
                    modified_files = true;
                }
                if file.status().intersects(STAGED) {
                    staged_files = true;
                }
                if file.status().intersects(NEW) {
                    new_files = true;
                }
            }
        }

        let head = git.head();
        let mut commits = false;
        let mut branch = None;
        let mut remote_branch_diff = None;

        if let Ok(head) = head {
            commits = true;
            if head.is_branch() {
                branch = head.shorthand().map(ToString::to_string);
                remote_branch_diff =
                    head.resolve()
                        .ok()
                        .map(|head| {
                            (
                                head.target(),
                                head.shorthand().map(ToString::to_string),
                            )
                        })
                        .and_then(|(head_id, name)| {
                            head_id.and_then(|head_id| {
                                name.and_then(|name| {
                                    git.refname_to_id(&format!(
                                        "refs/remotes/origin/{}",
                                        name
                                    ))
                                    .ok()
                                    .and_then(|remote_id| {
                                        git.graph_ahead_behind(
                                            head_id, remote_id,
                                        )
                                        .ok()
                                    })
                                })
                            })
                        });
            } else {
                branch =
                    head.resolve().ok().and_then(|head| head.target()).map(
                        |oid| {
                            let mut sha: String = oid
                                .as_bytes()
                                .iter()
                                .take(4)
                                .map(|b| format!("{:02x}", b))
                                .collect();
                            sha.truncate(7);
                            sha
                        },
                    );
            }
        }

        let active_operation = match git.state() {
            git2::RepositoryState::Merge => ActiveOperation::Merge,
            git2::RepositoryState::Revert
            | git2::RepositoryState::RevertSequence => {
                ActiveOperation::Revert
            }
            git2::RepositoryState::CherryPick
            | git2::RepositoryState::CherryPickSequence => {
                ActiveOperation::CherryPick
            }
            git2::RepositoryState::Bisect => ActiveOperation::Bisect,
            git2::RepositoryState::Rebase
            | git2::RepositoryState::RebaseInteractive
            | git2::RepositoryState::RebaseMerge => ActiveOperation::Rebase,
            _ => ActiveOperation::None,
        };

        Self {
            modified_files,
            staged_files,
            new_files,
            commits,
            active_operation,
            branch,
            remote_branch_diff,
        }
    }
}

impl std::fmt::Display for Info {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "g")?;

        if self.modified_files {
            write!(f, "*")?;
        }
        if self.staged_files {
            write!(f, "+")?;
        }
        if self.new_files {
            write!(f, "?")?;
        }
        if !self.commits {
            write!(f, "!")?;
            return Ok(());
        }

        let branch = self.branch.as_ref().map_or("???", |branch| {
            if branch == "master" {
                ""
            } else {
                branch
            }
        });
        if !branch.is_empty() {
            write!(f, ":")?;
        }
        write!(f, "{}", branch)?;

        if let Some((local, remote)) = self.remote_branch_diff {
            if local > 0 || remote > 0 {
                write!(f, ":")?;
            }
            if local > 0 {
                write!(f, "+{}", local)?;
            }
            if remote > 0 {
                write!(f, "-{}", remote)?;
            }
        } else {
            write!(f, ":-")?;
        }

        write!(f, "{}", self.active_operation)?;

        Ok(())
    }
}

#[derive(Debug, Copy, Clone)]
pub enum ActiveOperation {
    None,
    Merge,
    Revert,
    CherryPick,
    Bisect,
    Rebase,
}

impl std::fmt::Display for ActiveOperation {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            ActiveOperation::None => Ok(()),
            ActiveOperation::Merge => write!(f, "(m)"),
            ActiveOperation::Revert => write!(f, "(v)"),
            ActiveOperation::CherryPick => write!(f, "(c)"),
            ActiveOperation::Bisect => write!(f, "(b)"),
            ActiveOperation::Rebase => write!(f, "(r)"),
        }
    }
}