use crate::async_utils::try_join; use crate::command_runner::CommandRunner; use crate::symbols::Symbol; use async_trait::async_trait; use std::borrow::Borrow; use std::error::Error; use std::ffi::OsStr; use std::marker::PhantomData; use std::path::Path; #[derive(Debug)] pub struct Checkout<_C, C, P, S, B> { target: P, source: S, branch: B, command_runner: C, phantom: PhantomData<_C>, } impl Checkout<_C, C, P, S, B> { pub fn new(target: P, source: S, branch: B, command_runner: C) -> Self { Self { target, source, branch, command_runner, phantom: PhantomData::default(), } } } impl, P: AsRef, S, B> Checkout { async fn run_git(&self, args: &[impl AsRef]) -> Result, Box> { let mut new_args = Vec::with_capacity(args.len() + 2); new_args.extend_from_slice(args!["-C", self.target.as_ref()]); new_args.extend(args.iter().map(AsRef::as_ref)); self .command_runner .borrow() .get_output("git", &new_args) .await } } #[async_trait(?Send)] impl, P: AsRef, S: AsRef, B: AsRef> Symbol for Checkout { async fn target_reached(&self) -> Result> { if !self.target.as_ref().exists() { return Ok(false); } let fetch_head_f = async { self .run_git(args!["fetch", self.source, self.branch]) .await?; // git rev-list resolves tag objects self.run_git(&["rev-list", "-1", "FETCH_HEAD"]).await }; let head_f = self.run_git(&["rev-list", "-1", "HEAD"]); let (fetch_head, head) = try_join!(fetch_head_f, head_f)?; Ok(fetch_head == head) } async fn execute(&self) -> Result<(), Box> { if self.target.as_ref().exists() { self .run_git(&["fetch", self.source.as_ref(), self.branch.as_ref()]) .await?; self.run_git(&["merge", "FETCH_HEAD"]).await?; } else { self .command_runner .borrow() .run_successfully( "git", args![ "clone", "--depth", "1", "-b", self.branch.as_ref(), self.source.as_ref(), self.target.as_ref(), ], ) .await?; } Ok(()) } } #[cfg(test)] mod test { use super::Checkout; use crate::async_utils::run; use crate::async_utils::sleep; use crate::command_runner::CommandRunner; use crate::symbols::Symbol; use async_trait::async_trait; use std::cell::RefCell; use std::ffi::{OsStr, OsString}; use std::io::Result as IoResult; use std::os::unix::process::ExitStatusExt; use std::process::{ExitStatus, Output}; use std::time::{Duration, Instant}; struct DummyCommandRunner { pub args: RefCell>>, } #[async_trait(?Send)] impl CommandRunner for DummyCommandRunner { async fn run(&self, program: &str, args: &[&OsStr], stdin: &str) -> IoResult { assert_eq!(program, "git"); assert_eq!(stdin, ""); sleep(Duration::from_millis(50)).await; self .args .borrow_mut() .push(args.iter().map(|a| a.to_os_string()).collect()); Ok(Output { status: ExitStatus::from_raw(0), stdout: vec![], stderr: vec![], }) } } #[test] fn test() { let c = DummyCommandRunner { args: RefCell::new(vec![]), }; let checkout: Checkout = Checkout::new("target", "source", "branch", &c); let start = Instant::now(); assert!(run(checkout.target_reached()).unwrap()); let end = Instant::now(); assert_eq!( c.args.into_inner(), [ ["-C", "target", "fetch", "source", "branch"], ["-C", "target", "rev-list", "-1", "HEAD"], ["-C", "target", "rev-list", "-1", "FETCH_HEAD"] ] ); assert!((end - start).as_millis() >= 100); assert!((end - start).as_millis() < 150); } }