2020-12-12 11:00:42 +03:00
|
|
|
// Copyright 2020 Google LLC
|
|
|
|
//
|
|
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
// you may not use this file except in compliance with the License.
|
|
|
|
// You may obtain a copy of the License at
|
|
|
|
//
|
|
|
|
// https://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
//
|
|
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
// See the License for the specific language governing permissions and
|
|
|
|
// limitations under the License.
|
|
|
|
|
2021-05-15 09:22:42 +03:00
|
|
|
use std::fs::{File, OpenOptions};
|
|
|
|
use std::io::{Read, Write};
|
2021-03-12 04:36:55 +03:00
|
|
|
#[cfg(unix)]
|
2020-12-12 11:00:42 +03:00
|
|
|
use std::os::unix::fs::PermissionsExt;
|
2021-04-11 19:13:00 +03:00
|
|
|
use std::sync::Arc;
|
2020-12-12 11:00:42 +03:00
|
|
|
|
2021-06-09 23:57:48 +03:00
|
|
|
use itertools::Itertools;
|
2021-10-29 06:55:36 +03:00
|
|
|
use jujutsu_lib::backend::{Conflict, ConflictPart, TreeValue};
|
2021-05-15 19:16:31 +03:00
|
|
|
use jujutsu_lib::commit_builder::CommitBuilder;
|
|
|
|
use jujutsu_lib::repo::ReadonlyRepo;
|
2021-06-06 08:50:14 +03:00
|
|
|
use jujutsu_lib::repo_path::{RepoPath, RepoPathComponent};
|
2021-05-15 19:16:31 +03:00
|
|
|
use jujutsu_lib::settings::UserSettings;
|
|
|
|
use jujutsu_lib::testutils;
|
|
|
|
use jujutsu_lib::tree_builder::TreeBuilder;
|
2020-12-12 11:00:42 +03:00
|
|
|
use test_case::test_case;
|
|
|
|
|
2021-09-12 09:52:38 +03:00
|
|
|
#[test_case(false ; "local backend")]
|
|
|
|
#[test_case(true ; "git backend")]
|
2020-12-12 11:00:42 +03:00
|
|
|
fn test_root(use_git: bool) {
|
2021-08-11 21:18:37 +03:00
|
|
|
// Test that the working copy is clean and empty after init.
|
2020-12-12 11:00:42 +03:00
|
|
|
let settings = testutils::user_settings();
|
2021-11-22 01:39:34 +03:00
|
|
|
let mut test_workspace = testutils::init_repo(&settings, use_git);
|
2021-11-21 10:46:54 +03:00
|
|
|
let repo = &test_workspace.repo;
|
2020-12-12 11:00:42 +03:00
|
|
|
|
2021-11-22 01:39:34 +03:00
|
|
|
let wc = test_workspace.workspace.working_copy_mut();
|
2022-01-16 20:48:30 +03:00
|
|
|
let mut locked_wc = wc.start_mutation();
|
|
|
|
let new_tree_id = locked_wc.write_tree();
|
2021-08-11 21:18:37 +03:00
|
|
|
locked_wc.discard();
|
|
|
|
let checkout_commit = repo.store().get_commit(repo.view().checkout()).unwrap();
|
|
|
|
assert_eq!(&new_tree_id, checkout_commit.tree().id());
|
|
|
|
assert_eq!(&new_tree_id, repo.store().empty_tree_id());
|
2020-12-12 11:00:42 +03:00
|
|
|
}
|
|
|
|
|
2021-09-12 09:52:38 +03:00
|
|
|
#[test_case(false ; "local backend")]
|
|
|
|
#[test_case(true ; "git backend")]
|
2020-12-12 11:00:42 +03:00
|
|
|
fn test_checkout_file_transitions(use_git: bool) {
|
|
|
|
// Tests switching between commits where a certain path is of one type in one
|
|
|
|
// commit and another type in the other. Includes a "missing" type, so we cover
|
|
|
|
// additions and removals as well.
|
|
|
|
|
|
|
|
let settings = testutils::user_settings();
|
2021-11-22 01:39:34 +03:00
|
|
|
let mut test_workspace = testutils::init_repo(&settings, use_git);
|
2021-11-21 10:46:54 +03:00
|
|
|
let repo = &test_workspace.repo;
|
2020-12-12 11:00:42 +03:00
|
|
|
let store = repo.store().clone();
|
2021-11-24 09:45:41 +03:00
|
|
|
let workspace_root = test_workspace.workspace.workspace_root().clone();
|
2020-12-12 11:00:42 +03:00
|
|
|
|
|
|
|
#[derive(Debug, Clone, Copy)]
|
|
|
|
enum Kind {
|
|
|
|
Missing,
|
|
|
|
Normal,
|
|
|
|
Executable,
|
2021-10-29 06:55:36 +03:00
|
|
|
Conflict,
|
2021-06-14 07:38:34 +03:00
|
|
|
#[cfg_attr(windows, allow(dead_code))]
|
2020-12-12 11:00:42 +03:00
|
|
|
Symlink,
|
|
|
|
Tree,
|
|
|
|
GitSubmodule,
|
2021-02-27 10:00:46 +03:00
|
|
|
}
|
2020-12-12 11:00:42 +03:00
|
|
|
|
|
|
|
fn write_path(
|
|
|
|
settings: &UserSettings,
|
2021-04-11 19:13:00 +03:00
|
|
|
repo: &Arc<ReadonlyRepo>,
|
2020-12-12 11:00:42 +03:00
|
|
|
tree_builder: &mut TreeBuilder,
|
|
|
|
kind: Kind,
|
|
|
|
path: &str,
|
|
|
|
) {
|
|
|
|
let store = repo.store();
|
|
|
|
let value = match kind {
|
|
|
|
Kind::Missing => {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
Kind::Normal => {
|
2021-05-17 08:47:31 +03:00
|
|
|
let id = testutils::write_file(
|
|
|
|
store,
|
|
|
|
&RepoPath::from_internal_string(path),
|
|
|
|
"normal file contents",
|
|
|
|
);
|
2020-12-12 11:00:42 +03:00
|
|
|
TreeValue::Normal {
|
|
|
|
id,
|
|
|
|
executable: false,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Kind::Executable => {
|
2021-05-17 08:47:31 +03:00
|
|
|
let id = testutils::write_file(
|
|
|
|
store,
|
|
|
|
&RepoPath::from_internal_string(path),
|
|
|
|
"executable file contents",
|
|
|
|
);
|
2020-12-12 11:00:42 +03:00
|
|
|
TreeValue::Normal {
|
|
|
|
id,
|
|
|
|
executable: true,
|
|
|
|
}
|
|
|
|
}
|
2021-10-29 06:55:36 +03:00
|
|
|
Kind::Conflict => {
|
|
|
|
let base_file_id = testutils::write_file(
|
|
|
|
store,
|
|
|
|
&RepoPath::from_internal_string(path),
|
|
|
|
"base file contents",
|
|
|
|
);
|
|
|
|
let left_file_id = testutils::write_file(
|
|
|
|
store,
|
|
|
|
&RepoPath::from_internal_string(path),
|
|
|
|
"left file contents",
|
|
|
|
);
|
|
|
|
let right_file_id = testutils::write_file(
|
|
|
|
store,
|
|
|
|
&RepoPath::from_internal_string(path),
|
|
|
|
"right file contents",
|
|
|
|
);
|
|
|
|
let conflict = Conflict {
|
|
|
|
removes: vec![ConflictPart {
|
|
|
|
value: TreeValue::Normal {
|
|
|
|
id: base_file_id,
|
|
|
|
executable: false,
|
|
|
|
},
|
|
|
|
}],
|
|
|
|
adds: vec![
|
|
|
|
ConflictPart {
|
|
|
|
value: TreeValue::Normal {
|
|
|
|
id: left_file_id,
|
|
|
|
executable: false,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
ConflictPart {
|
|
|
|
value: TreeValue::Normal {
|
|
|
|
id: right_file_id,
|
|
|
|
executable: false,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
],
|
|
|
|
};
|
|
|
|
let conflict_id = store.write_conflict(&conflict).unwrap();
|
|
|
|
TreeValue::Conflict(conflict_id)
|
|
|
|
}
|
2020-12-12 11:00:42 +03:00
|
|
|
Kind::Symlink => {
|
|
|
|
let id = store
|
2021-05-17 08:47:31 +03:00
|
|
|
.write_symlink(&RepoPath::from_internal_string(path), "target")
|
2020-12-12 11:00:42 +03:00
|
|
|
.unwrap();
|
|
|
|
TreeValue::Symlink(id)
|
|
|
|
}
|
|
|
|
Kind::Tree => {
|
|
|
|
let mut sub_tree_builder = store.tree_builder(store.empty_tree_id().clone());
|
|
|
|
let file_path = path.to_owned() + "/file";
|
|
|
|
write_path(
|
|
|
|
settings,
|
|
|
|
repo,
|
|
|
|
&mut sub_tree_builder,
|
|
|
|
Kind::Normal,
|
|
|
|
&file_path,
|
|
|
|
);
|
|
|
|
let id = sub_tree_builder.write_tree();
|
|
|
|
TreeValue::Tree(id)
|
|
|
|
}
|
|
|
|
Kind::GitSubmodule => {
|
2021-09-11 19:45:06 +03:00
|
|
|
let mut tx = repo.start_transaction("test");
|
2021-06-14 10:18:38 +03:00
|
|
|
let id = testutils::create_random_commit(settings, repo)
|
2021-09-11 19:45:06 +03:00
|
|
|
.write_to_repo(tx.mut_repo())
|
2020-12-12 11:00:42 +03:00
|
|
|
.id()
|
|
|
|
.clone();
|
2021-09-11 19:45:06 +03:00
|
|
|
tx.commit();
|
2020-12-12 11:00:42 +03:00
|
|
|
TreeValue::GitSubmodule(id)
|
|
|
|
}
|
|
|
|
};
|
2021-05-17 08:47:31 +03:00
|
|
|
tree_builder.set(RepoPath::from_internal_string(path), value);
|
2021-02-27 10:00:46 +03:00
|
|
|
}
|
2020-12-12 11:00:42 +03:00
|
|
|
|
2021-10-29 06:55:36 +03:00
|
|
|
let mut kinds = vec![
|
|
|
|
Kind::Missing,
|
|
|
|
Kind::Normal,
|
|
|
|
Kind::Executable,
|
|
|
|
Kind::Conflict,
|
|
|
|
Kind::Tree,
|
|
|
|
];
|
2021-06-14 07:38:34 +03:00
|
|
|
#[cfg(unix)]
|
|
|
|
kinds.push(Kind::Symlink);
|
2020-12-12 11:00:42 +03:00
|
|
|
if use_git {
|
|
|
|
kinds.push(Kind::GitSubmodule);
|
|
|
|
}
|
|
|
|
let mut left_tree_builder = store.tree_builder(store.empty_tree_id().clone());
|
|
|
|
let mut right_tree_builder = store.tree_builder(store.empty_tree_id().clone());
|
|
|
|
let mut files = vec![];
|
|
|
|
for left_kind in &kinds {
|
|
|
|
for right_kind in &kinds {
|
|
|
|
let path = format!("{:?}_{:?}", left_kind, right_kind);
|
2021-11-21 10:46:54 +03:00
|
|
|
write_path(&settings, repo, &mut left_tree_builder, *left_kind, &path);
|
|
|
|
write_path(&settings, repo, &mut right_tree_builder, *right_kind, &path);
|
2020-12-12 11:00:42 +03:00
|
|
|
files.push((*left_kind, *right_kind, path));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
let left_tree_id = left_tree_builder.write_tree();
|
|
|
|
let right_tree_id = right_tree_builder.write_tree();
|
|
|
|
|
2021-09-11 19:45:06 +03:00
|
|
|
let mut tx = repo.start_transaction("test");
|
2020-12-12 11:00:42 +03:00
|
|
|
let left_commit = CommitBuilder::for_new_commit(&settings, repo.store(), left_tree_id)
|
|
|
|
.set_parents(vec![store.root_commit_id().clone()])
|
|
|
|
.set_open(true)
|
2021-09-11 19:45:06 +03:00
|
|
|
.write_to_repo(tx.mut_repo());
|
2020-12-12 11:00:42 +03:00
|
|
|
let right_commit = CommitBuilder::for_new_commit(&settings, repo.store(), right_tree_id)
|
|
|
|
.set_parents(vec![store.root_commit_id().clone()])
|
|
|
|
.set_open(true)
|
2021-09-11 19:45:06 +03:00
|
|
|
.write_to_repo(tx.mut_repo());
|
|
|
|
tx.commit();
|
2020-12-12 11:00:42 +03:00
|
|
|
|
2021-11-22 01:39:34 +03:00
|
|
|
let wc = test_workspace.workspace.working_copy_mut();
|
2022-01-16 04:25:47 +03:00
|
|
|
wc.check_out(None, left_commit).unwrap();
|
|
|
|
wc.check_out(None, right_commit.clone()).unwrap();
|
2020-12-12 11:00:42 +03:00
|
|
|
|
|
|
|
// Check that the working copy is clean.
|
2022-01-16 20:48:30 +03:00
|
|
|
let mut locked_wc = wc.start_mutation();
|
|
|
|
let new_tree_id = locked_wc.write_tree();
|
2021-08-11 21:18:37 +03:00
|
|
|
locked_wc.discard();
|
|
|
|
assert_eq!(&new_tree_id, right_commit.tree().id());
|
2020-12-12 11:00:42 +03:00
|
|
|
|
|
|
|
for (_left_kind, right_kind, path) in &files {
|
2021-11-24 09:45:41 +03:00
|
|
|
let wc_path = workspace_root.join(path);
|
2020-12-12 11:00:42 +03:00
|
|
|
let maybe_metadata = wc_path.symlink_metadata();
|
|
|
|
match right_kind {
|
|
|
|
Kind::Missing => {
|
2021-12-09 11:12:10 +03:00
|
|
|
assert!(maybe_metadata.is_err(), "{:?} should not exist", path);
|
2020-12-12 11:00:42 +03:00
|
|
|
}
|
|
|
|
Kind::Normal => {
|
2021-04-28 18:57:30 +03:00
|
|
|
assert!(maybe_metadata.is_ok(), "{:?} should exist", path);
|
2020-12-12 11:00:42 +03:00
|
|
|
let metadata = maybe_metadata.unwrap();
|
2021-04-28 18:57:30 +03:00
|
|
|
assert!(metadata.is_file(), "{:?} should be a file", path);
|
2021-03-11 09:13:52 +03:00
|
|
|
#[cfg(unix)]
|
2020-12-12 11:00:42 +03:00
|
|
|
assert_eq!(
|
|
|
|
metadata.permissions().mode() & 0o111,
|
|
|
|
0,
|
|
|
|
"{:?} should not be executable",
|
|
|
|
path
|
|
|
|
);
|
|
|
|
}
|
|
|
|
Kind::Executable => {
|
2021-04-28 18:57:30 +03:00
|
|
|
assert!(maybe_metadata.is_ok(), "{:?} should exist", path);
|
2020-12-12 11:00:42 +03:00
|
|
|
let metadata = maybe_metadata.unwrap();
|
2021-04-28 18:57:30 +03:00
|
|
|
assert!(metadata.is_file(), "{:?} should be a file", path);
|
2021-03-11 09:13:52 +03:00
|
|
|
#[cfg(unix)]
|
2020-12-12 11:00:42 +03:00
|
|
|
assert_ne!(
|
|
|
|
metadata.permissions().mode() & 0o111,
|
|
|
|
0,
|
|
|
|
"{:?} should be executable",
|
|
|
|
path
|
|
|
|
);
|
|
|
|
}
|
2021-10-29 06:55:36 +03:00
|
|
|
Kind::Conflict => {
|
|
|
|
assert!(maybe_metadata.is_ok(), "{:?} should exist", path);
|
|
|
|
let metadata = maybe_metadata.unwrap();
|
|
|
|
assert!(metadata.is_file(), "{:?} should be a file", path);
|
|
|
|
#[cfg(unix)]
|
|
|
|
assert_eq!(
|
|
|
|
metadata.permissions().mode() & 0o111,
|
|
|
|
0,
|
|
|
|
"{:?} should not be executable",
|
|
|
|
path
|
|
|
|
);
|
|
|
|
}
|
2020-12-12 11:00:42 +03:00
|
|
|
Kind::Symlink => {
|
2021-04-28 18:57:30 +03:00
|
|
|
assert!(maybe_metadata.is_ok(), "{:?} should exist", path);
|
2020-12-12 11:00:42 +03:00
|
|
|
let metadata = maybe_metadata.unwrap();
|
2021-04-28 18:57:30 +03:00
|
|
|
assert!(
|
2020-12-12 11:00:42 +03:00
|
|
|
metadata.file_type().is_symlink(),
|
|
|
|
"{:?} should be a symlink",
|
|
|
|
path
|
|
|
|
);
|
|
|
|
}
|
|
|
|
Kind::Tree => {
|
2021-04-28 18:57:30 +03:00
|
|
|
assert!(maybe_metadata.is_ok(), "{:?} should exist", path);
|
2020-12-12 11:00:42 +03:00
|
|
|
let metadata = maybe_metadata.unwrap();
|
2021-04-28 18:57:30 +03:00
|
|
|
assert!(metadata.is_dir(), "{:?} should be a directory", path);
|
2020-12-12 11:00:42 +03:00
|
|
|
}
|
|
|
|
Kind::GitSubmodule => {
|
|
|
|
// Not supported for now
|
2021-12-09 11:12:10 +03:00
|
|
|
assert!(maybe_metadata.is_err(), "{:?} should not exist", path);
|
2020-12-12 11:00:42 +03:00
|
|
|
}
|
|
|
|
};
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-01-18 02:20:59 +03:00
|
|
|
#[test]
|
|
|
|
fn test_reset() {
|
|
|
|
let settings = testutils::user_settings();
|
|
|
|
let mut test_workspace = testutils::init_repo(&settings, false);
|
|
|
|
let repo = &test_workspace.repo;
|
|
|
|
let workspace_root = test_workspace.workspace.workspace_root().clone();
|
|
|
|
|
|
|
|
let ignored_path = RepoPath::from_internal_string("ignored");
|
|
|
|
let gitignore_path = RepoPath::from_internal_string(".gitignore");
|
|
|
|
|
|
|
|
let tree_without_file = testutils::create_tree(repo, &[(&gitignore_path, "ignored\n")]);
|
|
|
|
let tree_with_file = testutils::create_tree(
|
|
|
|
repo,
|
|
|
|
&[(&gitignore_path, "ignored\n"), (&ignored_path, "code")],
|
|
|
|
);
|
|
|
|
let mut tx = repo.start_transaction("test");
|
|
|
|
let store = repo.store();
|
|
|
|
let root_commit = store.root_commit_id();
|
|
|
|
let commit_without_file = CommitBuilder::for_open_commit(
|
|
|
|
&settings,
|
|
|
|
store,
|
|
|
|
root_commit.clone(),
|
|
|
|
tree_without_file.id().clone(),
|
|
|
|
)
|
|
|
|
.write_to_repo(tx.mut_repo());
|
|
|
|
let commit_with_file = CommitBuilder::for_open_commit(
|
|
|
|
&settings,
|
|
|
|
store,
|
|
|
|
root_commit.clone(),
|
|
|
|
tree_with_file.id().clone(),
|
|
|
|
)
|
|
|
|
.write_to_repo(tx.mut_repo());
|
|
|
|
test_workspace.repo = tx.commit();
|
|
|
|
|
|
|
|
let wc = test_workspace.workspace.working_copy_mut();
|
2022-01-16 04:25:47 +03:00
|
|
|
wc.check_out(None, commit_with_file.clone()).unwrap();
|
2022-01-18 02:20:59 +03:00
|
|
|
|
|
|
|
// Test the setup: the file should exist on disk and in the tree state.
|
|
|
|
assert!(ignored_path.to_fs_path(&workspace_root).is_file());
|
|
|
|
assert!(wc.file_states().contains_key(&ignored_path));
|
|
|
|
|
|
|
|
// After we reset to the commit without the file, it should still exist on disk,
|
|
|
|
// but it should not be in the tree state, and it should not get added when we
|
|
|
|
// commit the working copy (because it's ignored).
|
|
|
|
let mut locked_wc = wc.start_mutation();
|
|
|
|
locked_wc.reset(&tree_without_file).unwrap();
|
|
|
|
locked_wc.finish(commit_without_file.id().clone());
|
|
|
|
assert!(ignored_path.to_fs_path(&workspace_root).is_file());
|
|
|
|
assert!(!wc.file_states().contains_key(&ignored_path));
|
|
|
|
let mut locked_wc = wc.start_mutation();
|
|
|
|
let new_tree_id = locked_wc.write_tree();
|
|
|
|
assert_eq!(new_tree_id, *tree_without_file.id());
|
|
|
|
locked_wc.discard();
|
|
|
|
|
|
|
|
// After we reset to the commit without the file, it should still exist on disk,
|
|
|
|
// but it should not be in the tree state, and it should not get added when we
|
|
|
|
// commit the working copy (because it's ignored).
|
|
|
|
let mut locked_wc = wc.start_mutation();
|
|
|
|
locked_wc.reset(&tree_without_file).unwrap();
|
|
|
|
locked_wc.finish(commit_without_file.id().clone());
|
|
|
|
assert!(ignored_path.to_fs_path(&workspace_root).is_file());
|
|
|
|
assert!(!wc.file_states().contains_key(&ignored_path));
|
|
|
|
let mut locked_wc = wc.start_mutation();
|
|
|
|
let new_tree_id = locked_wc.write_tree();
|
|
|
|
assert_eq!(new_tree_id, *tree_without_file.id());
|
|
|
|
locked_wc.discard();
|
|
|
|
|
|
|
|
// Now test the opposite direction: resetting to a commit where the file is
|
|
|
|
// tracked. The file should become tracked (even though it's ignored).
|
|
|
|
let mut locked_wc = wc.start_mutation();
|
|
|
|
locked_wc.reset(&tree_with_file).unwrap();
|
|
|
|
locked_wc.finish(commit_with_file.id().clone());
|
|
|
|
assert!(ignored_path.to_fs_path(&workspace_root).is_file());
|
|
|
|
assert!(wc.file_states().contains_key(&ignored_path));
|
|
|
|
let mut locked_wc = wc.start_mutation();
|
|
|
|
let new_tree_id = locked_wc.write_tree();
|
|
|
|
assert_eq!(new_tree_id, *tree_with_file.id());
|
|
|
|
locked_wc.discard();
|
|
|
|
}
|
|
|
|
|
2021-09-12 09:52:38 +03:00
|
|
|
#[test_case(false ; "local backend")]
|
|
|
|
#[test_case(true ; "git backend")]
|
2020-12-12 11:00:42 +03:00
|
|
|
fn test_commit_racy_timestamps(use_git: bool) {
|
|
|
|
// Tests that file modifications are detected even if they happen the same
|
|
|
|
// millisecond as the updated working copy state.
|
2021-03-17 03:09:33 +03:00
|
|
|
let _home_dir = testutils::new_user_home();
|
2020-12-12 11:00:42 +03:00
|
|
|
let settings = testutils::user_settings();
|
2021-11-22 01:39:34 +03:00
|
|
|
let mut test_workspace = testutils::init_repo(&settings, use_git);
|
2021-11-21 10:46:54 +03:00
|
|
|
let repo = &test_workspace.repo;
|
2021-11-24 09:45:41 +03:00
|
|
|
let workspace_root = test_workspace.workspace.workspace_root().clone();
|
2020-12-12 11:00:42 +03:00
|
|
|
|
2021-11-24 09:45:41 +03:00
|
|
|
let file_path = workspace_root.join("file");
|
2020-12-12 11:00:42 +03:00
|
|
|
let mut previous_tree_id = repo.store().empty_tree_id().clone();
|
2021-11-22 01:39:34 +03:00
|
|
|
let wc = test_workspace.workspace.working_copy_mut();
|
2020-12-12 11:00:42 +03:00
|
|
|
for i in 0..100 {
|
|
|
|
{
|
|
|
|
let mut file = OpenOptions::new()
|
|
|
|
.create(true)
|
|
|
|
.write(true)
|
|
|
|
.open(&file_path)
|
|
|
|
.unwrap();
|
|
|
|
file.write_all(format!("contents {}", i).as_bytes())
|
|
|
|
.unwrap();
|
|
|
|
}
|
2022-01-16 20:48:30 +03:00
|
|
|
let mut locked_wc = wc.start_mutation();
|
|
|
|
let new_tree_id = locked_wc.write_tree();
|
2021-08-11 21:18:37 +03:00
|
|
|
locked_wc.discard();
|
2020-12-12 11:00:42 +03:00
|
|
|
assert_ne!(new_tree_id, previous_tree_id);
|
|
|
|
previous_tree_id = new_tree_id;
|
|
|
|
}
|
|
|
|
}
|
2020-12-19 09:50:01 +03:00
|
|
|
|
2021-09-12 09:52:38 +03:00
|
|
|
#[test_case(false ; "local backend")]
|
|
|
|
#[test_case(true ; "git backend")]
|
2020-12-19 09:50:01 +03:00
|
|
|
fn test_gitignores(use_git: bool) {
|
|
|
|
// Tests that .gitignore files are respected.
|
|
|
|
|
2021-03-17 03:09:33 +03:00
|
|
|
let _home_dir = testutils::new_user_home();
|
2020-12-19 09:50:01 +03:00
|
|
|
let settings = testutils::user_settings();
|
2021-11-22 01:39:34 +03:00
|
|
|
let mut test_workspace = testutils::init_repo(&settings, use_git);
|
2021-11-21 10:46:54 +03:00
|
|
|
let repo = &test_workspace.repo;
|
2021-11-22 01:39:34 +03:00
|
|
|
let workspace_root = test_workspace.workspace.workspace_root().clone();
|
2020-12-19 09:50:01 +03:00
|
|
|
|
2021-05-17 08:47:31 +03:00
|
|
|
let gitignore_path = RepoPath::from_internal_string(".gitignore");
|
|
|
|
let added_path = RepoPath::from_internal_string("added");
|
|
|
|
let modified_path = RepoPath::from_internal_string("modified");
|
|
|
|
let removed_path = RepoPath::from_internal_string("removed");
|
|
|
|
let ignored_path = RepoPath::from_internal_string("ignored");
|
|
|
|
let subdir_modified_path = RepoPath::from_internal_string("dir/modified");
|
|
|
|
let subdir_ignored_path = RepoPath::from_internal_string("dir/ignored");
|
2020-12-19 09:50:01 +03:00
|
|
|
|
2021-11-22 01:39:34 +03:00
|
|
|
testutils::write_working_copy_file(&workspace_root, &gitignore_path, "ignored\n");
|
|
|
|
testutils::write_working_copy_file(&workspace_root, &modified_path, "1");
|
|
|
|
testutils::write_working_copy_file(&workspace_root, &removed_path, "1");
|
2021-11-22 01:31:44 +03:00
|
|
|
std::fs::create_dir(workspace_root.join("dir")).unwrap();
|
2021-11-22 01:39:34 +03:00
|
|
|
testutils::write_working_copy_file(&workspace_root, &subdir_modified_path, "1");
|
2020-12-19 09:50:01 +03:00
|
|
|
|
2021-11-22 01:39:34 +03:00
|
|
|
let wc = test_workspace.workspace.working_copy_mut();
|
2022-01-16 20:48:30 +03:00
|
|
|
let mut locked_wc = wc.start_mutation();
|
|
|
|
let new_tree_id1 = locked_wc.write_tree();
|
2021-08-11 21:18:37 +03:00
|
|
|
locked_wc.discard();
|
|
|
|
let tree1 = repo
|
|
|
|
.store()
|
|
|
|
.get_tree(&RepoPath::root(), &new_tree_id1)
|
|
|
|
.unwrap();
|
|
|
|
let files1 = tree1.entries().map(|(name, _value)| name).collect_vec();
|
2020-12-19 09:50:01 +03:00
|
|
|
assert_eq!(
|
|
|
|
files1,
|
|
|
|
vec![
|
2021-05-17 07:55:51 +03:00
|
|
|
gitignore_path.clone(),
|
|
|
|
subdir_modified_path.clone(),
|
|
|
|
modified_path.clone(),
|
|
|
|
removed_path.clone(),
|
2020-12-19 09:50:01 +03:00
|
|
|
]
|
|
|
|
);
|
|
|
|
|
2021-11-22 01:31:44 +03:00
|
|
|
testutils::write_working_copy_file(
|
2021-11-22 01:39:34 +03:00
|
|
|
&workspace_root,
|
2021-11-22 01:31:44 +03:00
|
|
|
&gitignore_path,
|
|
|
|
"ignored\nmodified\nremoved\n",
|
|
|
|
);
|
2021-11-22 01:39:34 +03:00
|
|
|
testutils::write_working_copy_file(&workspace_root, &added_path, "2");
|
|
|
|
testutils::write_working_copy_file(&workspace_root, &modified_path, "2");
|
|
|
|
std::fs::remove_file(removed_path.to_fs_path(&workspace_root)).unwrap();
|
|
|
|
testutils::write_working_copy_file(&workspace_root, &ignored_path, "2");
|
|
|
|
testutils::write_working_copy_file(&workspace_root, &subdir_modified_path, "2");
|
|
|
|
testutils::write_working_copy_file(&workspace_root, &subdir_ignored_path, "2");
|
2020-12-19 09:50:01 +03:00
|
|
|
|
2022-01-16 20:48:30 +03:00
|
|
|
let mut locked_wc = wc.start_mutation();
|
|
|
|
let new_tree_id2 = locked_wc.write_tree();
|
2021-08-11 21:18:37 +03:00
|
|
|
locked_wc.discard();
|
|
|
|
let tree2 = repo
|
|
|
|
.store()
|
|
|
|
.get_tree(&RepoPath::root(), &new_tree_id2)
|
|
|
|
.unwrap();
|
|
|
|
let files2 = tree2.entries().map(|(name, _value)| name).collect_vec();
|
2020-12-19 09:50:01 +03:00
|
|
|
assert_eq!(
|
|
|
|
files2,
|
|
|
|
vec![
|
2021-06-14 10:18:38 +03:00
|
|
|
gitignore_path,
|
|
|
|
added_path,
|
|
|
|
subdir_modified_path,
|
|
|
|
modified_path,
|
2020-12-19 09:50:01 +03:00
|
|
|
]
|
|
|
|
);
|
|
|
|
}
|
2021-05-15 09:22:42 +03:00
|
|
|
|
2021-09-12 09:52:38 +03:00
|
|
|
#[test_case(false ; "local backend")]
|
|
|
|
#[test_case(true ; "git backend")]
|
2021-05-15 09:22:42 +03:00
|
|
|
fn test_gitignores_checkout_overwrites_ignored(use_git: bool) {
|
|
|
|
// Tests that a .gitignore'd file gets overwritten if check out a commit where
|
|
|
|
// the file is tracked.
|
|
|
|
|
|
|
|
let _home_dir = testutils::new_user_home();
|
|
|
|
let settings = testutils::user_settings();
|
2021-11-22 01:39:34 +03:00
|
|
|
let mut test_workspace = testutils::init_repo(&settings, use_git);
|
2021-11-21 10:46:54 +03:00
|
|
|
let repo = &test_workspace.repo;
|
2021-11-24 09:45:41 +03:00
|
|
|
let workspace_root = test_workspace.workspace.workspace_root().clone();
|
2021-05-15 09:22:42 +03:00
|
|
|
|
|
|
|
// Write an ignored file called "modified" to disk
|
2021-05-17 08:47:31 +03:00
|
|
|
let gitignore_path = RepoPath::from_internal_string(".gitignore");
|
2021-11-24 09:45:41 +03:00
|
|
|
testutils::write_working_copy_file(&workspace_root, &gitignore_path, "modified\n");
|
2021-05-17 08:47:31 +03:00
|
|
|
let modified_path = RepoPath::from_internal_string("modified");
|
2021-11-24 09:45:41 +03:00
|
|
|
testutils::write_working_copy_file(&workspace_root, &modified_path, "garbage");
|
2021-05-15 09:22:42 +03:00
|
|
|
|
|
|
|
// Create a commit that adds the same file but with different contents
|
|
|
|
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, &modified_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 file".to_string())
|
|
|
|
.write_to_repo(tx.mut_repo());
|
|
|
|
let repo = tx.commit();
|
|
|
|
|
|
|
|
// Now check out the commit that adds the file "modified" with contents
|
|
|
|
// "contents". The exiting contents ("garbage") should be replaced in the
|
|
|
|
// working copy.
|
2021-11-22 01:39:34 +03:00
|
|
|
let wc = test_workspace.workspace.working_copy_mut();
|
2022-01-16 04:25:47 +03:00
|
|
|
wc.check_out(None, commit).unwrap();
|
2021-05-15 09:22:42 +03:00
|
|
|
|
|
|
|
// Check that the new contents are in the working copy
|
2021-11-24 09:45:41 +03:00
|
|
|
let path = workspace_root.join("modified");
|
2021-05-15 09:22:42 +03:00
|
|
|
assert!(path.is_file());
|
|
|
|
let mut file = File::open(path).unwrap();
|
|
|
|
let mut buf = Vec::new();
|
|
|
|
file.read_to_end(&mut buf).unwrap();
|
|
|
|
assert_eq!(buf, b"contents");
|
|
|
|
|
2021-08-11 21:18:37 +03:00
|
|
|
// Check that the file is in the tree created by committing the working copy
|
2022-01-16 20:48:30 +03:00
|
|
|
let mut locked_wc = wc.start_mutation();
|
|
|
|
let new_tree_id = locked_wc.write_tree();
|
2021-08-11 21:18:37 +03:00
|
|
|
locked_wc.discard();
|
|
|
|
let new_tree = repo
|
|
|
|
.store()
|
|
|
|
.get_tree(&RepoPath::root(), &new_tree_id)
|
|
|
|
.unwrap();
|
|
|
|
assert!(new_tree
|
2021-06-06 08:50:14 +03:00
|
|
|
.entry(&RepoPathComponent::from("modified"))
|
|
|
|
.is_some());
|
2021-05-15 09:22:42 +03:00
|
|
|
}
|
2021-05-15 18:27:28 +03:00
|
|
|
|
2021-09-12 09:52:38 +03:00
|
|
|
#[test_case(false ; "local backend")]
|
|
|
|
#[test_case(true ; "git backend")]
|
2021-05-15 18:27:28 +03:00
|
|
|
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();
|
2021-11-22 01:39:34 +03:00
|
|
|
let mut test_workspace = testutils::init_repo(&settings, use_git);
|
2021-11-21 10:46:54 +03:00
|
|
|
let repo = &test_workspace.repo;
|
2021-05-15 18:27:28 +03:00
|
|
|
|
|
|
|
// Add a .gitignore file saying to ignore the directory "ignored/"
|
2021-05-17 08:47:31 +03:00
|
|
|
let gitignore_path = RepoPath::from_internal_string(".gitignore");
|
2021-11-22 01:31:44 +03:00
|
|
|
testutils::write_working_copy_file(
|
|
|
|
test_workspace.workspace.workspace_root(),
|
|
|
|
&gitignore_path,
|
|
|
|
"/ignored/\n",
|
|
|
|
);
|
2021-05-17 08:47:31 +03:00
|
|
|
let file_path = RepoPath::from_internal_string("ignored/file");
|
2021-05-15 18:27:28 +03:00
|
|
|
|
|
|
|
// 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/
|
2021-11-22 01:39:34 +03:00
|
|
|
let wc = test_workspace.workspace.working_copy_mut();
|
2022-01-16 04:25:47 +03:00
|
|
|
wc.check_out(None, commit).unwrap();
|
2021-05-15 18:27:28 +03:00
|
|
|
|
2021-08-11 21:18:37 +03:00
|
|
|
// Check that the file is still in the tree created by committing the working
|
2021-05-15 18:27:28 +03:00
|
|
|
// copy (that it didn't get removed because the directory is ignored)
|
2022-01-16 20:48:30 +03:00
|
|
|
let mut locked_wc = wc.start_mutation();
|
|
|
|
let new_tree_id = locked_wc.write_tree();
|
2021-08-11 21:18:37 +03:00
|
|
|
locked_wc.discard();
|
|
|
|
let new_tree = repo
|
|
|
|
.store()
|
|
|
|
.get_tree(&RepoPath::root(), &new_tree_id)
|
|
|
|
.unwrap();
|
|
|
|
assert!(new_tree.path_value(&file_path).is_some());
|
2021-05-15 18:27:28 +03:00
|
|
|
}
|
2021-08-31 19:38:28 +03:00
|
|
|
|
2021-09-12 09:52:38 +03:00
|
|
|
#[test_case(false ; "local backend")]
|
|
|
|
#[test_case(true ; "git backend")]
|
2021-08-31 19:38:28 +03:00
|
|
|
fn test_dotgit_ignored(use_git: bool) {
|
|
|
|
// Tests that .git directories and files are always ignored (we could accept
|
|
|
|
// them if the backend is not git).
|
|
|
|
|
|
|
|
let _home_dir = testutils::new_user_home();
|
|
|
|
let settings = testutils::user_settings();
|
2021-11-22 01:39:34 +03:00
|
|
|
let mut test_workspace = testutils::init_repo(&settings, use_git);
|
2021-11-21 10:46:54 +03:00
|
|
|
let repo = &test_workspace.repo;
|
2021-11-22 01:39:34 +03:00
|
|
|
let workspace_root = test_workspace.workspace.workspace_root().clone();
|
2021-08-31 19:38:28 +03:00
|
|
|
|
|
|
|
// Test with a .git/ directory (with a file in, since we don't write empty
|
|
|
|
// trees)
|
2021-11-24 09:45:41 +03:00
|
|
|
let dotgit_path = workspace_root.join(".git");
|
2021-08-31 19:38:28 +03:00
|
|
|
std::fs::create_dir(&dotgit_path).unwrap();
|
|
|
|
testutils::write_working_copy_file(
|
2021-11-22 01:39:34 +03:00
|
|
|
&workspace_root,
|
2021-08-31 19:38:28 +03:00
|
|
|
&RepoPath::from_internal_string(".git/file"),
|
|
|
|
"contents",
|
|
|
|
);
|
2022-01-16 20:48:30 +03:00
|
|
|
let mut locked_wc = test_workspace.workspace.working_copy_mut().start_mutation();
|
|
|
|
let new_tree_id = locked_wc.write_tree();
|
2021-08-31 19:38:28 +03:00
|
|
|
assert_eq!(new_tree_id, *repo.store().empty_tree_id());
|
2022-01-16 20:48:30 +03:00
|
|
|
locked_wc.discard();
|
2021-08-31 19:38:28 +03:00
|
|
|
std::fs::remove_dir_all(&dotgit_path).unwrap();
|
|
|
|
|
|
|
|
// Test with a .git file
|
2021-11-22 01:31:44 +03:00
|
|
|
testutils::write_working_copy_file(
|
2021-11-22 01:39:34 +03:00
|
|
|
&workspace_root,
|
2021-11-22 01:31:44 +03:00
|
|
|
&RepoPath::from_internal_string(".git"),
|
|
|
|
"contents",
|
|
|
|
);
|
2022-01-16 20:48:30 +03:00
|
|
|
let mut locked_wc = test_workspace.workspace.working_copy_mut().start_mutation();
|
|
|
|
let new_tree_id = locked_wc.write_tree();
|
2021-08-31 19:38:28 +03:00
|
|
|
assert_eq!(new_tree_id, *repo.store().empty_tree_id());
|
2022-01-16 20:48:30 +03:00
|
|
|
locked_wc.discard();
|
2021-08-31 19:38:28 +03:00
|
|
|
}
|