From 83f89188161b38046b39124ffb45bbbd6fcb572f Mon Sep 17 00:00:00 2001 From: Jesse Luehrs Date: Tue, 20 Feb 2018 01:48:17 -0500 Subject: add vcs support --- src/vcs/git.rs | 176 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 176 insertions(+) create mode 100644 src/vcs/git.rs (limited to 'src/vcs/git.rs') diff --git a/src/vcs/git.rs b/src/vcs/git.rs new file mode 100644 index 0000000..749d498 --- /dev/null +++ b/src/vcs/git.rs @@ -0,0 +1,176 @@ +use git2; +use std; + +use std::fmt::Write; + +#[derive(Debug)] +pub struct GitInfo { + modified_files: bool, + staged_files: bool, + new_files: bool, + commits: bool, + active_operation: super::ActiveOperation, + branch: Option, + remote_branch_diff: Option<(usize, usize)>, +} + +impl GitInfo { + pub fn new(git: git2::Repository) -> GitInfo { + let mut modified_statuses = git2::Status::empty(); + modified_statuses.insert(git2::STATUS_WT_DELETED); + modified_statuses.insert(git2::STATUS_WT_MODIFIED); + modified_statuses.insert(git2::STATUS_WT_RENAMED); + modified_statuses.insert(git2::STATUS_WT_TYPECHANGE); + let mut staged_statuses = git2::Status::empty(); + staged_statuses.insert(git2::STATUS_INDEX_DELETED); + staged_statuses.insert(git2::STATUS_INDEX_MODIFIED); + staged_statuses.insert(git2::STATUS_INDEX_NEW); + staged_statuses.insert(git2::STATUS_INDEX_RENAMED); + staged_statuses.insert(git2::STATUS_INDEX_TYPECHANGE); + let mut new_statuses = git2::Status::empty(); + new_statuses.insert(git2::STATUS_WT_NEW); + + let mut status_options = git2::StatusOptions::new(); + status_options.include_untracked(true); + if true { // XXX + status_options.update_index(true); + } + else { + status_options.update_index(false); + status_options.no_refresh(true); + } + let status = git.statuses(Some(&mut status_options)); + let mut modified_files = false; + let mut staged_files = false; + let mut new_files = false; + for status in status.iter() { + for file in status.iter() { + if file.status().intersects(modified_statuses) { + modified_files = true; + } + if file.status().intersects(staged_statuses) { + staged_files = true; + } + if file.status().intersects(new_statuses) { + new_files = true; + } + } + } + + let head = git.head(); + let commits = head.is_ok(); + let branch = head.ok() + .and_then(|head| { + if head.is_branch() { + head.shorthand().map(|s| s.to_string()) + } + else { + head.resolve().ok() + .and_then(|head| head.target()) + .map(|oid| { + let mut sha = String::new(); + for b in oid.as_bytes().iter() { + write!(sha, "{:02x}", b).unwrap(); + } + sha.truncate(7); + sha + }) + } + }); + + let active_operation = match git.state() { + git2::RepositoryState::Merge + => super::ActiveOperation::Merge, + git2::RepositoryState::Revert + | git2::RepositoryState::RevertSequence + => super::ActiveOperation::Revert, + git2::RepositoryState::CherryPick + | git2::RepositoryState::CherryPickSequence + => super::ActiveOperation::CherryPick, + git2::RepositoryState::Bisect + => super::ActiveOperation::Bisect, + git2::RepositoryState::Rebase + | git2::RepositoryState::RebaseInteractive + | git2::RepositoryState::RebaseMerge + => super::ActiveOperation::Rebase, + _ => super::ActiveOperation::None, + }; + + let remote_branch_diff = git.head().ok() + .and_then(|head| if head.is_branch() { Some(head) } else { None }) + .and_then(|head| { + head.resolve().ok() + }) + .map(|head| { + (head.target(), head.shorthand().map(|s| s.to_string())) + }) + .and_then(|(head_id, name)| { + head_id.and_then(|head_id| { + name.and_then(|name| { + git.refname_to_id( + &(String::from("refs/remotes/origin/") + &name) + ).ok().and_then(|remote_id| { + git.graph_ahead_behind(head_id, remote_id).ok() + }) + }) + }) + }); + + GitInfo { + modified_files: modified_files, + staged_files: staged_files, + new_files: new_files, + commits: commits, + active_operation: active_operation, + branch: branch, + remote_branch_diff: remote_branch_diff, + } + } +} + +impl super::VcsInfo for GitInfo { + fn vcs(&self) -> super::VcsType { + super::VcsType::Git + } + + fn has_modified_files(&self) -> bool { + self.modified_files + } + + fn has_staged_files(&self) -> bool { + self.staged_files + } + + fn has_new_files(&self) -> bool { + self.new_files + } + + fn has_commits(&self) -> bool { + self.commits + } + + fn active_operation(&self) -> super::ActiveOperation { + self.active_operation + } + + fn branch(&self) -> Option { + self.branch.clone() + } + + fn remote_branch_diff(&self) -> Option<(usize, usize)> { + self.remote_branch_diff + } +} + +pub fn detect() -> Option> { + let git = std::env::current_dir().ok().and_then(|pwd| { + git2::Repository::discover(pwd).ok() + }); + + if let Some(git) = git { + Some(Box::new(GitInfo::new(git))) + } + else { + None + } +} -- cgit v1.2.3-54-g00ecf