267 lines
8.4 KiB
Rust
267 lines
8.4 KiB
Rust
use quicommit::git::GitRepo;
|
|
use std::fs;
|
|
use std::path::Path;
|
|
use std::process::Command;
|
|
use tempfile::TempDir;
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Helpers
|
|
// ---------------------------------------------------------------------------
|
|
|
|
/// Run a git command in the given directory, returning stdout as a String.
|
|
/// Panics if the command fails.
|
|
fn git(dir: &Path, args: &[&str]) -> String {
|
|
let output = Command::new("git")
|
|
.args(args)
|
|
.current_dir(dir)
|
|
.output()
|
|
.expect("Failed to execute git command");
|
|
if !output.status.success() {
|
|
let stderr = String::from_utf8_lossy(&output.stderr);
|
|
panic!("git {:?} failed in {:?}: {}", args, dir, stderr);
|
|
}
|
|
String::from_utf8_lossy(&output.stdout).to_string()
|
|
}
|
|
|
|
/// Initialize a new git repo in the given directory and configure a local
|
|
/// user identity so commits can be created.
|
|
fn init_repo(dir: &Path) {
|
|
git(dir, &["init"]);
|
|
git(dir, &["config", "user.name", "Test User"]);
|
|
git(dir, &["config", "user.email", "test@example.com"]);
|
|
// Disable commit signing in case the global config enables it.
|
|
git(dir, &["config", "commit.gpgsign", "false"]);
|
|
}
|
|
|
|
/// Write a file with the given content, creating parent directories as needed.
|
|
fn write_file(dir: &Path, rel_path: &str, content: &str) {
|
|
let file_path = dir.join(rel_path);
|
|
if let Some(parent) = file_path.parent() {
|
|
fs::create_dir_all(parent).expect("Failed to create parent directories");
|
|
}
|
|
fs::write(&file_path, content).expect("Failed to write file");
|
|
}
|
|
|
|
/// Get the list of files in the index as a String (one path per line).
|
|
fn ls_files(dir: &Path) -> String {
|
|
git(dir, &["ls-files"])
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Tests for is_path_ignored
|
|
// ---------------------------------------------------------------------------
|
|
|
|
#[test]
|
|
fn test_is_path_ignored_with_ignored_path() {
|
|
let temp_dir = TempDir::new().unwrap();
|
|
let repo_path = temp_dir.path();
|
|
init_repo(repo_path);
|
|
|
|
write_file(repo_path, ".gitignore", "__pycache__/\n");
|
|
write_file(repo_path, "__pycache__/foo.pyc", "bytecode");
|
|
write_file(repo_path, "subdir/__pycache__/bar.pyc", "more bytecode");
|
|
|
|
let repo = GitRepo::open(repo_path).expect("Failed to open repo");
|
|
|
|
// Top-level ignored file
|
|
assert!(
|
|
repo.is_path_ignored("__pycache__/foo.pyc").unwrap(),
|
|
"__pycache__/foo.pyc should be ignored"
|
|
);
|
|
|
|
// Nested ignored file under a subdirectory
|
|
assert!(
|
|
repo.is_path_ignored("subdir/__pycache__/bar.pyc").unwrap(),
|
|
"subdir/__pycache__/bar.pyc should be ignored"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_is_path_ignored_with_non_ignored_path() {
|
|
let temp_dir = TempDir::new().unwrap();
|
|
let repo_path = temp_dir.path();
|
|
init_repo(repo_path);
|
|
|
|
write_file(repo_path, ".gitignore", "__pycache__/\n");
|
|
write_file(repo_path, "src/main.rs", "fn main() {}");
|
|
|
|
let repo = GitRepo::open(repo_path).expect("Failed to open repo");
|
|
|
|
assert!(
|
|
!repo.is_path_ignored("src/main.rs").unwrap(),
|
|
"src/main.rs should not be ignored"
|
|
);
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Tests for stage_all
|
|
// ---------------------------------------------------------------------------
|
|
|
|
#[test]
|
|
fn test_stage_all_removes_ignored_tracked_files() {
|
|
let temp_dir = TempDir::new().unwrap();
|
|
let repo_path = temp_dir.path();
|
|
init_repo(repo_path);
|
|
|
|
// Create and commit __pycache__/foo.pyc so it becomes a tracked file.
|
|
write_file(repo_path, "__pycache__/foo.pyc", "original bytecode");
|
|
git(repo_path, &["add", "__pycache__/foo.pyc"]);
|
|
git(repo_path, &["commit", "-m", "initial commit"]);
|
|
|
|
// Add a .gitignore that now ignores __pycache__/.
|
|
write_file(repo_path, ".gitignore", "__pycache__/\n");
|
|
|
|
// Modify the tracked file so the working tree has unstaged changes.
|
|
write_file(repo_path, "__pycache__/foo.pyc", "modified bytecode");
|
|
|
|
let repo = GitRepo::open(repo_path).expect("Failed to open repo");
|
|
let removed = repo.stage_all().expect("stage_all should succeed");
|
|
|
|
assert!(
|
|
removed.iter().any(|f| f == "__pycache__/foo.pyc"),
|
|
"stage_all should return __pycache__/foo.pyc in removed list, got: {:?}",
|
|
removed
|
|
);
|
|
|
|
// Verify the file is no longer in the index.
|
|
let files = ls_files(repo_path);
|
|
assert!(
|
|
!files.lines().any(|l| l == "__pycache__/foo.pyc"),
|
|
"__pycache__/foo.pyc should no longer be in the index, got: {}",
|
|
files
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_stage_all_no_ignored_files_returns_empty() {
|
|
let temp_dir = TempDir::new().unwrap();
|
|
let repo_path = temp_dir.path();
|
|
init_repo(repo_path);
|
|
|
|
write_file(repo_path, ".gitignore", "*.log\n");
|
|
write_file(repo_path, "src/main.rs", "fn main() {}");
|
|
|
|
let repo = GitRepo::open(repo_path).expect("Failed to open repo");
|
|
let removed = repo.stage_all().expect("stage_all should succeed");
|
|
|
|
assert!(
|
|
removed.is_empty(),
|
|
"stage_all should return empty Vec when no ignored files are tracked, got: {:?}",
|
|
removed
|
|
);
|
|
|
|
// Verify src/main.rs was staged.
|
|
let files = ls_files(repo_path);
|
|
assert!(
|
|
files.lines().any(|l| l == "src/main.rs"),
|
|
"src/main.rs should be in the index, got: {}",
|
|
files
|
|
);
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Tests for stage_files
|
|
// ---------------------------------------------------------------------------
|
|
|
|
#[test]
|
|
fn test_stage_files_skips_ignored_paths() {
|
|
let temp_dir = TempDir::new().unwrap();
|
|
let repo_path = temp_dir.path();
|
|
init_repo(repo_path);
|
|
|
|
write_file(repo_path, ".gitignore", "__pycache__/\n");
|
|
write_file(repo_path, "__pycache__/foo.pyc", "bytecode");
|
|
write_file(repo_path, "src/main.rs", "fn main() {}");
|
|
|
|
let repo = GitRepo::open(repo_path).expect("Failed to open repo");
|
|
let skipped = repo
|
|
.stage_files(&["__pycache__/foo.pyc", "src/main.rs"])
|
|
.expect("stage_files should succeed");
|
|
|
|
assert!(
|
|
skipped.iter().any(|f| f == "__pycache__/foo.pyc"),
|
|
"skipped list should contain __pycache__/foo.pyc, got: {:?}",
|
|
skipped
|
|
);
|
|
assert!(
|
|
!skipped.iter().any(|f| f == "src/main.rs"),
|
|
"skipped list should not contain src/main.rs, got: {:?}",
|
|
skipped
|
|
);
|
|
|
|
let files = ls_files(repo_path);
|
|
assert!(
|
|
files.lines().any(|l| l == "src/main.rs"),
|
|
"src/main.rs should be staged, got: {}",
|
|
files
|
|
);
|
|
assert!(
|
|
!files.lines().any(|l| l == "__pycache__/foo.pyc"),
|
|
"__pycache__/foo.pyc should not be staged, got: {}",
|
|
files
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_stage_files_all_paths_ignored() {
|
|
let temp_dir = TempDir::new().unwrap();
|
|
let repo_path = temp_dir.path();
|
|
init_repo(repo_path);
|
|
|
|
write_file(repo_path, ".gitignore", "__pycache__/\n");
|
|
write_file(repo_path, "__pycache__/foo.pyc", "bytecode");
|
|
|
|
let repo = GitRepo::open(repo_path).expect("Failed to open repo");
|
|
let skipped = repo
|
|
.stage_files(&["__pycache__/foo.pyc"])
|
|
.expect("stage_files should succeed");
|
|
|
|
assert!(
|
|
skipped.iter().any(|f| f == "__pycache__/foo.pyc"),
|
|
"skipped list should contain __pycache__/foo.pyc, got: {:?}",
|
|
skipped
|
|
);
|
|
|
|
// Verify the index remains empty (the file was not staged).
|
|
let files = ls_files(repo_path);
|
|
assert!(
|
|
files.trim().is_empty(),
|
|
"index should be empty, got: {}",
|
|
files
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_stage_files_normal_paths_unchanged_behavior() {
|
|
let temp_dir = TempDir::new().unwrap();
|
|
let repo_path = temp_dir.path();
|
|
init_repo(repo_path);
|
|
|
|
// No .gitignore (or one that does not match these paths).
|
|
write_file(repo_path, "src/main.rs", "fn main() {}");
|
|
write_file(repo_path, "src/lib.rs", "pub fn lib() {}");
|
|
|
|
let repo = GitRepo::open(repo_path).expect("Failed to open repo");
|
|
let skipped = repo
|
|
.stage_files(&["src/main.rs", "src/lib.rs"])
|
|
.expect("stage_files should succeed");
|
|
|
|
assert!(
|
|
skipped.is_empty(),
|
|
"skipped list should be empty, got: {:?}",
|
|
skipped
|
|
);
|
|
|
|
let files = ls_files(repo_path);
|
|
assert!(
|
|
files.lines().any(|l| l == "src/main.rs"),
|
|
"src/main.rs should be staged, got: {}",
|
|
files
|
|
);
|
|
assert!(
|
|
files.lines().any(|l| l == "src/lib.rs"),
|
|
"src/lib.rs should be staged, got: {}",
|
|
files
|
|
);
|
|
}
|