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.

156 lines
4.1 KiB

7 years ago
8 years ago
8 years ago
5 years ago
5 years ago
8 years ago
8 years ago
8 years ago
8 years ago
4 years ago
8 years ago
3 years ago
3 years ago
3 years ago
  1. use crate::async_utils::try_join;
  2. use crate::command_runner::CommandRunner;
  3. use crate::symbols::Symbol;
  4. use async_trait::async_trait;
  5. use std::borrow::Borrow;
  6. use std::error::Error;
  7. use std::ffi::OsStr;
  8. use std::marker::PhantomData;
  9. use std::path::Path;
  10. #[derive(Debug)]
  11. pub struct Checkout<_C, C, P, S, B> {
  12. target: P,
  13. source: S,
  14. branch: B,
  15. command_runner: C,
  16. phantom: PhantomData<_C>,
  17. }
  18. impl<C, _C, P, S, B> Checkout<_C, C, P, S, B> {
  19. pub fn new(target: P, source: S, branch: B, command_runner: C) -> Self {
  20. Self {
  21. target,
  22. source,
  23. branch,
  24. command_runner,
  25. phantom: PhantomData::default(),
  26. }
  27. }
  28. }
  29. impl<C: CommandRunner, _C: Borrow<C>, P: AsRef<Path>, S, B> Checkout<C, _C, P, S, B> {
  30. async fn run_git(&self, args: &[impl AsRef<OsStr>]) -> Result<Vec<u8>, Box<dyn Error>> {
  31. let mut new_args = Vec::with_capacity(args.len() + 2);
  32. new_args.extend_from_slice(args!["-C", self.target.as_ref()]);
  33. new_args.extend(args.iter().map(AsRef::as_ref));
  34. self
  35. .command_runner
  36. .borrow()
  37. .get_output("git", &new_args)
  38. .await
  39. }
  40. }
  41. #[async_trait(?Send)]
  42. impl<C: CommandRunner, _C: Borrow<C>, P: AsRef<Path>, S: AsRef<str>, B: AsRef<str>> Symbol
  43. for Checkout<C, _C, P, S, B>
  44. {
  45. async fn target_reached(&self) -> Result<bool, Box<dyn Error>> {
  46. if !self.target.as_ref().exists() {
  47. return Ok(false);
  48. }
  49. let fetch_head_f = async {
  50. self
  51. .run_git(args!["fetch", self.source, self.branch])
  52. .await?;
  53. // git rev-list resolves tag objects
  54. self.run_git(&["rev-list", "-1", "FETCH_HEAD"]).await
  55. };
  56. let head_f = self.run_git(&["rev-list", "-1", "HEAD"]);
  57. let (fetch_head, head) = try_join!(fetch_head_f, head_f)?;
  58. Ok(fetch_head == head)
  59. }
  60. async fn execute(&self) -> Result<(), Box<dyn Error>> {
  61. if self.target.as_ref().exists() {
  62. self
  63. .run_git(&["fetch", self.source.as_ref(), self.branch.as_ref()])
  64. .await?;
  65. self.run_git(&["merge", "FETCH_HEAD"]).await?;
  66. } else {
  67. self
  68. .command_runner
  69. .borrow()
  70. .run_successfully(
  71. "git",
  72. args![
  73. "clone",
  74. "--depth",
  75. "1",
  76. "-b",
  77. self.branch.as_ref(),
  78. self.source.as_ref(),
  79. self.target.as_ref(),
  80. ],
  81. )
  82. .await?;
  83. }
  84. Ok(())
  85. }
  86. }
  87. #[cfg(test)]
  88. mod test {
  89. use super::Checkout;
  90. use crate::async_utils::run;
  91. use crate::async_utils::sleep;
  92. use crate::command_runner::CommandRunner;
  93. use crate::symbols::Symbol;
  94. use async_trait::async_trait;
  95. use std::cell::RefCell;
  96. use std::ffi::{OsStr, OsString};
  97. use std::io::Result as IoResult;
  98. use std::os::unix::process::ExitStatusExt;
  99. use std::process::{ExitStatus, Output};
  100. use std::time::{Duration, Instant};
  101. struct DummyCommandRunner {
  102. pub args: RefCell<Vec<Vec<OsString>>>,
  103. }
  104. #[async_trait(?Send)]
  105. impl CommandRunner for DummyCommandRunner {
  106. async fn run(&self, program: &str, args: &[&OsStr], stdin: &str) -> IoResult<Output> {
  107. assert_eq!(program, "git");
  108. assert_eq!(stdin, "");
  109. sleep(Duration::from_millis(50)).await;
  110. self
  111. .args
  112. .borrow_mut()
  113. .push(args.iter().map(|a| a.to_os_string()).collect());
  114. Ok(Output {
  115. status: ExitStatus::from_raw(0),
  116. stdout: vec![],
  117. stderr: vec![],
  118. })
  119. }
  120. }
  121. #[test]
  122. fn test() {
  123. let c = DummyCommandRunner {
  124. args: RefCell::new(vec![]),
  125. };
  126. let checkout: Checkout<DummyCommandRunner, _, _, _, _> =
  127. Checkout::new("target", "source", "branch", &c);
  128. let start = Instant::now();
  129. assert!(run(checkout.target_reached()).unwrap());
  130. let end = Instant::now();
  131. let mut args = c.args.into_inner();
  132. let first_two_args = &mut args[0..2];
  133. first_two_args.sort_unstable();
  134. assert_eq!(
  135. first_two_args,
  136. [
  137. ["-C", "target", "fetch", "source", "branch"],
  138. ["-C", "target", "rev-list", "-1", "HEAD"],
  139. ]
  140. );
  141. drop(first_two_args);
  142. assert_eq!(args[2],
  143. ["-C", "target", "rev-list", "-1", "FETCH_HEAD"]);
  144. assert!((end - start).as_millis() >= 100);
  145. assert!((end - start).as_millis() < 150);
  146. }
  147. }