ok/jj
1
0
Fork 0
forked from mirrors/jj

working copy: don't remove already-tracked files in ignored directories

The recent optimization to not walk ignored directories did not
account for the case where there already are files in the ignored
directory.
This commit is contained in:
Martin von Zweigbergk 2021-05-15 08:27:28 -07:00
parent cbc3e915b7
commit a028f33e3b
2 changed files with 62 additions and 3 deletions

View file

@ -18,6 +18,7 @@ use std::convert::TryInto;
use std::fs;
use std::fs::{File, OpenOptions};
use std::io::Read;
use std::ops::Bound;
#[cfg(unix)]
use std::os::unix::fs::symlink;
#[cfg(unix)]
@ -344,10 +345,28 @@ impl TreeState {
}
if file_type.is_dir() {
let subdir = dir.join(&DirRepoPathComponent::from(name));
if !git_ignore.matches_all_files_in(&subdir.to_internal_string()) {
let disk_subdir = disk_dir.join(file_name);
work.push((subdir, disk_subdir, git_ignore.clone()));
if git_ignore.matches_all_files_in(&subdir.to_internal_string()) {
// If the whole directory is ignored, skip it unless we're already tracking
// some file in it. TODO: This is pretty ugly... Also, we should
// optimize it to check exactly the already-tracked files (we know that
// we won't have to consider new files in the directory).
let first_file_in_dir = dir.join(&FileRepoPathComponent::from("\0"));
if let Some((maybe_subdir_file, _)) = self
.file_states
.range((Bound::Included(&first_file_in_dir), Bound::Unbounded))
.next()
{
if !maybe_subdir_file
.dir()
.components()
.starts_with(dir.components())
{
continue;
}
}
}
let disk_subdir = disk_dir.join(file_name);
work.push((subdir, disk_subdir, git_ignore.clone()));
} else {
let file = dir.join(&FileRepoPathComponent::from(name));
let disk_file = disk_dir.join(file_name);

View file

@ -386,3 +386,43 @@ fn test_gitignores_checkout_overwrites_ignored(use_git: bool) {
let (_repo, new_commit) = repo.working_copy_locked().commit(&settings, repo.clone());
assert!(new_commit.tree().entry("modified").is_some());
}
#[test_case(false ; "local store")]
#[test_case(true ; "git store")]
fn test_gitignores_ignored_directory_already_tracked(use_git: bool) {
// Tests that a .gitignore'd directory that already has a tracked file in it
// does not get removed when committing the working directory.
let _home_dir = testutils::new_user_home();
let settings = testutils::user_settings();
let (_temp_dir, repo) = testutils::init_repo(&settings, use_git);
// Add a .gitignore file saying to ignore the directory "ignored/"
let gitignore_path = FileRepoPath::from(".gitignore");
testutils::write_working_copy_file(&repo, &gitignore_path, "/ignored/\n");
let file_path = FileRepoPath::from("ignored/file");
// Create a commit that adds a file in the ignored directory
let mut tx = repo.start_transaction("test");
let mut tree_builder = repo
.store()
.tree_builder(repo.store().empty_tree_id().clone());
testutils::write_normal_file(&mut tree_builder, &file_path, "contents");
let tree_id = tree_builder.write_tree();
let commit = CommitBuilder::for_new_commit(&settings, repo.store(), tree_id)
.set_open(true)
.set_description("add ignored file".to_string())
.write_to_repo(tx.mut_repo());
let repo = tx.commit();
// Check out the commit with the file in ignored/
repo.working_copy_locked().check_out(commit).unwrap();
// Check that the file is still in the commit created by committing the working
// copy (that it didn't get removed because the directory is ignored)
let (_repo, new_commit) = repo.working_copy_locked().commit(&settings, repo.clone());
assert!(new_commit
.tree()
.path_value(&file_path.to_repo_path())
.is_some());
}