fluidattacks_core_git/
ops.rs1use std::path::Path;
2
3use anyhow::Result;
4
5use crate::cli;
6
7pub async fn reset_repo(repo_path: &Path) -> Result<bool> {
11 if !repo_path.join(".git").exists() {
12 tracing::error!(repo_path = %repo_path.display(), "not the root of a repository");
13 return Ok(false);
14 }
15 if let Err(error) = cli::reset_hard(repo_path).await {
16 tracing::error!(repo_path = %repo_path.display(), error = %error, "failed to reset the repository");
17 return Ok(false);
18 }
19 let root = repo_path.to_path_buf();
20 tokio::task::spawn_blocking(move || super::fs::remove_symlinks(&root)).await??;
21 Ok(true)
22}
23
24#[cfg(test)]
25mod tests {
26 use super::reset_repo;
27 use std::path::Path;
28
29 fn git(repo: &Path, args: &[&str]) {
30 let status = std::process::Command::new("git")
31 .arg("-C")
32 .arg(repo)
33 .args(args)
34 .status()
35 .unwrap();
36 assert!(status.success());
37 }
38
39 #[tokio::test]
40 async fn only_the_root_of_the_repository_gets_reset() {
41 let dir = tempfile::tempdir().unwrap();
42 std::env::set_var("GIT_CONFIG_GLOBAL", dir.path().join("gitconfig"));
43
44 let repo = dir.path().join("repo");
45 std::fs::create_dir_all(repo.join("sub/deep")).unwrap();
46 git(&repo, &["init", "-q"]);
47 std::fs::write(repo.join("f.txt"), b"committed").unwrap();
48 git(&repo, &["add", "-A"]);
49 git(
50 &repo,
51 &[
52 "-c",
53 "user.email=dev@fluidattacks.com",
54 "-c",
55 "user.name=dev",
56 "commit",
57 "-q",
58 "-m",
59 "files",
60 ],
61 );
62 std::fs::write(repo.join("f.txt"), b"dirty").unwrap();
63
64 let deep = repo.join("sub/deep");
65 assert!(!reset_repo(&deep).await.unwrap());
66 assert_eq!(
67 std::fs::read_to_string(repo.join("f.txt")).unwrap(),
68 "dirty"
69 );
70
71 assert!(reset_repo(&repo).await.unwrap());
72 assert_eq!(
73 std::fs::read_to_string(repo.join("f.txt")).unwrap(),
74 "committed"
75 );
76 }
77
78 #[tokio::test]
79 async fn the_reset_runs_no_fsmonitor_of_the_repository() {
80 let dir = tempfile::tempdir().unwrap();
81 let marker = dir.path().join("pwned");
82 let payload = dir.path().join("payload.sh");
83 std::fs::write(&payload, format!("#!/bin/sh\ntouch {}\n", marker.display())).unwrap();
84 std::fs::set_permissions(
85 &payload,
86 std::os::unix::fs::PermissionsExt::from_mode(0o755),
87 )
88 .unwrap();
89
90 let repo = dir.path().join("repo");
91 std::fs::create_dir_all(&repo).unwrap();
92 git(&repo, &["init", "-q"]);
93 std::fs::write(repo.join("f.txt"), b"committed").unwrap();
94 git(&repo, &["add", "-A"]);
95 git(
96 &repo,
97 &[
98 "-c",
99 "user.email=dev@fluidattacks.com",
100 "-c",
101 "user.name=dev",
102 "commit",
103 "-q",
104 "-m",
105 "files",
106 ],
107 );
108 git(
109 &repo,
110 &["config", "core.fsmonitor", &payload.to_string_lossy()],
111 );
112
113 assert!(reset_repo(&repo).await.unwrap());
114 assert!(!marker.exists());
115 }
116
117 #[tokio::test]
118 #[cfg(unix)]
119 async fn a_symlink_that_cannot_be_removed_fails_the_reset() {
120 use std::os::unix::fs::PermissionsExt;
121
122 let dir = tempfile::tempdir().unwrap();
123 let repo = dir.path().join("repo");
124 let sealed = repo.join("sealed");
125 std::fs::create_dir_all(&sealed).unwrap();
126 std::os::unix::fs::symlink("target.txt", sealed.join("link.lnk")).unwrap();
127 git(&repo, &["init", "-q"]);
128 git(&repo, &["add", "-A"]);
129 git(
130 &repo,
131 &[
132 "-c",
133 "user.email=dev@fluidattacks.com",
134 "-c",
135 "user.name=dev",
136 "commit",
137 "-q",
138 "-m",
139 "files",
140 ],
141 );
142 std::fs::set_permissions(&sealed, std::fs::Permissions::from_mode(0o500)).unwrap();
143
144 if std::fs::write(sealed.join("probe"), b"").is_ok() {
147 std::fs::set_permissions(&sealed, std::fs::Permissions::from_mode(0o755)).unwrap();
148 return;
149 }
150
151 let result = reset_repo(&repo).await;
152
153 std::fs::set_permissions(&sealed, std::fs::Permissions::from_mode(0o755)).unwrap();
154 assert!(result.is_err());
155 assert!(sealed.join("link.lnk").is_symlink());
156 }
157}