use std::path::Path;
use crate::cli;
use crate::errors::ResetError;
pub async fn reset_repo(repo_path: &Path) -> Result<(), ResetError> {
cli::reset_hard(repo_path).await?;
let root = repo_path.to_path_buf();
tokio::task::spawn_blocking(move || super::fs::remove_symlinks(&root))
.await
.unwrap_or_else(|error| std::panic::resume_unwind(error.into_panic()))?;
Ok(())
}
#[cfg(test)]
mod tests {
use super::reset_repo;
use crate::errors::ResetError;
use std::path::Path;
fn git(repo: &Path, args: &[&str]) {
let status = std::process::Command::new("git")
.arg("-C")
.arg(repo)
.args(args)
.status()
.unwrap();
assert!(status.success());
}
#[tokio::test]
async fn only_the_root_of_the_repository_gets_reset() {
let dir = tempfile::tempdir().unwrap();
std::env::set_var("GIT_CONFIG_GLOBAL", dir.path().join("gitconfig"));
let repo = dir.path().join("repo");
std::fs::create_dir_all(repo.join("sub/deep")).unwrap();
git(&repo, &["init", "-q"]);
std::fs::write(repo.join("f.txt"), b"committed").unwrap();
git(&repo, &["add", "-A"]);
git(
&repo,
&[
"-c",
"user.email=dev@fluidattacks.com",
"-c",
"user.name=dev",
"commit",
"-q",
"-m",
"files",
],
);
std::fs::write(repo.join("f.txt"), b"dirty").unwrap();
for not_the_root in [repo.join("sub/deep"), repo.join(".git")] {
let outcome = reset_repo(¬_the_root).await;
assert!(matches!(outcome, Err(ResetError::Git(_))), "{outcome:?}");
}
assert_eq!(
std::fs::read_to_string(repo.join("f.txt")).unwrap(),
"dirty"
);
reset_repo(&repo).await.unwrap();
assert_eq!(
std::fs::read_to_string(repo.join("f.txt")).unwrap(),
"committed"
);
}
#[tokio::test]
async fn the_reset_runs_no_fsmonitor_of_the_repository() {
let dir = tempfile::tempdir().unwrap();
let marker = dir.path().join("pwned");
let payload = dir.path().join("payload.sh");
std::fs::write(&payload, format!("#!/bin/sh\ntouch {}\n", marker.display())).unwrap();
std::fs::set_permissions(
&payload,
std::os::unix::fs::PermissionsExt::from_mode(0o755),
)
.unwrap();
let repo = dir.path().join("repo");
std::fs::create_dir_all(&repo).unwrap();
git(&repo, &["init", "-q"]);
std::fs::write(repo.join("f.txt"), b"committed").unwrap();
git(&repo, &["add", "-A"]);
git(
&repo,
&[
"-c",
"user.email=dev@fluidattacks.com",
"-c",
"user.name=dev",
"commit",
"-q",
"-m",
"files",
],
);
git(
&repo,
&["config", "core.fsmonitor", &payload.to_string_lossy()],
);
reset_repo(&repo).await.unwrap();
assert!(!marker.exists());
}
#[tokio::test]
#[cfg(unix)]
async fn a_symlink_that_cannot_be_removed_fails_the_reset() {
use std::os::unix::fs::PermissionsExt;
let dir = tempfile::tempdir().unwrap();
let repo = dir.path().join("repo");
let sealed = repo.join("sealed");
std::fs::create_dir_all(&sealed).unwrap();
std::os::unix::fs::symlink("target.txt", sealed.join("link.lnk")).unwrap();
git(&repo, &["init", "-q"]);
git(&repo, &["add", "-A"]);
git(
&repo,
&[
"-c",
"user.email=dev@fluidattacks.com",
"-c",
"user.name=dev",
"commit",
"-q",
"-m",
"files",
],
);
std::fs::set_permissions(&sealed, std::fs::Permissions::from_mode(0o500)).unwrap();
if std::fs::write(sealed.join("probe"), b"").is_ok() {
std::fs::set_permissions(&sealed, std::fs::Permissions::from_mode(0o755)).unwrap();
return;
}
let result = reset_repo(&repo).await;
std::fs::set_permissions(&sealed, std::fs::Permissions::from_mode(0o755)).unwrap();
assert!(matches!(result, Err(ResetError::Symlinks(_))), "{result:?}");
assert!(sealed.join("link.lnk").is_symlink());
}
}