From a55ad865461b961b83f550a350621d2b0ea2afe9 Mon Sep 17 00:00:00 2001 From: Hatter Jiang Date: Sun, 29 Nov 2020 10:24:25 +0800 Subject: [PATCH] feat: add util_git --- Cargo.toml | 2 +- src/lib.rs | 1 + src/util_git.rs | 147 ++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 149 insertions(+), 1 deletion(-) create mode 100644 src/util_git.rs diff --git a/Cargo.toml b/Cargo.toml index b37012f..8817583 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rust_util" -version = "0.6.18" +version = "0.6.19" authors = ["Hatter Jiang "] edition = "2018" description = "Hatter's Rust Util" diff --git a/src/lib.rs b/src/lib.rs index 6535085..3ce8f2f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -15,6 +15,7 @@ pub mod util_file; pub mod util_time; pub mod util_net; pub mod util_term; +pub mod util_git; /// iff!(condition, result_when_true, result_when_false) #[macro_export] macro_rules! iff { diff --git a/src/util_git.rs b/src/util_git.rs new file mode 100644 index 0000000..19b99e3 --- /dev/null +++ b/src/util_git.rs @@ -0,0 +1,147 @@ +use std::process::Command; +use crate::XResult; +use crate::util_msg; +use crate::util_cmd::run_command_and_wait; + +const LANG: &str = "LANG"; +const EN_US: &str = "en_US"; + +#[derive(Default, Debug, Clone)] +pub struct GitStatusChange { + pub added: Vec, + pub modified: Vec, + pub deleted: Vec, + pub untracked: Vec, +} + +impl GitStatusChange { + pub fn is_empty(&self) -> bool { + self.added.is_empty() && self.modified.is_empty() + && self.deleted.is_empty() && self.untracked.is_empty() + } +} + +pub fn git_status_change(working_dir: Option<&str>) -> XResult { + let git_status = git_status(working_dir)?; + parse_git_status_change(&git_status) +} + +pub fn git_fetch_dry_run(working_dir: Option<&str>) -> XResult { + let mut cmd = new_git_command(working_dir); + cmd.args(vec!["fetch", "--dry-run"]); + util_msg::print_info(&format!("Exec: {:?}", cmd)); + let output = cmd.output()?; + let fetch_dry_run = String::from_utf8(output.stdout)?; + Ok(fetch_dry_run.trim().is_empty()) +} + +pub fn git_status(working_dir: Option<&str>) -> XResult { + let mut cmd = new_git_command(working_dir); + cmd.arg("status"); + util_msg::print_info(&format!("Exec: {:?}", cmd)); + let output = cmd.output()?; + let git_status = String::from_utf8(output.stdout)?; + Ok(git_status) +} + +pub fn git_push(working_dir: Option<&str>) { + let mut cmd = new_git_command(working_dir); + cmd.arg("push"); + util_msg::print_info(&format!("Exec: {:?}", cmd)); + if let Err(e) = run_command_and_wait(&mut cmd) { + util_msg::print_error(&format!("Run git push failed: {}", e)); + } +} + +pub fn git_add(working_dir: Option<&str>, files: &[String]) { + let mut cmd = new_git_command(working_dir); + cmd.arg("add"); + for f in files { + cmd.arg(&f); + } + util_msg::print_info(&format!("Exec: {:?}", cmd)); + if let Err(e) = run_command_and_wait(&mut cmd) { + util_msg::print_error(&format!("Run git add failed: {}", e)); + } +} + +pub fn git_commit(working_dir: Option<&str>, message: &str, files: &[String]) { + let mut cmd = new_git_command(working_dir); + cmd.arg("commit"); + cmd.arg("-m"); + cmd.arg(message); + for f in files { + cmd.arg(&f); + } + util_msg::print_info(&format!("Exec: {:?}", cmd)); + if let Err(e) = run_command_and_wait(&mut cmd) { + util_msg::print_error(&format!("Run git commit failed: {}", e)); + } +} + +fn parse_git_status_change(git_status: &str) -> XResult { + let mut git_status_change: GitStatusChange = Default::default(); + for ln in git_status.lines() { + if ln.starts_with("\t") { + let ln = ln.trim(); + if ln.starts_with("new file:") { + let f = ln["new file:".len()..].trim(); + git_status_change.added.push(f.to_owned()); + } else if ln.starts_with("deleted:") { + let f = ln["deleted:".len()..].trim(); + git_status_change.deleted.push(f.to_owned()); + } else if ln.starts_with("modified:") { + let f = ln["modified:".len()..].trim(); + git_status_change.modified.push(f.to_owned()); + } else { + git_status_change.untracked.push(ln.to_owned()); + } + } + } + Ok(git_status_change) +} + +fn new_git_command(working_dir: Option<&str>) -> Command { + let mut cmd = Command::new("git"); + cmd.env(LANG, EN_US); + if let Some(working_dir) = working_dir { + cmd.current_dir(working_dir); + } + cmd +} + + +#[test] +fn test_git_status() { + let git_status = r#"On branch master +Your branch is up to date with 'origin/master'. + +Changes to be committed: + (use "git reset HEAD ..." to unstage) + + new file: src/util_git.rs + +Changes not staged for commit: + (use "git add/rm ..." to update what will be committed) + (use "git checkout -- ..." to discard changes in working directory) + + deleted: README.md + modified: src/lib.rs + +Untracked files: + (use "git add ..." to include in what will be committed) + + Test + +H"#; + let gsc = parse_git_status_change(git_status).unwrap(); + println!("{:#?}", gsc); + assert_eq!(1, gsc.added.len()); + assert_eq!("src/util_git.rs", gsc.added[0]); + assert_eq!(1, gsc.modified.len()); + assert_eq!("src/lib.rs", gsc.modified[0]); + assert_eq!(1, gsc.deleted.len()); + assert_eq!("README.md", gsc.deleted[0]); + assert_eq!(1, gsc.untracked.len()); + assert_eq!("Test", gsc.untracked[0]); +} \ No newline at end of file