Lightweight decentralized “knot” server prototype using Iroh + ATProto.
1use std::path::{Path, PathBuf};
2use std::process::Command;
3
4use crate::{Error, Result};
5
6const MAIN_REF: &str = "refs/heads/main";
7
8pub fn read_head_commit(repo_path: &Path) -> Result<String> {
9 let output = Command::new("git")
10 .arg("-C")
11 .arg(repo_path)
12 .args(["rev-parse", "HEAD"])
13 .output()?;
14 if !output.status.success() {
15 return Err(Error::Invalid(format!(
16 "git rev-parse HEAD failed with {}",
17 output.status
18 )));
19 }
20 let head = String::from_utf8_lossy(&output.stdout).trim().to_string();
21 if head.is_empty() {
22 return Err(Error::Invalid("git rev-parse HEAD returned empty".into()));
23 }
24 Ok(head)
25}
26
27pub fn resolve_git_dir(repo_path: &Path) -> Result<PathBuf> {
28 let output = Command::new("git")
29 .arg("-C")
30 .arg(repo_path)
31 .args(["rev-parse", "--git-dir"])
32 .output()?;
33 if !output.status.success() {
34 return Err(Error::Invalid(format!(
35 "git rev-parse --git-dir failed with {}",
36 output.status
37 )));
38 }
39 let raw = String::from_utf8_lossy(&output.stdout).trim().to_string();
40 if raw.is_empty() {
41 return Err(Error::Invalid("git rev-parse --git-dir returned empty".into()));
42 }
43 let git_dir = PathBuf::from(raw);
44 if git_dir.is_absolute() {
45 Ok(git_dir)
46 } else {
47 Ok(repo_path.join(git_dir))
48 }
49}
50
51pub fn read_main_ref(repo_path: &Path) -> Result<String> {
52 let output = Command::new("git")
53 .arg("-C")
54 .arg(repo_path)
55 .args(["rev-parse", MAIN_REF])
56 .output()?;
57 if !output.status.success() {
58 return Err(Error::Invalid(format!(
59 "git rev-parse {MAIN_REF} failed with {}",
60 output.status
61 )));
62 }
63 let head = String::from_utf8_lossy(&output.stdout).trim().to_string();
64 if head.is_empty() {
65 return Err(Error::Invalid(format!(
66 "git rev-parse {MAIN_REF} returned empty"
67 )));
68 }
69 Ok(head)
70}
71
72pub fn reset_main_ref(repo_path: &Path, target: &str) -> Result<()> {
73 let status = Command::new("git")
74 .arg("-C")
75 .arg(repo_path)
76 .args(["update-ref", MAIN_REF, target])
77 .status()?;
78 if !status.success() {
79 return Err(Error::Invalid(format!(
80 "git update-ref {MAIN_REF} failed with {status}"
81 )));
82 }
83 Ok(())
84}
85
86pub fn is_fast_forward(repo_path: &Path, old: &str, new: &str) -> Result<bool> {
87 let status = Command::new("git")
88 .arg("-C")
89 .arg(repo_path)
90 .args(["merge-base", "--is-ancestor", old, new])
91 .status()?;
92 if status.success() {
93 return Ok(true);
94 }
95 if status.code() == Some(1) {
96 return Ok(false);
97 }
98 Err(Error::Invalid(format!(
99 "git merge-base --is-ancestor failed with {status}"
100 )))
101}
102
103#[cfg(test)]
104mod tests {
105 use super::resolve_git_dir;
106 use std::fs;
107 use std::process::Command;
108
109 #[test]
110 fn resolve_git_dir_for_working_repo() {
111 let tmp = tempfile::tempdir().unwrap();
112 let repo = tmp.path().join("repo");
113 fs::create_dir_all(&repo).unwrap();
114
115 let status = Command::new("git")
116 .args([
117 "-c",
118 "init.defaultBranch=main",
119 "init",
120 repo.to_string_lossy().as_ref(),
121 ])
122 .status()
123 .unwrap();
124 assert!(status.success());
125
126 let git_dir = resolve_git_dir(&repo).unwrap();
127 assert!(git_dir.ends_with(".git"));
128 }
129}