1use std::path::Path;
12use std::process::Command;
13
14pub fn current_refspec(cwd: &Path) -> Option<String> {
17 let branch = current_branch(cwd)?;
18 if let Some(tracked) = tracked_upstream(cwd, &branch) {
19 return Some(tracked);
20 }
21 Some(format!("refs/heads/{branch}"))
22}
23
24fn current_branch(cwd: &Path) -> Option<String> {
27 let out = Command::new("git")
28 .arg("-C")
29 .arg(cwd)
30 .args(["symbolic-ref", "--short", "HEAD"])
31 .output()
32 .ok()?;
33 if !out.status.success() {
34 return None;
35 }
36 let s = String::from_utf8_lossy(&out.stdout).trim().to_owned();
37 if s.is_empty() { None } else { Some(s) }
38}
39
40fn tracked_upstream(cwd: &Path, branch: &str) -> Option<String> {
44 let key = format!("branch.{branch}.merge");
45 let out = Command::new("git")
46 .arg("-C")
47 .arg(cwd)
48 .args(["config", "--get", &key])
49 .output()
50 .ok()?;
51 if !out.status.success() {
52 return None;
53 }
54 let s = String::from_utf8_lossy(&out.stdout).trim().to_owned();
55 if s.is_empty() { None } else { Some(s) }
56}
57
58#[cfg(test)]
59mod tests {
60 use super::*;
61 use crate::tests::commit_helper;
62
63 #[test]
64 fn refspec_falls_back_to_current_branch() {
65 let tmp = commit_helper::init_repo();
66 commit_helper::commit_file(&tmp, "a.txt", b"hi");
67 assert_eq!(
69 current_refspec(tmp.path()).as_deref(),
70 Some("refs/heads/main"),
71 );
72 }
73
74 #[test]
75 fn refspec_prefers_tracked_upstream() {
76 let tmp = commit_helper::init_repo();
77 commit_helper::commit_file(&tmp, "a.txt", b"hi");
78 std::process::Command::new("git")
79 .arg("-C")
80 .arg(tmp.path())
81 .args(["config", "branch.main.merge", "refs/heads/tracked"])
82 .status()
83 .unwrap();
84 assert_eq!(
85 current_refspec(tmp.path()).as_deref(),
86 Some("refs/heads/tracked"),
87 );
88 }
89
90 #[test]
91 fn refspec_none_on_detached_head() {
92 let tmp = commit_helper::init_repo();
93 commit_helper::commit_file(&tmp, "a.txt", b"hi");
94 let head = commit_helper::head_oid(&tmp);
95 std::process::Command::new("git")
96 .arg("-C")
97 .arg(tmp.path())
98 .args(["checkout", "--quiet", &head])
99 .status()
100 .unwrap();
101 assert_eq!(current_refspec(tmp.path()), None);
102 }
103}