/ src / git.rs
git.rs
 1  use anyhow::Result;
 2  use colored::Colorize;
 3  use std::process::{Command, Output};
 4  
 5  pub fn git(args: &[&str]) -> Output {
 6    let log = format!("+> git {}", args.join(" "));
 7    println!("{}", log.bright_black());
 8  
 9    Command::new("git")
10      .args(args)
11      .output()
12      .expect("failed to run git command, make sure git is installed on your machine")
13  }
14  
15  // get every commit message between the two versions
16  pub fn diff(old_version: &str, new_version: &str) -> String {
17    let references = format!("{old_version}..{new_version}");
18  
19    let output = git(&["log", "--oneline", "--pretty=format:%s (%h)", &references]);
20    let output = String::from_utf8_lossy(&output.stdout).to_string();
21  
22    let mut lines = output.lines().collect::<Vec<_>>();
23  
24    // reverse the lines to get the oldest commit first
25    lines.reverse();
26  
27    // remove the last line because it's the version commit
28    lines.pop();
29  
30    lines
31      .iter()
32      .map(|line| format!("* {line}"))
33      .collect::<Vec<_>>()
34      .join("\n")
35  }
36  
37  pub fn origin_url() -> String {
38    let output = git(&["remote", "get-url", "origin"]);
39    let url = String::from_utf8_lossy(&output.stdout);
40  
41    url.trim().to_string()
42  }
43  
44  pub fn branch_name() -> String {
45    let output = git(&["rev-parse", "--abbrev-ref", "HEAD"]);
46    let branch_name = String::from_utf8_lossy(&output.stdout);
47    branch_name.trim().to_string()
48  }
49  
50  pub fn is_repo_dirty() -> Result<bool> {
51    let output = git(&["status", "--porcelain"]);
52  
53    if !output.status.success() {
54      return Err(anyhow::anyhow!("failed to check repository status"));
55    }
56  
57    // If there's any output, the repo is dirty
58    Ok(!output.stdout.is_empty())
59  }
60  
61  pub fn is_behind_upstream(branch_name: &str) -> Result<bool> {
62    let fetch = git(&["fetch"]);
63    if !fetch.status.success() {
64      return Err(anyhow::anyhow!("failed to fetch from remote"));
65    }
66  
67    let upstream = format!("origin/{branch_name}");
68    let output = git(&["rev-list", "--count", &format!("HEAD..{upstream}")]);
69  
70    if !output.status.success() {
71      return Err(anyhow::anyhow!(
72        "failed to check if branch is behind remote"
73      ));
74    }
75  
76    let behind_count = String::from_utf8_lossy(&output.stdout)
77      .trim()
78      .parse::<u32>()
79      .unwrap_or(0);
80  
81    Ok(behind_count > 0)
82  }
83  
84  /// Get all tags of the repository, latest first to oldest tag.
85  pub fn tags() -> Vec<String> {
86    let output = git(&["tag", "--sort=-v:refname"]);
87    let output = String::from_utf8_lossy(&output.stdout).to_string();
88  
89    output.lines().map(|line| line.into()).collect::<Vec<_>>()
90  }