Skip to main content

fluidattacks_core_git/
ops.rs

1use std::path::Path;
2
3use crate::cli;
4
5/// Runs `git reset --hard`, removes symlinks, and returns whether it succeeded.
6pub async fn reset_repo(repo_path: &Path) -> bool {
7    if !repo_path.join(".git").exists() {
8        tracing::error!(repo_path = %repo_path.display(), "not the root of a repository");
9        return false;
10    }
11    if let Err(error) = cli::reset_hard(repo_path).await {
12        tracing::error!(repo_path = %repo_path.display(), error = %error, "failed to reset the repository");
13        return false;
14    }
15    let root = repo_path.to_path_buf();
16    match tokio::task::spawn_blocking(move || super::fs::remove_symlinks(&root)).await {
17        Ok(Ok(())) => true,
18        Ok(Err(error)) => {
19            tracing::error!(repo_path = %repo_path.display(), error = %error, "failed to remove the symlinks");
20            false
21        }
22        Err(error) => {
23            tracing::error!(repo_path = %repo_path.display(), error = %error, "failed to remove the symlinks");
24            false
25        }
26    }
27}
28
29#[cfg(test)]
30mod tests {
31    use super::reset_repo;
32    use std::path::Path;
33
34    fn git(repo: &Path, args: &[&str]) {
35        let status = std::process::Command::new("git")
36            .arg("-C")
37            .arg(repo)
38            .args(args)
39            .status()
40            .unwrap();
41        assert!(status.success());
42    }
43
44    #[tokio::test]
45    async fn only_the_root_of_the_repository_gets_reset() {
46        let dir = tempfile::tempdir().unwrap();
47        std::env::set_var("GIT_CONFIG_GLOBAL", dir.path().join("gitconfig"));
48
49        let repo = dir.path().join("repo");
50        std::fs::create_dir_all(repo.join("sub/deep")).unwrap();
51        git(&repo, &["init", "-q"]);
52        std::fs::write(repo.join("f.txt"), b"committed").unwrap();
53        git(&repo, &["add", "-A"]);
54        git(
55            &repo,
56            &[
57                "-c",
58                "user.email=dev@fluidattacks.com",
59                "-c",
60                "user.name=dev",
61                "commit",
62                "-q",
63                "-m",
64                "files",
65            ],
66        );
67        std::fs::write(repo.join("f.txt"), b"dirty").unwrap();
68
69        let deep = repo.join("sub/deep");
70        assert!(!reset_repo(&deep).await);
71        assert_eq!(
72            std::fs::read_to_string(repo.join("f.txt")).unwrap(),
73            "dirty"
74        );
75
76        assert!(reset_repo(&repo).await);
77        assert_eq!(
78            std::fs::read_to_string(repo.join("f.txt")).unwrap(),
79            "committed"
80        );
81    }
82
83    #[tokio::test]
84    async fn the_reset_runs_no_fsmonitor_of_the_repository() {
85        let dir = tempfile::tempdir().unwrap();
86        let marker = dir.path().join("pwned");
87        let payload = dir.path().join("payload.sh");
88        std::fs::write(&payload, format!("#!/bin/sh\ntouch {}\n", marker.display())).unwrap();
89        std::fs::set_permissions(
90            &payload,
91            std::os::unix::fs::PermissionsExt::from_mode(0o755),
92        )
93        .unwrap();
94
95        let repo = dir.path().join("repo");
96        std::fs::create_dir_all(&repo).unwrap();
97        git(&repo, &["init", "-q"]);
98        std::fs::write(repo.join("f.txt"), b"committed").unwrap();
99        git(&repo, &["add", "-A"]);
100        git(
101            &repo,
102            &[
103                "-c",
104                "user.email=dev@fluidattacks.com",
105                "-c",
106                "user.name=dev",
107                "commit",
108                "-q",
109                "-m",
110                "files",
111            ],
112        );
113        git(
114            &repo,
115            &["config", "core.fsmonitor", &payload.to_string_lossy()],
116        );
117
118        assert!(reset_repo(&repo).await);
119        assert!(!marker.exists());
120    }
121}