Line data Source code
1 : use crate::CommandExt; 2 : use anyhow::{Context, Result}; 3 : use git2::Repository; 4 : use if_chain::if_chain; 5 : use std::{ 6 : path::Path, 7 : process::{Command, Stdio}, 8 : }; 9 : 10 : // smoelius: I think this imitates Cargo's default behavior: 11 : // https://doc.rust-lang.org/cargo/reference/config.html#netretry 12 : const N_RETRIES: usize = 2; 13 : 14 : // smoelius: I think I may have run into https://github.com/libgit2/libgit2/issues/5294 a few times, 15 : // but I don't know of a good general-purpose solution. TODO: Investigate whether/how Cargo's 16 : // wrappers handle this. 17 9 : pub fn clone(url: &str, refname: &str, path: &Path, quiet: bool) -> Result<Repository> { 18 9 : let repository = if Command::new("git") 19 9 : .args(["--version"]) 20 9 : .stdout(Stdio::null()) 21 9 : .success() 22 9 : .is_ok() 23 : { 24 9 : clone_with_cli(url, path, quiet) 25 : } else { 26 0 : clone_with_git2(url, path, quiet) 27 0 : }?; 28 : 29 9 : checkout(&repository, refname)?; 30 : 31 9 : Ok(repository) 32 9 : } 33 : 34 9 : fn clone_with_cli(url: &str, path: &Path, quiet: bool) -> Result<Repository> { 35 9 : let mut command = Command::new("git"); 36 9 : command.args(["clone", url, &path.to_string_lossy()]); 37 9 : if quiet { 38 0 : command.args(["--quiet"]); 39 9 : } 40 9 : command.success()?; 41 : 42 9 : Repository::open(path).map_err(Into::into) 43 9 : } 44 : 45 0 : fn clone_with_git2(url: &str, path: &Path, _quiet: bool) -> Result<Repository> { 46 0 : let mut result = Repository::clone(url, path); 47 : 48 0 : for _ in 0..N_RETRIES { 49 0 : if result.is_err() { 50 0 : result = Repository::clone(url, path); 51 0 : } else { 52 0 : break; 53 : } 54 : } 55 : 56 0 : result.map_err(Into::into) 57 0 : } 58 : 59 : // smoelius: `checkout` is based on: https://stackoverflow.com/a/67240436 60 9 : pub fn checkout(repository: &Repository, refname: &str) -> Result<()> { 61 9 : let (object, reference) = repository 62 9 : .revparse_ext(refname) 63 9 : .with_context(|| format!("`revparse_ext` failed for `{refname}`"))?; 64 : 65 9 : repository 66 9 : .checkout_tree(&object, None) 67 9 : .with_context(|| format!("`checkout_tree` failed for `{object:?}`"))?; 68 : 69 0 : if_chain! { 70 9 : if let Some(reference) = reference; 71 9 : if let Some(refname) = reference.name(); 72 : then { 73 9 : repository 74 9 : .set_head(refname) 75 9 : .with_context(|| format!("`set_head` failed for `{refname}`"))?; 76 : } else { 77 0 : repository 78 0 : .set_head_detached(object.id()) 79 0 : .with_context(|| format!("`set_head_detached` failed for `{}`", object.id()))?; 80 : } 81 : } 82 : 83 9 : Ok(()) 84 9 : }