mirror of
https://github.com/martinvonz/jj.git
synced 2025-01-14 08:34:43 +00:00
index: keep up to date within transaction
With tons of groundwork done, wee can now finally keep the index up to date within a transaction! That means that we can start relying on the index to always be valid, so we can use it e.g. for finding common ancestors within a transaction. That should help speed up `jj evolve` immensely on large repos. We still don't write the updated index to disk when the transaction closes. That will come later.
This commit is contained in:
parent
e19a65cf14
commit
713d32d803
6 changed files with 79 additions and 12 deletions
|
@ -124,10 +124,10 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns neighbors before the node itself.
|
/// Returns neighbors before the node itself.
|
||||||
pub fn topo_order_reverse<T, ID, II, NI>(
|
pub fn topo_order_reverse<'a, T, ID, II, NI>(
|
||||||
start: II,
|
start: II,
|
||||||
id_fn: Box<dyn Fn(&T) -> ID>,
|
id_fn: Box<dyn Fn(&T) -> ID + 'a>,
|
||||||
mut neighbors_fn: Box<dyn FnMut(&T) -> NI>,
|
mut neighbors_fn: Box<dyn FnMut(&T) -> NI + 'a>,
|
||||||
) -> Vec<T>
|
) -> Vec<T>
|
||||||
where
|
where
|
||||||
T: Hash + Eq + Clone,
|
T: Hash + Eq + Clone,
|
||||||
|
|
|
@ -375,11 +375,14 @@ impl MutableIndex {
|
||||||
IndexRef::Mutable(self)
|
IndexRef::Mutable(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn add_commit(&mut self, commit: &Commit) {
|
pub fn add_commit(&mut self, commit: &Commit) {
|
||||||
self.add_commit_data(commit.id().clone(), commit.parent_ids());
|
self.add_commit_data(commit.id().clone(), commit.parent_ids());
|
||||||
}
|
}
|
||||||
|
|
||||||
fn add_commit_data(&mut self, id: CommitId, parent_ids: Vec<CommitId>) {
|
fn add_commit_data(&mut self, id: CommitId, parent_ids: Vec<CommitId>) {
|
||||||
|
if self.lookup.contains_key(&id) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
let mut entry = MutableGraphEntry {
|
let mut entry = MutableGraphEntry {
|
||||||
commit_id: id,
|
commit_id: id,
|
||||||
generation_number: 0,
|
generation_number: 0,
|
||||||
|
|
|
@ -24,7 +24,7 @@ use thiserror::Error;
|
||||||
use crate::commit_builder::{new_change_id, signature};
|
use crate::commit_builder::{new_change_id, signature};
|
||||||
use crate::evolution::{EvolutionRef, MutableEvolution, ReadonlyEvolution};
|
use crate::evolution::{EvolutionRef, MutableEvolution, ReadonlyEvolution};
|
||||||
use crate::git_store::GitStore;
|
use crate::git_store::GitStore;
|
||||||
use crate::index::ReadonlyIndex;
|
use crate::index::{IndexRef, MutableIndex, ReadonlyIndex};
|
||||||
use crate::local_store::LocalStore;
|
use crate::local_store::LocalStore;
|
||||||
use crate::operation::Operation;
|
use crate::operation::Operation;
|
||||||
use crate::settings::{RepoSettings, UserSettings};
|
use crate::settings::{RepoSettings, UserSettings};
|
||||||
|
@ -70,6 +70,13 @@ impl<'a, 'r> RepoRef<'a, 'r> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn index(&self) -> IndexRef {
|
||||||
|
match self {
|
||||||
|
RepoRef::Readonly(repo) => IndexRef::Readonly(repo.index()),
|
||||||
|
RepoRef::Mutable(repo) => IndexRef::Mutable(repo.index()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn view(&self) -> ViewRef<'a> {
|
pub fn view(&self) -> ViewRef<'a> {
|
||||||
match self {
|
match self {
|
||||||
RepoRef::Readonly(repo) => ViewRef::Readonly(repo.view()),
|
RepoRef::Readonly(repo) => ViewRef::Readonly(repo.view()),
|
||||||
|
@ -327,7 +334,12 @@ impl ReadonlyRepo {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn start_transaction(&self, description: &str) -> Transaction {
|
pub fn start_transaction(&self, description: &str) -> Transaction {
|
||||||
let mut_repo = MutableRepo::new(self, &self.view, &self.evolution.as_ref().unwrap());
|
let mut_repo = MutableRepo::new(
|
||||||
|
self,
|
||||||
|
self.index(),
|
||||||
|
&self.view,
|
||||||
|
&self.evolution.as_ref().unwrap(),
|
||||||
|
);
|
||||||
Transaction::new(mut_repo, description)
|
Transaction::new(mut_repo, description)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -356,6 +368,7 @@ impl ReadonlyRepo {
|
||||||
|
|
||||||
pub struct MutableRepo<'r> {
|
pub struct MutableRepo<'r> {
|
||||||
repo: &'r ReadonlyRepo,
|
repo: &'r ReadonlyRepo,
|
||||||
|
index: Option<MutableIndex>,
|
||||||
view: Option<MutableView>,
|
view: Option<MutableView>,
|
||||||
evolution: Option<MutableEvolution<'static, 'static>>,
|
evolution: Option<MutableEvolution<'static, 'static>>,
|
||||||
}
|
}
|
||||||
|
@ -363,12 +376,15 @@ pub struct MutableRepo<'r> {
|
||||||
impl<'r> MutableRepo<'r> {
|
impl<'r> MutableRepo<'r> {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
repo: &'r ReadonlyRepo,
|
repo: &'r ReadonlyRepo,
|
||||||
|
index: Arc<ReadonlyIndex>,
|
||||||
view: &ReadonlyView,
|
view: &ReadonlyView,
|
||||||
evolution: &ReadonlyEvolution<'r>,
|
evolution: &ReadonlyEvolution<'r>,
|
||||||
) -> Arc<MutableRepo<'r>> {
|
) -> Arc<MutableRepo<'r>> {
|
||||||
let mut_view = view.start_modification();
|
let mut_view = view.start_modification();
|
||||||
|
let mut_index = MutableIndex::incremental(index);
|
||||||
let mut mut_repo = Arc::new(MutableRepo {
|
let mut mut_repo = Arc::new(MutableRepo {
|
||||||
repo,
|
repo,
|
||||||
|
index: Some(mut_index),
|
||||||
view: Some(mut_view),
|
view: Some(mut_view),
|
||||||
evolution: None,
|
evolution: None,
|
||||||
});
|
});
|
||||||
|
@ -390,6 +406,14 @@ impl<'r> MutableRepo<'r> {
|
||||||
self.repo.store()
|
self.repo.store()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn index(&self) -> &MutableIndex {
|
||||||
|
self.index.as_ref().unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn index_mut(&mut self) -> &mut MutableIndex {
|
||||||
|
self.index.as_mut().unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
pub fn base_repo(&self) -> &'r ReadonlyRepo {
|
pub fn base_repo(&self) -> &'r ReadonlyRepo {
|
||||||
self.repo
|
self.repo
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,7 +15,9 @@
|
||||||
use crate::commit::Commit;
|
use crate::commit::Commit;
|
||||||
use crate::commit_builder::CommitBuilder;
|
use crate::commit_builder::CommitBuilder;
|
||||||
use crate::conflicts;
|
use crate::conflicts;
|
||||||
|
use crate::dag_walk::topo_order_reverse;
|
||||||
use crate::evolution::MutableEvolution;
|
use crate::evolution::MutableEvolution;
|
||||||
|
use crate::index::MutableIndex;
|
||||||
use crate::op_store;
|
use crate::op_store;
|
||||||
use crate::operation::Operation;
|
use crate::operation::Operation;
|
||||||
use crate::repo::{MutableRepo, ReadonlyRepo, RepoRef};
|
use crate::repo::{MutableRepo, ReadonlyRepo, RepoRef};
|
||||||
|
@ -59,6 +61,10 @@ impl<'r> Transaction<'r> {
|
||||||
Arc::get_mut(self.repo.as_mut().unwrap()).unwrap()
|
Arc::get_mut(self.repo.as_mut().unwrap()).unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn index(&self) -> &MutableIndex {
|
||||||
|
self.repo.as_ref().unwrap().index()
|
||||||
|
}
|
||||||
|
|
||||||
pub fn view(&self) -> &MutableView {
|
pub fn view(&self) -> &MutableView {
|
||||||
self.repo.as_ref().unwrap().view()
|
self.repo.as_ref().unwrap().view()
|
||||||
}
|
}
|
||||||
|
@ -135,9 +141,25 @@ impl<'r> Transaction<'r> {
|
||||||
.iter()
|
.iter()
|
||||||
.all(|parent_id| current_heads.contains(parent_id))
|
.all(|parent_id| current_heads.contains(parent_id))
|
||||||
{
|
{
|
||||||
|
mut_repo.index_mut().add_commit(head);
|
||||||
mut_repo.view_mut().add_head(head);
|
mut_repo.view_mut().add_head(head);
|
||||||
mut_repo.evolution_mut().add_commit(head);
|
mut_repo.evolution_mut().add_commit(head);
|
||||||
} else {
|
} else {
|
||||||
|
let missing_commits = topo_order_reverse(
|
||||||
|
vec![head.clone()],
|
||||||
|
Box::new(|commit: &Commit| commit.id().clone()),
|
||||||
|
Box::new(|commit: &Commit| -> Vec<Commit> {
|
||||||
|
commit
|
||||||
|
.parents()
|
||||||
|
.into_iter()
|
||||||
|
.filter(|parent| !current_heads.contains(parent.id()))
|
||||||
|
.collect()
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
let mut_index = mut_repo.index_mut();
|
||||||
|
for missing_commit in missing_commits.iter().rev() {
|
||||||
|
mut_index.add_commit(missing_commit);
|
||||||
|
}
|
||||||
mut_repo.view_mut().add_head(head);
|
mut_repo.view_mut().add_head(head);
|
||||||
mut_repo.evolution_mut().invalidate();
|
mut_repo.evolution_mut().invalidate();
|
||||||
}
|
}
|
||||||
|
|
|
@ -337,10 +337,13 @@ fn test_index_commits_incremental(use_git: bool) {
|
||||||
assert_eq!(stats.num_commits, 2 + 3);
|
assert_eq!(stats.num_commits, 2 + 3);
|
||||||
assert_eq!(stats.num_merges, 0);
|
assert_eq!(stats.num_merges, 0);
|
||||||
assert_eq!(stats.max_generation_number, 3);
|
assert_eq!(stats.max_generation_number, 3);
|
||||||
assert_eq!(stats.levels.len(), 2);
|
assert_eq!(stats.levels.len(), 3);
|
||||||
assert_eq!(stats.levels[0].num_commits, 2);
|
assert_eq!(stats.levels[0].num_commits, 2);
|
||||||
assert_eq!(stats.levels[1].num_commits, 3);
|
assert_eq!(stats.levels[1].num_commits, 1);
|
||||||
assert_ne!(stats.levels[1].name, stats.levels[0].name);
|
assert_ne!(stats.levels[1].name, stats.levels[0].name);
|
||||||
|
assert_eq!(stats.levels[2].num_commits, 2);
|
||||||
|
assert_ne!(stats.levels[2].name, stats.levels[0].name);
|
||||||
|
assert_ne!(stats.levels[2].name, stats.levels[1].name);
|
||||||
|
|
||||||
assert_eq!(generation_number(index.clone(), root_commit.id()), 0);
|
assert_eq!(generation_number(index.clone(), root_commit.id()), 0);
|
||||||
assert_eq!(generation_number(index.clone(), commit_a.id()), 1);
|
assert_eq!(generation_number(index.clone(), commit_a.id()), 1);
|
||||||
|
@ -383,9 +386,10 @@ fn test_index_commits_incremental_empty_transaction(use_git: bool) {
|
||||||
assert_eq!(stats.num_commits, 2 + 1);
|
assert_eq!(stats.num_commits, 2 + 1);
|
||||||
assert_eq!(stats.num_merges, 0);
|
assert_eq!(stats.num_merges, 0);
|
||||||
assert_eq!(stats.max_generation_number, 1);
|
assert_eq!(stats.max_generation_number, 1);
|
||||||
assert_eq!(stats.levels.len(), 2);
|
assert_eq!(stats.levels.len(), 3);
|
||||||
assert_eq!(stats.levels[0].num_commits, 0);
|
assert_eq!(stats.levels[0].num_commits, 0);
|
||||||
assert_eq!(stats.levels[1].num_commits, 3);
|
assert_eq!(stats.levels[1].num_commits, 1);
|
||||||
|
assert_eq!(stats.levels[2].num_commits, 2);
|
||||||
|
|
||||||
assert_eq!(generation_number(index.clone(), root_commit.id()), 0);
|
assert_eq!(generation_number(index.clone(), root_commit.id()), 0);
|
||||||
assert_eq!(generation_number(index.clone(), commit_a.id()), 1);
|
assert_eq!(generation_number(index.clone(), commit_a.id()), 1);
|
||||||
|
|
|
@ -298,7 +298,7 @@ fn test_checkout_previous_empty_and_pruned(use_git: bool) {
|
||||||
// #[test_case(true ; "git store")]
|
// #[test_case(true ; "git store")]
|
||||||
fn test_add_head_success(use_git: bool) {
|
fn test_add_head_success(use_git: bool) {
|
||||||
// Test that Transaction::add_head() adds the head, and that it's still there
|
// Test that Transaction::add_head() adds the head, and that it's still there
|
||||||
// after commit.
|
// after commit. It should also be indexed.
|
||||||
let settings = testutils::user_settings();
|
let settings = testutils::user_settings();
|
||||||
let (_temp_dir, mut repo) = testutils::init_repo(&settings, use_git);
|
let (_temp_dir, mut repo) = testutils::init_repo(&settings, use_git);
|
||||||
|
|
||||||
|
@ -311,11 +311,14 @@ fn test_add_head_success(use_git: bool) {
|
||||||
|
|
||||||
let mut tx = repo.start_transaction("test");
|
let mut tx = repo.start_transaction("test");
|
||||||
assert!(!tx.view().heads().contains(new_commit.id()));
|
assert!(!tx.view().heads().contains(new_commit.id()));
|
||||||
|
assert!(!tx.as_repo_ref().index().has_id(new_commit.id()));
|
||||||
tx.add_head(&new_commit);
|
tx.add_head(&new_commit);
|
||||||
assert!(tx.view().heads().contains(new_commit.id()));
|
assert!(tx.view().heads().contains(new_commit.id()));
|
||||||
|
assert!(tx.as_repo_ref().index().has_id(new_commit.id()));
|
||||||
tx.commit();
|
tx.commit();
|
||||||
Arc::get_mut(&mut repo).unwrap().reload();
|
Arc::get_mut(&mut repo).unwrap().reload();
|
||||||
assert!(repo.view().heads().contains(new_commit.id()));
|
assert!(repo.view().heads().contains(new_commit.id()));
|
||||||
|
assert!(repo.index().has_id(new_commit.id()));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test_case(false ; "local store")]
|
#[test_case(false ; "local store")]
|
||||||
|
@ -381,7 +384,12 @@ fn test_add_head_not_immediate_child(use_git: bool) {
|
||||||
// #[test_case(true ; "git store")]
|
// #[test_case(true ; "git store")]
|
||||||
fn test_remove_head(use_git: bool) {
|
fn test_remove_head(use_git: bool) {
|
||||||
// Test that Transaction::remove_head() removes the head, and that it's still
|
// Test that Transaction::remove_head() removes the head, and that it's still
|
||||||
// removed after commit.
|
// removed after commit. It should remain in the index, since we otherwise would
|
||||||
|
// have to reindex everything.
|
||||||
|
// TODO: Consider if it's better to have the index be exactly the commits
|
||||||
|
// reachable from the view's heads. We would probably want to add tombstones
|
||||||
|
// for commits no longer visible in that case so we don't have to reindex e.g.
|
||||||
|
// when the user does `jj op undo`.
|
||||||
let settings = testutils::user_settings();
|
let settings = testutils::user_settings();
|
||||||
let (_temp_dir, mut repo) = testutils::init_repo(&settings, use_git);
|
let (_temp_dir, mut repo) = testutils::init_repo(&settings, use_git);
|
||||||
|
|
||||||
|
@ -403,6 +411,9 @@ fn test_remove_head(use_git: bool) {
|
||||||
assert!(!heads.contains(commit3.id()));
|
assert!(!heads.contains(commit3.id()));
|
||||||
assert!(!heads.contains(commit2.id()));
|
assert!(!heads.contains(commit2.id()));
|
||||||
assert!(!heads.contains(commit1.id()));
|
assert!(!heads.contains(commit1.id()));
|
||||||
|
assert!(tx.index().has_id(commit1.id()));
|
||||||
|
assert!(tx.index().has_id(commit2.id()));
|
||||||
|
assert!(tx.index().has_id(commit3.id()));
|
||||||
tx.commit();
|
tx.commit();
|
||||||
|
|
||||||
Arc::get_mut(&mut repo).unwrap().reload();
|
Arc::get_mut(&mut repo).unwrap().reload();
|
||||||
|
@ -410,6 +421,9 @@ fn test_remove_head(use_git: bool) {
|
||||||
assert!(!heads.contains(commit3.id()));
|
assert!(!heads.contains(commit3.id()));
|
||||||
assert!(!heads.contains(commit2.id()));
|
assert!(!heads.contains(commit2.id()));
|
||||||
assert!(!heads.contains(commit1.id()));
|
assert!(!heads.contains(commit1.id()));
|
||||||
|
assert!(repo.index().has_id(commit1.id()));
|
||||||
|
assert!(repo.index().has_id(commit2.id()));
|
||||||
|
assert!(repo.index().has_id(commit3.id()));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test_case(false ; "local store")]
|
#[test_case(false ; "local store")]
|
||||||
|
|
Loading…
Reference in a new issue