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

view: make sure we don't leave a dangling git ref

All commits in the view are supposed to be reachable from its
heads. If a head is removed and there are git refs pointing to
ancestors of it (or to the removed head itself), we should make that
ancestor a head.
This commit is contained in:
Martin von Zweigbergk 2021-01-15 16:27:16 -08:00
parent 1f593a4193
commit f43880381f
2 changed files with 42 additions and 0 deletions

View file

@ -66,6 +66,7 @@ fn enforce_invariants(store: &StoreWrapper, view: &mut op_store::View) {
// TODO: This is surely terribly slow on large repos, at least in its current
// form. We should make it faster (using the index) and avoid calling it in
// most cases (avoid adding a head that's already reachable in the view).
view.head_ids.extend(view.git_refs.values().cloned());
view.head_ids = heads_of_set(store, view.head_ids.iter().cloned());
}
@ -434,6 +435,8 @@ impl MutableView {
pub fn remove_head(&mut self, head: &Commit) {
self.data.head_ids.remove(head.id());
// To potentially add back heads based on git refs
enforce_invariants(&self.store, &mut self.data);
}
pub fn insert_git_ref(&mut self, name: String, commit_id: CommitId) {

View file

@ -399,3 +399,42 @@ fn test_remove_head(use_git: bool) {
assert!(!heads.contains(commit2.id()));
assert!(!heads.contains(commit1.id()));
}
#[test_case(false ; "local store")]
// #[test_case(true ; "git store")]
fn test_remove_head_ancestor_git_ref(use_git: bool) {
// Test that Transaction::remove_head() does not leave the view with a git ref
// pointing to a commit that's not reachable by any head.
let settings = testutils::user_settings();
let (_temp_dir, mut repo) = testutils::init_repo(&settings, use_git);
let store = repo.store();
let mut tx = repo.start_transaction("test");
let commit1 = CommitBuilder::for_new_commit(&settings, store, store.empty_tree_id().clone())
.write_to_transaction(&mut tx);
let commit2 = CommitBuilder::for_new_commit(&settings, store, store.empty_tree_id().clone())
.set_parents(vec![commit1.id().clone()])
.write_to_transaction(&mut tx);
let commit3 = CommitBuilder::for_new_commit(&settings, store, store.empty_tree_id().clone())
.set_parents(vec![commit2.id().clone()])
.write_to_transaction(&mut tx);
tx.insert_git_ref("refs/heads/main".to_string(), commit1.id().clone());
tx.commit();
Arc::get_mut(&mut repo).unwrap().reload();
let mut tx = repo.start_transaction("test");
let heads: HashSet<_> = tx.as_repo().view().heads().cloned().collect();
assert!(heads.contains(commit3.id()));
tx.remove_head(&commit3);
let heads: HashSet<_> = tx.as_repo().view().heads().cloned().collect();
assert!(!heads.contains(commit3.id()));
assert!(!heads.contains(commit2.id()));
assert!(heads.contains(commit1.id()));
tx.commit();
Arc::get_mut(&mut repo).unwrap().reload();
let heads: HashSet<_> = repo.view().heads().cloned().collect();
assert!(!heads.contains(commit3.id()));
assert!(!heads.contains(commit2.id()));
assert!(heads.contains(commit1.id()));
}