mirror of
https://github.com/martinvonz/jj.git
synced 2024-12-26 14:00:51 +00:00
9f4a7318c7
This addresses the test instability. The underlying problem still exists, but it's unlikely to trigger user-facing issues because of that. A repo instance won't be reused after gc() call. Fixes #3537
193 lines
6.3 KiB
Rust
193 lines
6.3 KiB
Rust
// Copyright 2024 The Jujutsu Authors
|
|
//
|
|
// 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.
|
|
|
|
use std::collections::HashSet;
|
|
use std::path::Path;
|
|
use std::process::Command;
|
|
use std::sync::Arc;
|
|
use std::time::{Duration, SystemTime};
|
|
|
|
use jj_lib::backend::CommitId;
|
|
use jj_lib::git_backend::GitBackend;
|
|
use jj_lib::repo::{ReadonlyRepo, Repo};
|
|
use maplit::hashset;
|
|
use testutils::{create_random_commit, CommitGraphBuilder, TestRepo, TestRepoBackend};
|
|
|
|
fn get_git_backend(repo: &Arc<ReadonlyRepo>) -> &GitBackend {
|
|
repo.store()
|
|
.backend_impl()
|
|
.downcast_ref::<GitBackend>()
|
|
.unwrap()
|
|
}
|
|
|
|
fn collect_no_gc_refs(git_repo_path: &Path) -> HashSet<CommitId> {
|
|
// Load fresh git repo to isolate from false caching issue. Here we want to
|
|
// ensure that the underlying data is correct. We could test the in-memory
|
|
// data as well, but we don't have any special handling in our code.
|
|
let git_repo = gix::open(git_repo_path).unwrap();
|
|
let git_refs = git_repo.references().unwrap();
|
|
let no_gc_refs_iter = git_refs.prefixed("refs/jj/keep/").unwrap();
|
|
no_gc_refs_iter
|
|
.map(|git_ref| CommitId::from_bytes(git_ref.unwrap().id().as_bytes()))
|
|
.collect()
|
|
}
|
|
|
|
#[test]
|
|
fn test_gc() {
|
|
// TODO: Better way to disable the test if git command couldn't be executed
|
|
if Command::new("git").arg("--version").status().is_err() {
|
|
eprintln!("Skipping because git command might fail to run");
|
|
return;
|
|
}
|
|
|
|
let settings = testutils::user_settings();
|
|
let test_repo = TestRepo::init_with_backend(TestRepoBackend::Git);
|
|
let repo = test_repo.repo;
|
|
let git_repo_path = get_git_backend(&repo).git_repo_path();
|
|
let base_index = repo.readonly_index();
|
|
|
|
// Set up commits:
|
|
//
|
|
// H (predecessor: D)
|
|
// G |
|
|
// |\|
|
|
// | F
|
|
// E |
|
|
// D | |
|
|
// C |/
|
|
// |/
|
|
// B
|
|
// A
|
|
let mut tx = repo.start_transaction(&settings);
|
|
let mut graph_builder = CommitGraphBuilder::new(&settings, tx.mut_repo());
|
|
let commit_a = graph_builder.initial_commit();
|
|
let commit_b = graph_builder.commit_with_parents(&[&commit_a]);
|
|
let commit_c = graph_builder.commit_with_parents(&[&commit_b]);
|
|
let commit_d = graph_builder.commit_with_parents(&[&commit_c]);
|
|
let commit_e = graph_builder.commit_with_parents(&[&commit_b]);
|
|
let commit_f = graph_builder.commit_with_parents(&[&commit_b]);
|
|
let commit_g = graph_builder.commit_with_parents(&[&commit_e, &commit_f]);
|
|
let commit_h = create_random_commit(tx.mut_repo(), &settings)
|
|
.set_parents(vec![commit_f.id().clone()])
|
|
.set_predecessors(vec![commit_d.id().clone()])
|
|
.write()
|
|
.unwrap();
|
|
let repo = tx.commit("test");
|
|
assert_eq!(
|
|
*repo.view().heads(),
|
|
hashset! {
|
|
commit_d.id().clone(),
|
|
commit_g.id().clone(),
|
|
commit_h.id().clone(),
|
|
},
|
|
);
|
|
|
|
// At first, all commits have no-gc refs
|
|
assert_eq!(
|
|
collect_no_gc_refs(git_repo_path),
|
|
hashset! {
|
|
commit_a.id().clone(),
|
|
commit_b.id().clone(),
|
|
commit_c.id().clone(),
|
|
commit_d.id().clone(),
|
|
commit_e.id().clone(),
|
|
commit_f.id().clone(),
|
|
commit_g.id().clone(),
|
|
commit_h.id().clone(),
|
|
},
|
|
);
|
|
|
|
// Empty index, but all kept by file modification time
|
|
// (Beware that this invokes "git gc" and refs will be packed.)
|
|
repo.store()
|
|
.gc(base_index.as_index(), SystemTime::UNIX_EPOCH)
|
|
.unwrap();
|
|
assert_eq!(
|
|
collect_no_gc_refs(git_repo_path),
|
|
hashset! {
|
|
commit_a.id().clone(),
|
|
commit_b.id().clone(),
|
|
commit_c.id().clone(),
|
|
commit_d.id().clone(),
|
|
commit_e.id().clone(),
|
|
commit_f.id().clone(),
|
|
commit_g.id().clone(),
|
|
commit_h.id().clone(),
|
|
},
|
|
);
|
|
|
|
// Don't rely on the exact system time because file modification time might
|
|
// have lower precision for example.
|
|
let now = || SystemTime::now() + Duration::from_secs(1);
|
|
|
|
// All reachable: redundant no-gc refs will be removed
|
|
repo.store().gc(repo.index(), now()).unwrap();
|
|
assert_eq!(
|
|
collect_no_gc_refs(git_repo_path),
|
|
hashset! {
|
|
commit_d.id().clone(),
|
|
commit_g.id().clone(),
|
|
commit_h.id().clone(),
|
|
},
|
|
);
|
|
|
|
// G is no longer reachable
|
|
let mut mut_index = base_index.start_modification();
|
|
mut_index.add_commit(&commit_a);
|
|
mut_index.add_commit(&commit_b);
|
|
mut_index.add_commit(&commit_c);
|
|
mut_index.add_commit(&commit_d);
|
|
mut_index.add_commit(&commit_e);
|
|
mut_index.add_commit(&commit_f);
|
|
mut_index.add_commit(&commit_h);
|
|
repo.store().gc(mut_index.as_index(), now()).unwrap();
|
|
assert_eq!(
|
|
collect_no_gc_refs(git_repo_path),
|
|
hashset! {
|
|
commit_d.id().clone(),
|
|
commit_e.id().clone(),
|
|
commit_h.id().clone(),
|
|
},
|
|
);
|
|
|
|
// D|E|H are no longer reachable
|
|
let mut mut_index = base_index.start_modification();
|
|
mut_index.add_commit(&commit_a);
|
|
mut_index.add_commit(&commit_b);
|
|
mut_index.add_commit(&commit_c);
|
|
mut_index.add_commit(&commit_f);
|
|
repo.store().gc(mut_index.as_index(), now()).unwrap();
|
|
assert_eq!(
|
|
collect_no_gc_refs(git_repo_path),
|
|
hashset! {
|
|
commit_c.id().clone(),
|
|
commit_f.id().clone(),
|
|
},
|
|
);
|
|
|
|
// B|C|F are no longer reachable
|
|
let mut mut_index = base_index.start_modification();
|
|
mut_index.add_commit(&commit_a);
|
|
repo.store().gc(mut_index.as_index(), now()).unwrap();
|
|
assert_eq!(
|
|
collect_no_gc_refs(git_repo_path),
|
|
hashset! {
|
|
commit_a.id().clone(),
|
|
},
|
|
);
|
|
|
|
// All unreachable
|
|
repo.store().gc(base_index.as_index(), now()).unwrap();
|
|
assert_eq!(collect_no_gc_refs(git_repo_path), hashset! {});
|
|
}
|