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

index: fix partial reindexing to not lose commits only reachable from one side

Spotted while adding error propagation there. This wouldn't likely be a real
problem because "jj debug reindex" removes all of the operation links.

The "} else {" condition is removed because it doesn't make sense to exclude
only the exact parent_op_id operation. This can be optimized to not walk
ancestors of the parent_op_id operation, but I don't see a motivation to add
tests covering such scenarios. It's pretty rare that an intermediate operation
link is missing.
This commit is contained in:
Yuya Nishihara 2023-12-23 11:22:30 +09:00
parent 320d15412b
commit b954bab0ca
2 changed files with 63 additions and 10 deletions

View file

@ -121,14 +121,16 @@ impl DefaultIndexStore {
|op: &Operation| op.parents().collect_vec(),
) {
let op = op?;
if operations_dir.join(op.id().hex()).is_file() {
if parent_op_id.is_none() {
parent_op_id = Some(op.id().clone())
}
} else {
for head in op.view()?.heads() {
new_heads.insert(head.clone());
}
// Pick the latest existing ancestor operation as the parent
// segment. Perhaps, breadth-first search is more appropriate here,
// but that wouldn't matter in practice as the operation log is
// mostly linear.
if parent_op_id.is_none() && operations_dir.join(op.id().hex()).is_file() {
parent_op_id = Some(op.id().clone());
}
// TODO: no need to walk ancestors of the parent_op_id operation
for head in op.view()?.heads() {
new_heads.insert(head.clone());
}
}
let maybe_parent_file;

View file

@ -12,9 +12,10 @@
// See the License for the specific language governing permissions and
// limitations under the License.
use std::fs;
use std::sync::Arc;
use jj_lib::backend::CommitId;
use jj_lib::backend::{CommitId, ObjectId as _};
use jj_lib::commit::Commit;
use jj_lib::commit_builder::CommitBuilder;
use jj_lib::default_index::{
@ -24,7 +25,8 @@ use jj_lib::index::Index as _;
use jj_lib::repo::{MutableRepo, ReadonlyRepo, Repo};
use jj_lib::settings::UserSettings;
use testutils::{
create_random_commit, load_repo_at_head, write_random_commit, CommitGraphBuilder, TestRepo,
commit_transactions, create_random_commit, load_repo_at_head, write_random_commit,
CommitGraphBuilder, TestRepo,
};
fn child_commit<'repo>(
@ -531,6 +533,55 @@ fn test_index_commits_incremental_squashed() {
assert_eq!(commits_by_level(&repo), vec![71, 20]);
}
#[test]
fn test_reindex_from_merged_operation() {
let settings = testutils::user_settings();
let test_repo = TestRepo::init();
let repo = &test_repo.repo;
// The following operation log:
// x (add head, index will be missing)
// x (add head, index will be missing)
// |\
// o o (remove head)
// o o (add head)
// |/
// o
let mut txs = Vec::new();
for _ in 0..2 {
let mut tx = repo.start_transaction(&settings);
let commit = write_random_commit(tx.mut_repo(), &settings);
let repo = tx.commit("test");
let mut tx = repo.start_transaction(&settings);
tx.mut_repo().remove_head(commit.id());
txs.push(tx);
}
let repo = commit_transactions(&settings, txs);
let mut op_ids_to_delete = Vec::new();
op_ids_to_delete.push(repo.op_id());
let mut tx = repo.start_transaction(&settings);
write_random_commit(tx.mut_repo(), &settings);
let repo = tx.commit("test");
op_ids_to_delete.push(repo.op_id());
let operation_to_reload = repo.operation();
// Sanity check before corrupting the index store
let index = as_readonly_composite(&repo);
assert_eq!(index.num_commits(), 4);
let index_operations_dir = repo.repo_path().join("index").join("operations");
for &op_id in &op_ids_to_delete {
fs::remove_file(index_operations_dir.join(op_id.hex())).unwrap();
}
// When re-indexing, one of the merge parent operations will be selected as
// the parent index segment. The commits in the other side should still be
// reachable.
let repo = repo.reload_at(operation_to_reload).unwrap();
let index = as_readonly_composite(&repo);
assert_eq!(index.num_commits(), 4);
}
/// Test that .jj/repo/index/type is created when the repo is created, and that
/// it is created when an old repo is loaded.
#[test]