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