fluidattacks_core_git/clone/
reason.rs1use anyhow::{Context, Result};
4use tokio::process::Command;
5
6const REDACTED: &str = "[REDACTED]";
7
8pub async fn run(command: &mut Command, transport: &str, secrets: &[&str]) -> Result<()> {
12 let output = command
13 .output()
14 .await
15 .with_context(|| format!("running git clone ({transport})"))?;
16
17 if output.status.success() {
18 return Ok(());
19 }
20
21 let said = String::from_utf8_lossy(&output.stderr);
22 let said = hide(said.trim(), secrets);
23
24 if said.is_empty() {
25 anyhow::bail!(
26 "git clone ({transport}) failed with status {}",
27 output.status
28 );
29 }
30
31 anyhow::bail!("{said}");
32}
33
34#[must_use]
37pub fn hide(said: &str, secrets: &[&str]) -> String {
38 secrets
39 .iter()
40 .filter(|secret| !secret.is_empty())
41 .fold(said.to_owned(), |text, secret| {
42 text.replace(secret, REDACTED)
43 })
44}
45
46#[cfg(test)]
47mod tests {
48 use tokio::process::Command;
49
50 use super::run;
51
52 fn shell(script: &str) -> Command {
53 let mut command = Command::new("sh");
54 command.args(["-c", script]);
55
56 command
57 }
58
59 #[tokio::test]
60 async fn a_clone_that_worked_says_nothing() {
61 assert!(run(&mut shell("exit 0"), "TEST", &[]).await.is_ok());
62 }
63
64 #[tokio::test]
65 async fn a_clone_that_failed_carries_what_it_printed() {
66 let outcome = run(
67 &mut shell("echo 'fatal: no such repository' >&2; exit 128"),
68 "TEST",
69 &[],
70 )
71 .await;
72
73 assert_eq!(
74 outcome.unwrap_err().to_string(),
75 "fatal: no such repository"
76 );
77 }
78
79 #[tokio::test]
80 async fn a_clone_that_printed_nothing_carries_its_status() {
81 let outcome = run(&mut shell("exit 3"), "TEST", &[]).await;
82
83 assert!(outcome.unwrap_err().to_string().contains("(TEST)"));
84 }
85
86 #[tokio::test]
87 async fn a_secret_the_server_answered_is_taken_out() {
88 let outcome = run(
89 &mut shell("echo 'remote: rejected s3cr3t' >&2; exit 128"),
90 "TEST",
91 &["s3cr3t"],
92 )
93 .await;
94
95 assert_eq!(
96 outcome.unwrap_err().to_string(),
97 "remote: rejected [REDACTED]"
98 );
99 }
100
101 #[tokio::test]
102 async fn a_credential_that_is_empty_leaves_the_message_alone() {
103 let outcome = run(
104 &mut shell("echo 'fatal: no such repository' >&2; exit 128"),
105 "TEST",
106 &[""],
107 )
108 .await;
109
110 assert_eq!(
111 outcome.unwrap_err().to_string(),
112 "fatal: no such repository"
113 );
114 }
115}