A library for writing host-specific, single-binary configuration management and deployment tools
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

121 lines
3.3 KiB

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<_C, C, P, S, B> 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<_C: CommandRunner, C: Borrow<_C>, P: AsRef<Path>, S, B> Checkout<_C, C, P, S, B> {
async fn run_git(&self, args: &[impl AsRef<OsStr>]) -> Result<Vec<u8>, Box<dyn Error>> {
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<_C: CommandRunner, C: Borrow<_C>, P: AsRef<Path>, S: AsRef<str>, B: AsRef<str>> Symbol
for Checkout<_C, C, P, S, B>
{
async fn target_reached(&self) -> Result<bool, Box<dyn Error>> {
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<dyn Error>> {
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, Symbol};
use crate::async_utils::run;
use crate::command_runner::MockCommandRunner;
use mockall::Sequence;
#[test]
fn test() {
let mut seq = Sequence::new();
let mut c = MockCommandRunner::new();
c.expect_get_output()
.times(1)
.withf(|cmd, args| cmd == "git" && args == ["-C", ".", "rev-list", "-1", "HEAD"])
.returning(|_, _| Ok("".into()));
c.expect_get_output()
.times(1)
.in_sequence(&mut seq)
.withf(|cmd, args| cmd == "git" && args == ["-C", ".", "fetch", "source", "branch"])
.returning(|_, _| Ok("".into()));
c.expect_get_output()
.times(1)
.in_sequence(&mut seq)
.withf(|cmd, args| cmd == "git" && args == ["-C", ".", "rev-list", "-1", "FETCH_HEAD"])
.returning(|_, _| Ok("".into()));
let checkout = Checkout::new(".", "source", "branch", c);
assert!(run(checkout.target_reached()).unwrap());
}
}