diff options
| author | Mehmet Samet Duman <yongdohyun@projecttick.org> | 2026-04-04 20:47:05 +0300 |
|---|---|---|
| committer | Mehmet Samet Duman <yongdohyun@projecttick.org> | 2026-04-04 20:47:05 +0300 |
| commit | 17962fd076e857921c374b4d705a54d5e1055178 (patch) | |
| tree | 6195e9cfdc913cd95b8f577eca3f39d41b089008 /ofborg/tickborg/src/checkout.rs | |
| parent | 7c7f28532f1898a81b0250f875614ad3aa494a1c (diff) | |
| download | Project-Tick-17962fd076e857921c374b4d705a54d5e1055178.tar.gz Project-Tick-17962fd076e857921c374b4d705a54d5e1055178.zip | |
NOISSUE welcome to ofborg! (tickborg)
Signed-off-by: Mehmet Samet Duman <yongdohyun@projecttick.org>
Diffstat (limited to 'ofborg/tickborg/src/checkout.rs')
| -rw-r--r-- | ofborg/tickborg/src/checkout.rs | 340 |
1 files changed, 340 insertions, 0 deletions
diff --git a/ofborg/tickborg/src/checkout.rs b/ofborg/tickborg/src/checkout.rs new file mode 100644 index 0000000000..731e68d3f0 --- /dev/null +++ b/ofborg/tickborg/src/checkout.rs @@ -0,0 +1,340 @@ +use crate::clone::{self, GitClonable}; + +use std::ffi::{OsStr, OsString}; +use std::fs; +use std::io::Error; +use std::path::{Path, PathBuf}; +use std::process::{Command, Stdio}; + +use tracing::info; + +pub struct CachedCloner { + root: PathBuf, +} + +pub fn cached_cloner(path: &Path) -> CachedCloner { + CachedCloner { + root: path.to_path_buf(), + } +} + +pub struct CachedProject { + root: PathBuf, + clone_url: String, +} + +pub struct CachedProjectCo { + root: PathBuf, + id: String, + clone_url: String, + local_reference: PathBuf, +} + +impl CachedCloner { + pub fn project(&self, name: &str, clone_url: String) -> CachedProject { + // <root>/repo/<hash>/clone + // <root>/repo/<hash>/clone.lock + // <root>/repo/<hash>/<type>/<id> + // <root>/repo/<hash>/<type>/<id>.lock + + let mut new_root = self.root.clone(); + new_root.push("repo"); + new_root.push(format!("{:x}", md5::compute(name))); + + CachedProject { + root: new_root, + clone_url, + } + } +} + +impl CachedProject { + pub fn clone_for(&self, use_category: String, id: String) -> Result<CachedProjectCo, Error> { + self.prefetch_cache()?; + + let mut new_root = self.root.clone(); + new_root.push(use_category); + + Ok(CachedProjectCo { + root: new_root, + id, + clone_url: self.clone_from(), + local_reference: self.clone_to(), + }) + } + + fn prefetch_cache(&self) -> Result<PathBuf, Error> { + fs::create_dir_all(&self.root)?; + + self.clone_repo()?; + self.fetch_repo()?; + + Ok(self.clone_to()) + } +} + +impl CachedProjectCo { + pub fn checkout_origin_ref(&self, git_ref: &OsStr) -> Result<String, Error> { + let mut pref = OsString::from("origin/"); + pref.push(git_ref); + + self.checkout_ref(&pref) + } + + pub fn checkout_ref(&self, git_ref: &OsStr) -> Result<String, Error> { + fs::create_dir_all(&self.root)?; + + self.clone_repo()?; + self.fetch_repo()?; + self.clean()?; + self.checkout(git_ref)?; + + // let build_dir = self.build_dir(); + + let canonicalized = fs::canonicalize(self.clone_to()).unwrap(); + Ok(canonicalized.to_str().unwrap().to_string()) + } + + pub fn fetch_pr(&self, pr_id: u64) -> Result<(), Error> { + let mut lock = self.lock()?; + + info!("Fetching PR #{}", pr_id); + let result = Command::new("git") + .arg("fetch") + .arg("origin") + .arg(format!("+refs/pull/{pr_id}/head:pr")) + .current_dir(self.clone_to()) + .stdout(Stdio::null()) + .status()?; + + lock.unlock(); + + if result.success() { + Ok(()) + } else { + Err(Error::other("Failed to fetch PR")) + } + } + + pub fn commit_exists(&self, commit: &OsStr) -> bool { + let mut lock = self.lock().expect("Failed to lock"); + + info!("Checking if commit {:?} exists", commit); + let result = Command::new("git") + .arg("--no-pager") + .arg("show") + .arg(commit) + .current_dir(self.clone_to()) + .stdout(Stdio::null()) + .status() + .expect("git show <commit> failed"); + + lock.unlock(); + + result.success() + } + + pub fn merge_commit(&self, commit: &OsStr) -> Result<(), Error> { + let mut lock = self.lock()?; + + info!("Merging commit {:?}", commit); + let result = Command::new("git") + .arg("merge") + .arg("--no-gpg-sign") + .arg("-m") + .arg("Automatic merge for GrahamCOfBorg") + .arg(commit) + .current_dir(self.clone_to()) + .stdout(Stdio::null()) + .status()?; + + lock.unlock(); + + if result.success() { + Ok(()) + } else { + Err(Error::other("Failed to merge")) + } + } + + pub fn commit_messages_from_head(&self, commit: &str) -> Result<Vec<String>, Error> { + let mut lock = self.lock()?; + + let result = Command::new("git") + .arg("log") + .arg("--format=format:%s") + .arg(format!("HEAD..{commit}")) + .current_dir(self.clone_to()) + .output()?; + + lock.unlock(); + + if result.status.success() { + Ok(String::from_utf8_lossy(&result.stdout) + .lines() + .map(|l| l.to_owned()) + .collect()) + } else { + Err(Error::other( + String::from_utf8_lossy(&result.stderr).to_lowercase(), + )) + } + } + + pub fn files_changed_from_head(&self, commit: &str) -> Result<Vec<String>, Error> { + let mut lock = self.lock()?; + + let result = Command::new("git") + .arg("diff") + .arg("--name-only") + .arg(format!("HEAD...{commit}")) + .current_dir(self.clone_to()) + .output()?; + + lock.unlock(); + + if result.status.success() { + Ok(String::from_utf8_lossy(&result.stdout) + .lines() + .map(|l| l.to_owned()) + .collect()) + } else { + Err(Error::other( + String::from_utf8_lossy(&result.stderr).to_lowercase(), + )) + } + } +} + +impl clone::GitClonable for CachedProjectCo { + fn clone_from(&self) -> String { + self.clone_url.clone() + } + + fn clone_to(&self) -> PathBuf { + let mut clone_path = self.root.clone(); + clone_path.push(&self.id); + clone_path + } + + fn lock_path(&self) -> PathBuf { + let mut lock_path = self.root.clone(); + lock_path.push(format!("{}.lock", self.id)); + lock_path + } + + fn extra_clone_args(&self) -> Vec<&OsStr> { + let local_ref = self.local_reference.as_ref(); + vec![ + OsStr::new("--shared"), + OsStr::new("--reference-if-able"), + local_ref, + ] + } +} + +impl clone::GitClonable for CachedProject { + fn clone_from(&self) -> String { + self.clone_url.clone() + } + + fn clone_to(&self) -> PathBuf { + let mut clone_path = self.root.clone(); + clone_path.push("clone"); + clone_path + } + + fn lock_path(&self) -> PathBuf { + let mut clone_path = self.root.clone(); + clone_path.push("clone.lock"); + clone_path + } + + fn extra_clone_args(&self) -> Vec<&OsStr> { + vec![OsStr::new("--bare")] + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::test_scratch::TestScratch; + use std::path::{Path, PathBuf}; + use std::process::{Command, Stdio}; + + fn tpath(component: &str) -> PathBuf { + Path::new(env!("CARGO_MANIFEST_DIR")).join(component) + } + + fn make_pr_repo(bare: &Path, co: &Path) -> String { + let output = Command::new("bash") + .current_dir(tpath("./test-srcs")) + .arg("./make-pr.sh") + .arg(bare) + .arg(co) + .stdout(Stdio::piped()) + .output() + .expect("building the test PR failed"); + + let stderr = + String::from_utf8(output.stderr).unwrap_or_else(|err| format!("warning: {err}")); + println!("{stderr}"); + + let hash = String::from_utf8(output.stdout).expect("Should just be a hash"); + hash.trim().to_owned() + } + + #[test] + pub fn test_commit_msg_list() { + let workingdir = TestScratch::new_dir("test-test-commit-msg-list"); + + let bare = TestScratch::new_dir("bare-commit-messages"); + let mk_co = TestScratch::new_dir("mk-commit-messages"); + let hash = make_pr_repo(&bare.path(), &mk_co.path()); + + let cloner = cached_cloner(&workingdir.path()); + let project = cloner.project("commit-msg-list", bare.string()); + let working_co = project + .clone_for("testing-commit-msgs".to_owned(), "123".to_owned()) + .expect("clone should work"); + working_co + .checkout_origin_ref(OsStr::new("master")) + .unwrap(); + + let expect: Vec<String> = vec!["check out this cool PR".to_owned()]; + + assert_eq!( + working_co + .commit_messages_from_head(&hash) + .expect("fetching messages should work",), + expect + ); + } + + #[test] + pub fn test_files_changed_list() { + let workingdir = TestScratch::new_dir("test-test-files-changed-list"); + + let bare = TestScratch::new_dir("bare-files-changed"); + let mk_co = TestScratch::new_dir("mk-files-changed"); + let hash = make_pr_repo(&bare.path(), &mk_co.path()); + + let cloner = cached_cloner(&workingdir.path()); + let project = cloner.project("commit-files-changed-list", bare.string()); + let working_co = project + .clone_for("testing-files-changed".to_owned(), "123".to_owned()) + .expect("clone should work"); + working_co + .checkout_origin_ref(OsStr::new("master")) + .unwrap(); + + let expect: Vec<String> = vec!["default.nix".to_owned(), "hi another file".to_owned()]; + + assert_eq!( + working_co + .files_changed_from_head(&hash) + .expect("fetching files changed should work",), + expect + ); + } +} |
