1use std::path::Path;
6
7use crate::error::{Error, Result};
8use crate::git::cli::GitCli;
9
10pub(crate) fn ahead_behind(
13 git: &dyn GitCli,
14 dir: &Path,
15 upstream_ref: &str,
16 branch_ref: &str,
17) -> Result<(u32, u32)> {
18 let range = format!("{upstream_ref}...{branch_ref}");
19 let output = git.run(dir, &["rev-list", "--left-right", "--count", &range])?;
20 parse_left_right(&output)
21}
22
23fn parse_left_right(text: &str) -> Result<(u32, u32)> {
26 let mut parts = text.split_whitespace();
27 let behind = parts.next().and_then(|s| s.parse::<u32>().ok());
28 let ahead = parts.next().and_then(|s| s.parse::<u32>().ok());
29 match (ahead, behind) {
30 (Some(ahead), Some(behind)) => Ok((ahead, behind)),
31 _ => Err(Error::operation(format!(
32 "unexpected rev-list output: {text:?}"
33 ))),
34 }
35}
36
37#[cfg(test)]
38mod tests {
39 use super::*;
40 use crate::git::cli::RealGit;
41 use crate::testutil::TestRepo;
42
43 #[test]
44 fn parse_left_right_orders_ahead_then_behind() {
45 assert_eq!(parse_left_right("0\t2\n").unwrap(), (2, 0));
47 assert_eq!(parse_left_right("3\t1").unwrap(), (1, 3));
48 assert!(parse_left_right("garbage").is_err());
49 }
50
51 #[test]
52 fn ahead_of_upstream() {
53 let repo = TestRepo::init();
54 let base = repo.git(&["rev-parse", "HEAD"]).trim().to_string();
56 repo.git(&["update-ref", "refs/remotes/origin/main", &base]);
57 repo.write("a.txt", "1\n");
59 repo.commit_all("c1");
60 repo.write("b.txt", "2\n");
61 repo.commit_all("c2");
62 let (ahead, behind) = ahead_behind(
63 &RealGit,
64 repo.root(),
65 "refs/remotes/origin/main",
66 "refs/heads/main",
67 )
68 .unwrap();
69 assert_eq!((ahead, behind), (2, 0));
70 }
71
72 #[test]
73 fn behind_upstream() {
74 let repo = TestRepo::init();
75 let c1 = repo.git(&["rev-parse", "HEAD"]).trim().to_string();
76 repo.write("a.txt", "1\n");
77 repo.commit_all("c2");
78 let c2 = repo.git(&["rev-parse", "HEAD"]).trim().to_string();
80 repo.git(&["update-ref", "refs/remotes/origin/main", &c2]);
81 repo.git(&["reset", "-q", "--hard", &c1]);
82 let (ahead, behind) = ahead_behind(
83 &RealGit,
84 repo.root(),
85 "refs/remotes/origin/main",
86 "refs/heads/main",
87 )
88 .unwrap();
89 assert_eq!((ahead, behind), (0, 1));
90 }
91}