thoughts_tool/git/
shell_push.rs1use anyhow::Context;
2use anyhow::Result;
3use anyhow::bail;
4use std::io::BufRead;
5use std::io::BufReader;
6use std::path::Path;
7use std::process::Command;
8use std::process::Stdio;
9
10pub fn build_push_command(repo_path: &Path, remote: &str, branch: &str) -> Command {
11 let mut cmd = Command::new("git");
12 cmd.current_dir(repo_path)
13 .arg("push")
14 .arg("--progress")
15 .arg(remote)
16 .arg(format!("HEAD:refs/heads/{branch}"));
17 cmd
18}
19
20fn print_progress_line(line: &str) {
21 if line.starts_with("To ")
22 || line.starts_with("Everything up-to-date")
23 || line.contains('%')
24 || line.starts_with("remote:")
25 || line.contains("Counting objects")
26 {
27 println!(" {}", line);
28 }
29}
30
31pub fn push_current_branch(repo_path: &Path, remote: &str, branch: &str) -> Result<()> {
32 which::which("git").context("git executable not found in PATH")?;
33
34 let mut cmd = build_push_command(repo_path, remote, branch);
35 let mut child = cmd
36 .stdout(Stdio::null())
37 .stderr(Stdio::piped())
38 .spawn()
39 .context("Failed to spawn git push")?;
40
41 if let Some(stderr) = child.stderr.take() {
42 let reader = BufReader::new(stderr);
43 for line in reader.lines() {
44 match line {
45 Ok(l) => print_progress_line(&l),
46 Err(_) => break,
47 }
48 }
49 }
50
51 let status = child.wait().context("Failed to wait for git push")?;
52 if !status.success() {
53 bail!("git push failed with exit code {:?}", status.code());
54 }
55 Ok(())
56}
57
58#[cfg(test)]
59mod tests {
60 use super::*;
61
62 #[test]
63 fn build_push_cmd_has_expected_args() {
64 let cmd = build_push_command(Path::new("/tmp/repo"), "origin", "main");
65 let args: Vec<String> = cmd
66 .get_args()
67 .map(|s| s.to_string_lossy().into_owned())
68 .collect();
69 assert_eq!(
70 args,
71 vec!["push", "--progress", "origin", "HEAD:refs/heads/main"]
72 );
73 assert_eq!(cmd.get_current_dir(), Some(Path::new("/tmp/repo")));
74 }
75}