Lightweight decentralized “knot” server prototype using Iroh + ATProto.
at master 129 lines 3.6 kB view raw
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}