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:
parent
82388c4d88
commit
4c4e436f38
8 changed files with 11 additions and 1029 deletions
|
@ -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)
|
||||
);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
|
@ -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);
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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();
|
||||
|
|
Loading…
Reference in a new issue