1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159
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<String>, pub modified: Vec<String>, pub renamed: Vec<(String, String)>, pub deleted: Vec<String>, pub untracked: Vec<String>, } 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<GitStatusChange> { let git_status = git_status(working_dir)?; parse_git_status_change(&git_status) } pub fn git_fetch_dry_run(working_dir: Option<&str>) -> XResult<bool> { 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<String> { 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<GitStatusChange> { 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 if ln.starts_with("renamed:") { let f = ln["renamed:".len()..].trim(); let mut fs = f.split("->"); let fa = fs.next(); let fb = fs.next(); if let (Some(fa), Some(fb)) = (fa, fb) { git_status_change.renamed.push((fa.trim().to_owned(), fb.trim().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 <file>..." to unstage) new file: src/util_git.rs renamed: src/template_regex.rs -> src/chk_regex.rs Changes not staged for commit: (use "git add/rm <file>..." to update what will be committed) (use "git checkout -- <file>..." to discard changes in working directory) deleted: README.md modified: src/lib.rs Untracked files: (use "git add <file>..." 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.renamed.len()); assert_eq!(("src/template_regex.rs".into(), "src/chk_regex.rs".into()), gsc.renamed[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]); }