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