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

evolution: delete it now that we don't use it anymore (#32)

It's been a lot of work, but now we're finally able to remove the
`Evolution` state! `jj obslog` still works as before (it just walks
the predecessor pointers).
This commit is contained in:
Martin von Zweigbergk 2021-10-06 23:05:10 -07:00
parent 82388c4d88
commit 4c4e436f38
8 changed files with 11 additions and 1029 deletions

View file

@ -1,799 +0,0 @@
// 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.
use std::collections::{HashMap, HashSet};
use std::sync::Arc;
use itertools::Itertools;
use crate::backend::{ChangeId, CommitId};
use crate::commit::Commit;
use crate::dag_walk::{bfs, leaves};
use crate::repo::{MutableRepo, ReadonlyRepo, RepoRef};
// TODO: Combine some maps/sets and use a struct as value instead.
// TODO: Move some of this into the index?
#[derive(Debug, Clone, Default)]
struct State {
children: HashMap<CommitId, HashSet<CommitId>>,
/// Contains all successors whether they have the same change id or not.
successors: HashMap<CommitId, HashSet<CommitId>>,
/// Contains the subset of the keys in `successors` for which there is a
/// successor with the same change id.
obsolete_commits: HashSet<CommitId>,
pruned_commits: HashSet<CommitId>,
orphan_commits: HashSet<CommitId>,
/// If there's more than one element in the value, then the change is
/// divergent.
non_obsoletes_by_changeid: HashMap<ChangeId, HashSet<CommitId>>,
}
impl State {
fn calculate(repo: RepoRef) -> State {
let view = repo.view();
let index = repo.index();
let mut state = State::default();
let head_ids = view.heads().iter().cloned().collect_vec();
let mut change_to_commits = HashMap::new();
for head_id in &head_ids {
state.children.insert(head_id.clone(), HashSet::new());
}
for entry in index.walk_revs(&head_ids, &[]) {
let commit_id = entry.commit_id();
change_to_commits
.entry(entry.change_id().clone())
.or_insert_with(HashSet::new)
.insert(commit_id.clone());
if entry.is_pruned() {
state.pruned_commits.insert(commit_id.clone());
}
for parent_entry in entry.parents() {
let parent_id = parent_entry.commit_id();
state
.children
.entry(parent_id.clone())
.or_insert_with(HashSet::new)
.insert(commit_id.clone());
}
for predecessor_entry in entry.predecessors() {
let predecessor_id = predecessor_entry.commit_id();
state
.successors
.entry(predecessor_id.clone())
.or_insert_with(HashSet::new)
.insert(commit_id.clone());
if predecessor_entry.change_id() == entry.change_id() {
state.obsolete_commits.insert(predecessor_id.clone());
}
}
}
// Find non-obsolete commits by change id (potentially divergent commits)
for (change_id, commit_ids) in change_to_commits {
let non_obsoletes: HashSet<CommitId> = commit_ids
.difference(&state.obsolete_commits)
.cloned()
.collect();
state
.non_obsoletes_by_changeid
.insert(change_id, non_obsoletes);
}
// Find orphans by walking to the children of obsolete commits
let mut work = state.obsolete_commits.iter().cloned().collect_vec();
work.extend(state.pruned_commits.iter().cloned());
while !work.is_empty() {
let commit_id = work.pop().unwrap();
if let Some(children) = state.children.get(&commit_id) {
for child in children {
if state.orphan_commits.insert(child.clone()) {
work.push(child.clone());
}
}
}
}
state.orphan_commits = state
.orphan_commits
.iter()
.filter(|commit_id| {
!(state.obsolete_commits.contains(commit_id)
|| state.pruned_commits.contains(commit_id))
})
.cloned()
.collect();
state
}
fn successors(&self, commit_id: &CommitId) -> HashSet<CommitId> {
self.successors
.get(commit_id)
.cloned()
.unwrap_or_else(HashSet::new)
}
fn is_obsolete(&self, commit_id: &CommitId) -> bool {
self.obsolete_commits.contains(commit_id)
}
fn is_orphan(&self, commit_id: &CommitId) -> bool {
self.orphan_commits.contains(commit_id)
}
fn is_divergent(&self, change_id: &ChangeId) -> bool {
self.non_obsoletes_by_changeid
.get(change_id)
.map_or(false, |non_obsoletes| non_obsoletes.len() > 1)
}
fn non_obsoletes(&self, change_id: &ChangeId) -> HashSet<CommitId> {
self.non_obsoletes_by_changeid
.get(change_id)
.cloned()
.unwrap_or_else(HashSet::new)
}
fn add_commit(&mut self, commit: &Commit) {
self.add_commit_data(
commit.id(),
commit.change_id(),
&commit.parent_ids(),
&commit.predecessor_ids(),
commit.is_pruned(),
);
}
fn add_commit_data(
&mut self,
commit_id: &CommitId,
change_id: &ChangeId,
parents: &[CommitId],
predecessors: &[CommitId],
is_pruned: bool,
) {
// TODO: Error out (or ignore?) if the root id is a predecessor or divergent
// (adding the root once should be fine). Perhaps this is not the right
// place to do that (we don't know what the root id is here).
for parent in parents {
self.children
.entry(parent.clone())
.or_default()
.insert(commit_id.clone());
}
if is_pruned {
self.pruned_commits.insert(commit_id.clone());
}
// Update the non_obsoletes_by_changeid by adding the new commit and removing
// the predecessors.
self.non_obsoletes_by_changeid
.entry(change_id.clone())
.or_default()
.insert(commit_id.clone());
for predecessor in predecessors {
self.successors
.entry(predecessor.clone())
.or_default()
.insert(commit_id.clone());
let became_obsolete = self
.non_obsoletes_by_changeid
.get_mut(change_id)
.unwrap()
.remove(predecessor);
// Mark descendants as orphans if the predecessor just became obsolete.
if became_obsolete {
assert!(self.obsolete_commits.insert(predecessor.clone()));
let mut descendants = HashSet::new();
for descendant in bfs(
vec![predecessor.clone()],
Box::new(|commit_id| commit_id.clone()),
Box::new(|commit_id| {
self.children
.get(commit_id)
.cloned()
.unwrap_or_else(HashSet::new)
}),
) {
descendants.insert(descendant);
}
descendants.remove(predecessor);
descendants = descendants
.iter()
.filter(|commit_id| {
!(self.obsolete_commits.contains(commit_id)
|| self.pruned_commits.contains(commit_id))
})
.cloned()
.collect();
self.orphan_commits.extend(descendants);
}
}
// Mark the new commit an orphan if any of its parents are obsolete, pruned, or
// orphans. Note that this has to be done late, in case a parent just got marked
// as obsolete or orphan above.
let is_orphan = parents.iter().any(|parent| {
self.obsolete_commits.contains(parent)
|| self.pruned_commits.contains(parent)
|| self.orphan_commits.contains(commit_id)
});
if is_orphan {
self.orphan_commits.insert(commit_id.clone());
}
}
pub fn new_parent(&self, repo: RepoRef, old_parent_id: &CommitId) -> HashSet<CommitId> {
let store = repo.store();
let mut new_parents = HashSet::new();
if let Some(successor_ids) = self.successors.get(old_parent_id) {
let old_parent = store.get_commit(old_parent_id).unwrap();
let successors: HashSet<_> = successor_ids
.iter()
.map(|id| store.get_commit(id).unwrap())
.collect();
let mut children = HashMap::new();
for successor in &successors {
for parent in successor.parents() {
if let Some(parent) = successors.get(&parent) {
children
.entry(parent.clone())
.or_insert_with(HashSet::new)
.insert(successor.clone());
}
}
}
let mut all_candidates = HashSet::new();
for successor in &successors {
if successor.change_id() != old_parent.change_id() {
continue;
}
// Start with the successor as candidate.
let mut candidates = HashSet::new();
candidates.insert(successor.clone());
// If the successor has children that are successors of the same
// commit, we consider the original commit to be a split. We then return
// the tip-most successor.
candidates = leaves(
candidates,
&mut |commit: &Commit| -> HashSet<Commit> {
if let Some(children) = children.get(commit) {
children.clone()
} else {
HashSet::new()
}
},
&|commit: &Commit| -> CommitId { commit.id().clone() },
);
// If a successor is pruned, use its parent(s) instead.
candidates = leaves(
candidates,
&mut |commit: &Commit| -> Vec<Commit> {
if commit.is_pruned() {
commit.parents()
} else {
vec![]
}
},
&|commit: &Commit| -> CommitId { commit.id().clone() },
);
for candidate in candidates {
all_candidates.insert(candidate.id().clone());
}
}
// Filter out candidates that are ancestors of other candidates.
let all_candidates = repo
.index()
.heads(all_candidates.iter())
.into_iter()
.collect_vec();
for candidate in all_candidates {
// TODO: Make this not recursive
for effective_successor in self.new_parent(repo, &candidate) {
new_parents.insert(effective_successor);
}
}
}
if new_parents.is_empty() {
// TODO: Should we go to the parents here too if the commit is pruned?
new_parents.insert(old_parent_id.clone());
}
new_parents
}
}
pub enum EvolutionRef<'a> {
Readonly(Arc<ReadonlyEvolution>),
Mutable(&'a MutableEvolution),
}
impl EvolutionRef<'_> {
pub fn successors(&self, commit_id: &CommitId) -> HashSet<CommitId> {
match self {
EvolutionRef::Readonly(evolution) => evolution.successors(commit_id),
EvolutionRef::Mutable(evolution) => evolution.successors(commit_id),
}
}
pub fn is_obsolete(&self, commit_id: &CommitId) -> bool {
match self {
EvolutionRef::Readonly(evolution) => evolution.is_obsolete(commit_id),
EvolutionRef::Mutable(evolution) => evolution.is_obsolete(commit_id),
}
}
pub fn is_orphan(&self, commit_id: &CommitId) -> bool {
match self {
EvolutionRef::Readonly(evolution) => evolution.is_orphan(commit_id),
EvolutionRef::Mutable(evolution) => evolution.is_orphan(commit_id),
}
}
pub fn is_divergent(&self, change_id: &ChangeId) -> bool {
match self {
EvolutionRef::Readonly(evolution) => evolution.is_divergent(change_id),
EvolutionRef::Mutable(evolution) => evolution.is_divergent(change_id),
}
}
pub fn non_obsoletes(&self, change_id: &ChangeId) -> HashSet<CommitId> {
match self {
EvolutionRef::Readonly(evolution) => evolution.non_obsoletes(change_id),
EvolutionRef::Mutable(evolution) => evolution.non_obsoletes(change_id),
}
}
}
pub struct ReadonlyEvolution {
state: State,
}
impl ReadonlyEvolution {
pub fn new(repo: &ReadonlyRepo) -> ReadonlyEvolution {
ReadonlyEvolution {
state: State::calculate(repo.as_repo_ref()),
}
}
pub fn start_modification(&self) -> MutableEvolution {
MutableEvolution {
state: self.state.clone(),
}
}
pub fn successors(&self, commit_id: &CommitId) -> HashSet<CommitId> {
self.state.successors(commit_id)
}
pub fn is_obsolete(&self, commit_id: &CommitId) -> bool {
self.state.is_obsolete(commit_id)
}
pub fn is_orphan(&self, commit_id: &CommitId) -> bool {
self.state.is_orphan(commit_id)
}
pub fn is_divergent(&self, change_id: &ChangeId) -> bool {
self.state.is_divergent(change_id)
}
pub fn non_obsoletes(&self, change_id: &ChangeId) -> HashSet<CommitId> {
self.state.non_obsoletes(change_id)
}
pub fn new_parent(&self, repo: RepoRef, old_parent_id: &CommitId) -> HashSet<CommitId> {
self.state.new_parent(repo, old_parent_id)
}
}
pub struct MutableEvolution {
state: State,
}
impl MutableEvolution {
pub fn new(repo: &MutableRepo) -> MutableEvolution {
MutableEvolution {
state: State::calculate(repo.as_repo_ref()),
}
}
pub fn successors(&self, commit_id: &CommitId) -> HashSet<CommitId> {
self.state.successors(commit_id)
}
pub fn is_obsolete(&self, commit_id: &CommitId) -> bool {
self.state.is_obsolete(commit_id)
}
pub fn is_orphan(&self, commit_id: &CommitId) -> bool {
self.state.is_orphan(commit_id)
}
pub fn is_divergent(&self, change_id: &ChangeId) -> bool {
self.state.is_divergent(change_id)
}
pub fn non_obsoletes(&self, change_id: &ChangeId) -> HashSet<CommitId> {
self.state.non_obsoletes(change_id)
}
pub fn new_parent(&self, repo: RepoRef, old_parent_id: &CommitId) -> HashSet<CommitId> {
self.state.new_parent(repo, old_parent_id)
}
pub fn add_commit(&mut self, commit: &Commit) {
self.state.add_commit(commit);
}
pub fn freeze(self) -> ReadonlyEvolution {
ReadonlyEvolution { state: self.state }
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn add_commit_data_initial() {
let mut state = State::default();
let initial_commit = CommitId::from_hex("aaa111");
let initial_change = ChangeId::from_hex("aaa111");
state.add_commit_data(&initial_commit, &initial_change, &[], &[], false);
assert!(!state.is_obsolete(&initial_commit));
assert!(!state.is_orphan(&initial_commit));
assert!(!state.is_divergent(&initial_change));
}
#[test]
fn add_commit_data_pruned() {
let mut state = State::default();
let initial_commit = CommitId::from_hex("aaa111");
let initial_change = ChangeId::from_hex("aaa111");
state.add_commit_data(&initial_commit, &initial_change, &[], &[], true);
assert!(!state.is_obsolete(&initial_commit));
assert!(!state.is_orphan(&initial_commit));
assert!(!state.is_divergent(&initial_change));
}
#[test]
fn add_commit_data_creating_orphan() {
let mut state = State::default();
let initial_commit = CommitId::from_hex("aaa111");
let initial_change = ChangeId::from_hex("aaa111");
let orphan_commit1 = CommitId::from_hex("bbb111");
let orphan_change1 = ChangeId::from_hex("bbb111");
let orphan_commit2 = CommitId::from_hex("ccc111");
let orphan_change2 = ChangeId::from_hex("ccc111");
let obsolete_orphan_commit = CommitId::from_hex("ddd111");
let obsolete_orphan_change = ChangeId::from_hex("ddd111");
let pruned_orphan_commit = CommitId::from_hex("eee111");
let rewritten_commit = CommitId::from_hex("aaa222");
state.add_commit_data(&initial_commit, &initial_change, &[], &[], false);
state.add_commit_data(
&orphan_commit1,
&orphan_change1,
&[initial_commit.clone()],
&[],
false,
);
state.add_commit_data(
&orphan_commit2,
&orphan_change2,
&[orphan_commit1.clone()],
&[],
false,
);
state.add_commit_data(
&obsolete_orphan_commit,
&obsolete_orphan_change,
&[initial_commit.clone()],
&[],
false,
);
state.add_commit_data(
&pruned_orphan_commit,
&obsolete_orphan_change,
&[initial_commit.clone()],
&[obsolete_orphan_commit.clone()],
true,
);
state.add_commit_data(
&rewritten_commit,
&initial_change,
&[],
&[initial_commit],
false,
);
assert!(state.is_orphan(&orphan_commit1));
assert!(state.is_orphan(&orphan_commit2));
assert!(!state.is_orphan(&obsolete_orphan_commit));
assert!(!state.is_orphan(&pruned_orphan_commit));
assert!(!state.is_obsolete(&orphan_commit1));
assert!(!state.is_obsolete(&orphan_commit2));
assert!(state.is_obsolete(&obsolete_orphan_commit));
assert!(!state.is_obsolete(&pruned_orphan_commit));
}
#[test]
fn add_commit_data_new_commit_on_obsolete() {
let mut state = State::default();
let initial_commit = CommitId::from_hex("aaa111");
let initial_change = ChangeId::from_hex("aaa111");
let rewritten_commit = CommitId::from_hex("aaa222");
let new_commit = CommitId::from_hex("bbb111");
let new_change = ChangeId::from_hex("bbb111");
state.add_commit_data(&initial_commit, &initial_change, &[], &[], false);
state.add_commit_data(
&rewritten_commit,
&initial_change,
&[],
&[initial_commit.clone()],
false,
);
state.add_commit_data(&new_commit, &new_change, &[initial_commit], &[], false);
assert!(state.is_orphan(&new_commit));
}
#[test]
fn add_commit_data_new_commit_on_orphan() {
let mut state = State::default();
let initial_commit = CommitId::from_hex("aaa111");
let initial_change = ChangeId::from_hex("aaa111");
let rewritten_commit = CommitId::from_hex("aaa222");
let orphan_commit = CommitId::from_hex("bbb111");
let orphan_change = ChangeId::from_hex("bbb111");
let new_commit = CommitId::from_hex("bbb111");
let new_change = ChangeId::from_hex("bbb111");
state.add_commit_data(&initial_commit, &initial_change, &[], &[], false);
state.add_commit_data(
&rewritten_commit,
&initial_change,
&[],
&[initial_commit.clone()],
false,
);
state.add_commit_data(
&orphan_commit,
&orphan_change,
&[initial_commit],
&[],
false,
);
state.add_commit_data(&new_commit, &new_change, &[orphan_commit], &[], false);
assert!(state.is_orphan(&new_commit));
}
#[test]
fn add_commit_data_new_commit_on_pruned() {
let mut state = State::default();
let pruned_commit = CommitId::from_hex("aaa111");
let pruned_change = ChangeId::from_hex("aaa111");
let new_commit = CommitId::from_hex("bbb111");
let new_change = ChangeId::from_hex("bbb111");
state.add_commit_data(&pruned_commit, &pruned_change, &[], &[], true);
state.add_commit_data(&new_commit, &new_change, &[pruned_commit], &[], false);
assert!(state.is_orphan(&new_commit));
}
#[test]
fn add_commit_data_rewrite_as_child() {
let mut state = State::default();
let initial_commit = CommitId::from_hex("aaa111");
let initial_change = ChangeId::from_hex("aaa111");
let rewritten_commit = CommitId::from_hex("aaa222");
state.add_commit_data(&initial_commit, &initial_change, &[], &[], false);
// The new commit is both a child and a successor of the initial commit
state.add_commit_data(
&rewritten_commit,
&initial_change,
&[initial_commit.clone()],
&[initial_commit.clone()],
false,
);
assert!(state.is_obsolete(&initial_commit));
assert!(!state.is_obsolete(&rewritten_commit));
assert!(!state.is_orphan(&initial_commit));
assert!(state.is_orphan(&rewritten_commit));
assert!(!state.is_divergent(&initial_change));
}
#[test]
fn add_commit_data_duplicates() {
let mut state = State::default();
let initial_commit = CommitId::from_hex("aaa111");
let initial_change = ChangeId::from_hex("aaa111");
let duplicate_commit1 = CommitId::from_hex("bbb111");
let duplicate_change1 = ChangeId::from_hex("bbb111");
let duplicate_commit2 = CommitId::from_hex("ccc111");
let duplicate_change2 = ChangeId::from_hex("ccc111");
state.add_commit_data(&initial_commit, &initial_change, &[], &[], false);
state.add_commit_data(
&duplicate_commit1,
&duplicate_change1,
&[],
&[initial_commit.clone()],
false,
);
state.add_commit_data(
&duplicate_commit2,
&duplicate_change2,
&[],
&[initial_commit.clone()],
false,
);
assert!(!state.is_obsolete(&initial_commit));
assert!(!state.is_obsolete(&duplicate_commit1));
assert!(!state.is_obsolete(&duplicate_commit2));
assert!(!state.is_divergent(&initial_change));
assert!(!state.is_divergent(&duplicate_change1));
assert!(!state.is_divergent(&duplicate_change2));
assert_eq!(
state.successors(&initial_commit),
hashset!(duplicate_commit1, duplicate_commit2)
);
}
#[test]
fn add_commit_data_divergent() {
let mut state = State::default();
let initial_commit = CommitId::from_hex("aaa111");
let initial_change = ChangeId::from_hex("aaa111");
let rewritten_commit1 = CommitId::from_hex("aaa222");
let rewritten_commit2 = CommitId::from_hex("aaa333");
state.add_commit_data(&initial_commit, &initial_change, &[], &[], false);
state.add_commit_data(
&rewritten_commit1,
&initial_change,
&[],
&[initial_commit.clone()],
false,
);
state.add_commit_data(
&rewritten_commit2,
&initial_change,
&[],
&[initial_commit.clone()],
false,
);
assert!(state.is_obsolete(&initial_commit));
assert!(!state.is_obsolete(&rewritten_commit1));
assert!(!state.is_obsolete(&rewritten_commit2));
assert!(state.is_divergent(&initial_change));
assert_eq!(
state.successors(&initial_commit),
hashset!(rewritten_commit1, rewritten_commit2)
);
}
#[test]
fn add_commit_data_divergent_pruned() {
let mut state = State::default();
let initial_commit = CommitId::from_hex("aaa111");
let initial_change = ChangeId::from_hex("aaa111");
let rewritten_pruned = CommitId::from_hex("aaa222");
let rewritten_non_pruned = CommitId::from_hex("aaa333");
state.add_commit_data(&initial_commit, &initial_change, &[], &[], false);
state.add_commit_data(
&rewritten_pruned,
&initial_change,
&[],
&[initial_commit.clone()],
true,
);
state.add_commit_data(
&rewritten_non_pruned,
&initial_change,
&[],
&[initial_commit.clone()],
false,
);
assert!(state.is_obsolete(&initial_commit));
assert!(!state.is_obsolete(&rewritten_pruned));
assert!(!state.is_obsolete(&rewritten_non_pruned));
// It's still divergent even if one side is pruned
assert!(state.is_divergent(&initial_change));
assert_eq!(
state.successors(&initial_commit),
hashset!(rewritten_pruned, rewritten_non_pruned)
);
}
#[test]
fn add_commit_data_divergent_unrelated() {
let mut state = State::default();
let initial_commit = CommitId::from_hex("aaa111");
let initial_change = ChangeId::from_hex("aaa111");
let rewritten_commit = CommitId::from_hex("aaa222");
state.add_commit_data(&initial_commit, &initial_change, &[], &[], false);
// Same change id as the initial commit but no predecessor relationship to it
state.add_commit_data(&rewritten_commit, &initial_change, &[], &[], false);
assert!(!state.is_obsolete(&initial_commit));
assert!(!state.is_obsolete(&rewritten_commit));
assert!(state.is_divergent(&initial_change));
assert_eq!(state.successors(&initial_commit), hashset!());
}
#[test]
fn add_commit_data_divergent_convergent() {
let mut state = State::default();
let initial_commit = CommitId::from_hex("aaa111");
let initial_change = ChangeId::from_hex("aaa111");
let rewritten_commit1 = CommitId::from_hex("aaa222");
let rewritten_commit2 = CommitId::from_hex("aaa333");
let convergent_commit = CommitId::from_hex("aaa444");
state.add_commit_data(&initial_commit, &initial_change, &[], &[], false);
state.add_commit_data(
&rewritten_commit1,
&initial_change,
&[],
&[initial_commit.clone()],
false,
);
state.add_commit_data(
&rewritten_commit2,
&initial_change,
&[],
&[initial_commit.clone()],
false,
);
state.add_commit_data(
&convergent_commit,
&initial_change,
&[],
&[rewritten_commit1.clone(), rewritten_commit2.clone()],
false,
);
assert!(state.is_obsolete(&initial_commit));
assert!(state.is_obsolete(&rewritten_commit1));
assert!(state.is_obsolete(&rewritten_commit2));
assert!(!state.is_obsolete(&convergent_commit));
assert!(!state.is_divergent(&initial_change));
assert_eq!(
state.successors(&rewritten_commit1),
hashset!(convergent_commit.clone())
);
assert_eq!(
state.successors(&rewritten_commit2),
hashset!(convergent_commit)
);
}
}

View file

@ -29,7 +29,6 @@ pub mod commit_builder;
pub mod conflicts;
pub mod dag_walk;
pub mod diff;
pub mod evolution;
pub mod file_util;
pub mod files;
pub mod git;

View file

@ -26,7 +26,6 @@ use crate::backend::{Backend, BackendError, CommitId};
use crate::commit::Commit;
use crate::commit_builder::{new_change_id, signature, CommitBuilder};
use crate::dag_walk::topo_order_reverse;
use crate::evolution::{EvolutionRef, MutableEvolution, ReadonlyEvolution};
use crate::git_backend::GitBackend;
use crate::index::{IndexRef, MutableIndex, ReadonlyIndex};
use crate::index_store::IndexStore;
@ -98,13 +97,6 @@ impl<'a> RepoRef<'a> {
RepoRef::Mutable(repo) => repo.view(),
}
}
pub fn evolution(&self) -> EvolutionRef {
match self {
RepoRef::Readonly(repo) => EvolutionRef::Readonly(repo.evolution()),
RepoRef::Mutable(repo) => EvolutionRef::Mutable(repo.evolution()),
}
}
}
pub struct ReadonlyRepo {
@ -119,7 +111,6 @@ pub struct ReadonlyRepo {
index: Mutex<Option<Arc<ReadonlyIndex>>>,
working_copy: Arc<Mutex<WorkingCopy>>,
view: View,
evolution: Mutex<Option<Arc<ReadonlyEvolution>>>,
}
impl Debug for ReadonlyRepo {
@ -260,7 +251,6 @@ impl ReadonlyRepo {
index: Mutex::new(None),
working_copy: Arc::new(Mutex::new(working_copy)),
view,
evolution: Mutex::new(None),
};
let repo = Arc::new(repo);
@ -313,14 +303,6 @@ impl ReadonlyRepo {
&self.view
}
pub fn evolution(&self) -> Arc<ReadonlyEvolution> {
let mut locked_evolution = self.evolution.lock().unwrap();
if locked_evolution.is_none() {
locked_evolution.replace(Arc::new(ReadonlyEvolution::new(self)));
}
locked_evolution.as_ref().unwrap().clone()
}
pub fn index(&self) -> &Arc<ReadonlyIndex> {
let mut locked_index = self.index.lock().unwrap();
if locked_index.is_none() {
@ -375,13 +357,7 @@ impl ReadonlyRepo {
}
pub fn start_transaction(self: &Arc<ReadonlyRepo>, description: &str) -> Transaction {
let locked_evolution = self.evolution.lock().unwrap();
let mut_repo = MutableRepo::new(
self.clone(),
self.index().clone(),
&self.view,
locked_evolution.as_ref(),
);
let mut_repo = MutableRepo::new(self.clone(), self.index().clone(), &self.view);
Transaction::new(mut_repo, description)
}
@ -474,7 +450,6 @@ impl RepoLoader {
view: View,
working_copy: Arc<Mutex<WorkingCopy>>,
index: Arc<ReadonlyIndex>,
evolution: Option<Arc<ReadonlyEvolution>>,
) -> Arc<ReadonlyRepo> {
let repo = ReadonlyRepo {
repo_path: self.repo_path.clone(),
@ -488,7 +463,6 @@ impl RepoLoader {
index: Mutex::new(Some(index)),
working_copy,
view,
evolution: Mutex::new(evolution),
};
Arc::new(repo)
}
@ -511,7 +485,6 @@ impl RepoLoader {
index: Mutex::new(None),
working_copy: Arc::new(Mutex::new(working_copy)),
view,
evolution: Mutex::new(None),
};
Arc::new(repo)
}
@ -521,7 +494,6 @@ pub struct MutableRepo {
base_repo: Arc<ReadonlyRepo>,
index: MutableIndex,
view: View,
evolution: Mutex<Option<MutableEvolution>>,
rewritten_commits: HashMap<CommitId, HashSet<CommitId>>,
abandoned_commits: HashSet<CommitId>,
}
@ -531,16 +503,13 @@ impl MutableRepo {
base_repo: Arc<ReadonlyRepo>,
index: Arc<ReadonlyIndex>,
view: &View,
evolution: Option<&Arc<ReadonlyEvolution>>,
) -> MutableRepo {
let mut_view = view.start_modification();
let mut_index = MutableIndex::incremental(index);
let mut_evolution = evolution.map(|evolution| evolution.start_modification());
MutableRepo {
base_repo,
index: mut_index,
view: mut_view,
evolution: Mutex::new(mut_evolution),
rewritten_commits: Default::default(),
abandoned_commits: Default::default(),
}
@ -570,33 +539,8 @@ impl MutableRepo {
&self.view
}
pub fn consume(self) -> (MutableIndex, View, Option<MutableEvolution>) {
(self.index, self.view, self.evolution.lock().unwrap().take())
}
pub fn evolution(&self) -> &MutableEvolution {
let mut locked_evolution = self.evolution.lock().unwrap();
if locked_evolution.is_none() {
locked_evolution.replace(MutableEvolution::new(self));
}
let evolution = locked_evolution.as_ref().unwrap();
// Extend lifetime from lifetime of MutexGuard to lifetime of self. Safe because
// the value won't change again except for by invalidate_evolution(), which
// requires a mutable reference.
unsafe { std::mem::transmute(evolution) }
}
pub fn evolution_mut(&mut self) -> Option<&mut MutableEvolution> {
let mut locked_evolution = self.evolution.lock().unwrap();
let maybe_evolution = locked_evolution.as_mut();
// Extend lifetime from lifetime of MutexGuard to lifetime of self. Safe because
// the value won't change again except for by invalidate_evolution(), which
// requires a mutable reference.
unsafe { std::mem::transmute(maybe_evolution) }
}
pub fn invalidate_evolution(&mut self) {
self.evolution.lock().unwrap().take();
pub fn consume(self) -> (MutableIndex, View) {
(self.index, self.view)
}
pub fn write_commit(&mut self, commit: backend::Commit) -> Commit {
@ -741,9 +685,6 @@ impl MutableRepo {
for parent_id in head.parent_ids() {
self.view.remove_head(&parent_id);
}
if let Some(evolution) = self.evolution_mut() {
evolution.add_commit(head)
}
} else {
let missing_commits = topo_order_reverse(
vec![head.clone()],
@ -761,27 +702,21 @@ impl MutableRepo {
}
self.view.add_head(head.id());
self.enforce_view_invariants();
self.invalidate_evolution();
}
}
pub fn remove_head(&mut self, head: &CommitId) {
self.view.remove_head(head);
self.enforce_view_invariants();
self.invalidate_evolution();
}
pub fn add_public_head(&mut self, head: &Commit) {
self.view.add_public_head(head.id());
self.enforce_view_invariants();
if let Some(evolution) = self.evolution_mut() {
evolution.add_commit(head)
}
}
pub fn remove_public_head(&mut self, head: &CommitId) {
self.view.remove_public_head(head);
self.invalidate_evolution();
}
pub fn get_branch(&self, name: &str) -> Option<&BranchTarget> {
@ -843,7 +778,6 @@ impl MutableRepo {
pub fn set_view(&mut self, data: op_store::View) {
self.view.set_view(data);
self.enforce_view_invariants();
self.invalidate_evolution();
}
pub fn merge(&mut self, base_repo: &ReadonlyRepo, other_repo: &ReadonlyRepo) {
@ -857,8 +791,6 @@ impl MutableRepo {
self.view
.merge(self.index.as_index_ref(), &base_repo.view, &other_repo.view);
self.enforce_view_invariants();
self.invalidate_evolution();
}
pub fn merge_single_ref(

View file

@ -16,7 +16,6 @@ use std::collections::HashMap;
use std::sync::{Arc, Mutex};
use crate::backend::Timestamp;
use crate::evolution::ReadonlyEvolution;
use crate::index::ReadonlyIndex;
use crate::op_store;
use crate::op_store::{OperationId, OperationMetadata};
@ -74,9 +73,7 @@ impl Transaction {
pub fn write(mut self) -> UnpublishedOperation {
let mut_repo = self.repo.take().unwrap();
let base_repo = mut_repo.base_repo().clone();
let (mut_index, view, maybe_mut_evolution) = mut_repo.consume();
let maybe_evolution =
maybe_mut_evolution.map(|mut_evolution| Arc::new(mut_evolution.freeze()));
let (mut_index, view) = mut_repo.consume();
let index = base_repo.index_store().write_index(mut_index).unwrap();
let view_id = base_repo.op_store().write_view(view.store_view()).unwrap();
@ -105,7 +102,6 @@ impl Transaction {
view,
base_repo.working_copy().clone(),
index,
maybe_evolution,
)
}
@ -127,7 +123,6 @@ struct NewRepoData {
view: View,
working_copy: Arc<Mutex<WorkingCopy>>,
index: Arc<ReadonlyIndex>,
evolution: Option<Arc<ReadonlyEvolution>>,
}
pub struct UnpublishedOperation {
@ -143,14 +138,12 @@ impl UnpublishedOperation {
view: View,
working_copy: Arc<Mutex<WorkingCopy>>,
index: Arc<ReadonlyIndex>,
evolution: Option<Arc<ReadonlyEvolution>>,
) -> Self {
let data = Some(NewRepoData {
operation,
view,
working_copy,
index,
evolution,
});
UnpublishedOperation {
repo_loader,
@ -168,26 +161,18 @@ impl UnpublishedOperation {
self.repo_loader
.op_heads_store()
.update_op_heads(&data.operation);
let repo = self.repo_loader.create_from(
data.operation,
data.view,
data.working_copy,
data.index,
data.evolution,
);
let repo =
self.repo_loader
.create_from(data.operation, data.view, data.working_copy, data.index);
self.closed = true;
repo
}
pub fn leave_unpublished(mut self) -> Arc<ReadonlyRepo> {
let data = self.data.take().unwrap();
let repo = self.repo_loader.create_from(
data.operation,
data.view,
data.working_copy,
data.index,
data.evolution,
);
let repo =
self.repo_loader
.create_from(data.operation, data.view, data.working_copy, data.index);
self.closed = true;
repo
}

View file

@ -1,128 +0,0 @@
// 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.
#![feature(assert_matches)]
use jujutsu_lib::commit::Commit;
use jujutsu_lib::commit_builder::CommitBuilder;
use jujutsu_lib::repo::ReadonlyRepo;
use jujutsu_lib::settings::UserSettings;
use jujutsu_lib::testutils;
use test_case::test_case;
#[must_use]
fn child_commit(settings: &UserSettings, repo: &ReadonlyRepo, commit: &Commit) -> CommitBuilder {
testutils::create_random_commit(settings, repo).set_parents(vec![commit.id().clone()])
}
#[test_case(false ; "local backend")]
#[test_case(true ; "git backend")]
fn test_obsolete_and_orphan(use_git: bool) {
let settings = testutils::user_settings();
let (_temp_dir, repo) = testutils::init_repo(&settings, use_git);
let root_commit = repo.store().root_commit();
let mut tx = repo.start_transaction("test");
let mut_repo = tx.mut_repo();
// A commit without successors should not be obsolete and not an orphan.
let original = child_commit(&settings, &repo, &root_commit).write_to_repo(mut_repo);
assert!(!mut_repo.evolution().is_obsolete(original.id()));
assert!(!mut_repo.evolution().is_orphan(original.id()));
// A commit with a successor with a different change_id should not be obsolete.
let child = child_commit(&settings, &repo, &original).write_to_repo(mut_repo);
let grandchild = child_commit(&settings, &repo, &child).write_to_repo(mut_repo);
let cherry_picked = child_commit(&settings, &repo, &root_commit)
.set_predecessors(vec![original.id().clone()])
.write_to_repo(mut_repo);
assert!(!mut_repo.evolution().is_obsolete(original.id()));
assert!(!mut_repo.evolution().is_orphan(original.id()));
assert!(!mut_repo.evolution().is_obsolete(child.id()));
assert!(!mut_repo.evolution().is_orphan(child.id()));
// A commit with a successor with the same change_id should be obsolete.
let rewritten = child_commit(&settings, &repo, &root_commit)
.set_predecessors(vec![original.id().clone()])
.set_change_id(original.change_id().clone())
.write_to_repo(mut_repo);
assert!(mut_repo.evolution().is_obsolete(original.id()));
assert!(!mut_repo.evolution().is_obsolete(child.id()));
assert!(mut_repo.evolution().is_orphan(child.id()));
assert!(mut_repo.evolution().is_orphan(grandchild.id()));
assert!(!mut_repo.evolution().is_obsolete(cherry_picked.id()));
assert!(!mut_repo.evolution().is_orphan(cherry_picked.id()));
assert!(!mut_repo.evolution().is_obsolete(rewritten.id()));
assert!(!mut_repo.evolution().is_orphan(rewritten.id()));
// It should no longer be obsolete if we remove the successor.
mut_repo.remove_head(rewritten.id());
assert!(!mut_repo.evolution().is_obsolete(original.id()));
assert!(!mut_repo.evolution().is_orphan(child.id()));
assert!(!mut_repo.evolution().is_orphan(grandchild.id()));
tx.discard();
}
#[test_case(false ; "local backend")]
#[test_case(true ; "git backend")]
fn test_divergent(use_git: bool) {
let settings = testutils::user_settings();
let (_temp_dir, repo) = testutils::init_repo(&settings, use_git);
let root_commit = repo.store().root_commit();
let mut tx = repo.start_transaction("test");
let mut_repo = tx.mut_repo();
// A single commit should not be divergent
let original = child_commit(&settings, &repo, &root_commit).write_to_repo(mut_repo);
assert!(!mut_repo.evolution().is_divergent(original.change_id()));
// Commits with the same change id are divergent, including the original commit
// (it's the change that's divergent)
child_commit(&settings, &repo, &root_commit)
.set_predecessors(vec![original.id().clone()])
.set_change_id(original.change_id().clone())
.write_to_repo(mut_repo);
child_commit(&settings, &repo, &root_commit)
.set_predecessors(vec![original.id().clone()])
.set_change_id(original.change_id().clone())
.write_to_repo(mut_repo);
assert!(mut_repo.evolution().is_divergent(original.change_id()));
tx.discard();
}
#[test_case(false ; "local backend")]
#[test_case(true ; "git backend")]
fn test_divergent_duplicate(use_git: bool) {
let settings = testutils::user_settings();
let (_temp_dir, repo) = testutils::init_repo(&settings, use_git);
let root_commit = repo.store().root_commit();
let mut tx = repo.start_transaction("test");
let mut_repo = tx.mut_repo();
// Successors with different change id are not divergent
let original = child_commit(&settings, &repo, &root_commit).write_to_repo(mut_repo);
let cherry_picked1 = child_commit(&settings, &repo, &root_commit)
.set_predecessors(vec![original.id().clone()])
.write_to_repo(mut_repo);
let cherry_picked2 = child_commit(&settings, &repo, &root_commit)
.set_predecessors(vec![original.id().clone()])
.write_to_repo(mut_repo);
assert!(!mut_repo.evolution().is_divergent(original.change_id()));
assert!(!mut_repo
.evolution()
.is_divergent(cherry_picked1.change_id()));
assert!(!mut_repo
.evolution()
.is_divergent(cherry_picked2.change_id()));
tx.discard();
}

View file

@ -335,7 +335,6 @@ fn test_add_head_not_immediate_child(use_git: bool) {
assert!(mut_repo.index().has_id(initial.id()));
assert!(mut_repo.index().has_id(rewritten.id()));
assert!(mut_repo.index().has_id(child.id()));
assert!(mut_repo.evolution().is_obsolete(initial.id()));
let index_stats = mut_repo.index().stats();
assert_eq!(index_stats.num_heads, 3);
assert_eq!(index_stats.num_commits, 5);

View file

@ -147,9 +147,6 @@ fn test_isolation(use_git: bool) {
assert_heads(repo.as_repo_ref(), vec![&wc_id, initial.id()]);
assert_heads(mut_repo1.as_repo_ref(), vec![&wc_id, initial.id()]);
assert_heads(mut_repo2.as_repo_ref(), vec![&wc_id, initial.id()]);
assert!(!repo.evolution().is_obsolete(initial.id()));
assert!(!mut_repo1.evolution().is_obsolete(initial.id()));
assert!(!mut_repo2.evolution().is_obsolete(initial.id()));
let rewrite1 = CommitBuilder::for_rewrite_from(&settings, repo.store(), &initial)
.set_description("rewrite1".to_string())
@ -169,9 +166,6 @@ fn test_isolation(use_git: bool) {
mut_repo2.as_repo_ref(),
vec![&wc_id, initial.id(), rewrite2.id()],
);
assert!(!repo.evolution().is_obsolete(initial.id()));
assert!(mut_repo1.evolution().is_obsolete(initial.id()));
assert!(mut_repo2.evolution().is_obsolete(initial.id()));
// The base repo and tx2 don't see the commits from tx1.
tx1.commit();

View file

@ -294,7 +294,7 @@ pub struct DivergentProperty {
impl DivergentProperty {
pub fn new(repo: RepoRef) -> Self {
// TODO: Create a persistent index from change id to commit ids.
// TODO: Create a persistent index from change id to commit ids.
let mut commit_count_by_change: HashMap<ChangeId, i32> = HashMap::new();
for index_entry in RevsetExpression::all().evaluate(repo).unwrap().iter() {
let change_id = index_entry.change_id();