diff --git a/lib/src/evolution.rs b/lib/src/evolution.rs deleted file mode 100644 index 495ec2558..000000000 --- a/lib/src/evolution.rs +++ /dev/null @@ -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>, - /// Contains all successors whether they have the same change id or not. - successors: HashMap>, - /// Contains the subset of the keys in `successors` for which there is a - /// successor with the same change id. - obsolete_commits: HashSet, - pruned_commits: HashSet, - orphan_commits: HashSet, - /// If there's more than one element in the value, then the change is - /// divergent. - non_obsoletes_by_changeid: HashMap>, -} - -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 = 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 { - 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 { - 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 { - 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 { - 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 { - 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), - Mutable(&'a MutableEvolution), -} - -impl EvolutionRef<'_> { - pub fn successors(&self, commit_id: &CommitId) -> HashSet { - 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 { - 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 { - 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 { - self.state.non_obsoletes(change_id) - } - - pub fn new_parent(&self, repo: RepoRef, old_parent_id: &CommitId) -> HashSet { - 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 { - 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 { - self.state.non_obsoletes(change_id) - } - - pub fn new_parent(&self, repo: RepoRef, old_parent_id: &CommitId) -> HashSet { - 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) - ); - } -} diff --git a/lib/src/lib.rs b/lib/src/lib.rs index 6a641e736..cec7ad577 100644 --- a/lib/src/lib.rs +++ b/lib/src/lib.rs @@ -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; diff --git a/lib/src/repo.rs b/lib/src/repo.rs index 0eb5b2012..470f989b6 100644 --- a/lib/src/repo.rs +++ b/lib/src/repo.rs @@ -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>>, working_copy: Arc>, view: View, - evolution: Mutex>>, } 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 { - 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 { 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, 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>, index: Arc, - evolution: Option>, ) -> Arc { 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, index: MutableIndex, view: View, - evolution: Mutex>, rewritten_commits: HashMap>, abandoned_commits: HashSet, } @@ -531,16 +503,13 @@ impl MutableRepo { base_repo: Arc, index: Arc, view: &View, - evolution: Option<&Arc>, ) -> 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) { - (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( diff --git a/lib/src/transaction.rs b/lib/src/transaction.rs index dc0b6f417..5baf903c6 100644 --- a/lib/src/transaction.rs +++ b/lib/src/transaction.rs @@ -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>, index: Arc, - evolution: Option>, } pub struct UnpublishedOperation { @@ -143,14 +138,12 @@ impl UnpublishedOperation { view: View, working_copy: Arc>, index: Arc, - evolution: Option>, ) -> 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 { 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 } diff --git a/lib/tests/test_evolution.rs b/lib/tests/test_evolution.rs deleted file mode 100644 index 58ba90e9d..000000000 --- a/lib/tests/test_evolution.rs +++ /dev/null @@ -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(); -} diff --git a/lib/tests/test_mut_repo.rs b/lib/tests/test_mut_repo.rs index 8cff133c3..772d19f0e 100644 --- a/lib/tests/test_mut_repo.rs +++ b/lib/tests/test_mut_repo.rs @@ -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); diff --git a/lib/tests/test_operations.rs b/lib/tests/test_operations.rs index ae4147d7c..bbecca51e 100644 --- a/lib/tests/test_operations.rs +++ b/lib/tests/test_operations.rs @@ -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(); diff --git a/src/templater.rs b/src/templater.rs index d5a39155c..c47170c60 100644 --- a/src/templater.rs +++ b/src/templater.rs @@ -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 = HashMap::new(); for index_entry in RevsetExpression::all().evaluate(repo).unwrap().iter() { let change_id = index_entry.change_id();