jujube_lib/
evolution.rs

1// Copyright 2020 Google LLC
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// https://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use std::collections::{HashMap, HashSet};
16use std::sync::{Arc, Mutex};
17
18use crate::commit::Commit;
19use crate::commit_builder::CommitBuilder;
20use crate::dag_walk::{bfs, closest_common_node, leaves, walk_ancestors};
21use crate::repo::{ReadonlyRepo, Repo};
22use crate::repo_path::DirRepoPath;
23use crate::rewrite::{merge_commit_trees, rebase_commit};
24use crate::settings::UserSettings;
25use crate::store::{ChangeId, CommitId};
26use crate::store_wrapper::StoreWrapper;
27use crate::transaction::{MutableRepo, Transaction};
28use crate::trees::merge_trees;
29use crate::view::View;
30
31// TODO: Combine some maps/sets and use a struct as value instead.
32// TODO: Move some of this into the index?
33#[derive(Debug, Clone, Default)]
34struct State {
35    children: HashMap<CommitId, HashSet<CommitId>>,
36    /// Contains all successors whether they have the same change id or not.
37    successors: HashMap<CommitId, HashSet<CommitId>>,
38    /// Contains the subset of the keys in `successors` for which there is a
39    /// successor with the same change id.
40    obsolete_commits: HashSet<CommitId>,
41    pruned_commits: HashSet<CommitId>,
42    orphan_commits: HashSet<CommitId>,
43    /// If there's more than one element in the value, then the change is
44    /// divergent.
45    non_obsoletes_by_changeid: HashMap<ChangeId, HashSet<CommitId>>,
46}
47
48impl State {
49    fn calculate(store: &StoreWrapper, view: &dyn View) -> State {
50        let mut state = State::default();
51        let mut heads = vec![];
52        for commit_id in view.heads() {
53            heads.push(store.get_commit(commit_id).unwrap());
54        }
55        let mut commits = HashSet::new();
56        let mut change_to_commits = HashMap::new();
57        for commit in walk_ancestors(heads) {
58            state.children.insert(commit.id().clone(), HashSet::new());
59            change_to_commits
60                .entry(commit.change_id().clone())
61                .or_insert_with(HashSet::new)
62                .insert(commit.id().clone());
63            commits.insert(commit);
64        }
65        // Scan all commits to find obsolete commits and to build a lookup for
66        // children of a commit
67        for commit in &commits {
68            if commit.is_pruned() {
69                state.pruned_commits.insert(commit.id().clone());
70            }
71            for predecessor in commit.predecessors() {
72                if !commits.contains(&predecessor) {
73                    continue;
74                }
75                state
76                    .successors
77                    .entry(predecessor.id().clone())
78                    .or_insert_with(HashSet::new)
79                    .insert(commit.id().clone());
80                if predecessor.change_id() == commit.change_id() {
81                    state.obsolete_commits.insert(predecessor.id().clone());
82                }
83            }
84            for parent in commit.parents() {
85                if let Some(children) = state.children.get_mut(parent.id()) {
86                    children.insert(commit.id().clone());
87                }
88            }
89        }
90        // Find non-obsolete commits by change id (potentially divergent commits)
91        for (change_id, commit_ids) in change_to_commits {
92            let non_obsoletes: HashSet<CommitId> = commit_ids
93                .difference(&state.obsolete_commits)
94                .cloned()
95                .collect();
96            state
97                .non_obsoletes_by_changeid
98                .insert(change_id, non_obsoletes);
99        }
100        // Find orphans by walking to the children of obsolete commits
101        let mut work: Vec<CommitId> = state.obsolete_commits.iter().cloned().collect();
102        work.extend(state.pruned_commits.iter().cloned());
103        while !work.is_empty() {
104            let commit_id = work.pop().unwrap();
105            for child in state.children.get(&commit_id).unwrap() {
106                if state.orphan_commits.insert(child.clone()) {
107                    work.push(child.clone());
108                }
109            }
110        }
111        state.orphan_commits = state
112            .orphan_commits
113            .iter()
114            .filter(|commit_id| {
115                !(state.obsolete_commits.contains(commit_id)
116                    || state.pruned_commits.contains(commit_id))
117            })
118            .cloned()
119            .collect();
120
121        state
122    }
123
124    fn successors(&self, commit_id: &CommitId) -> HashSet<CommitId> {
125        self.successors
126            .get(commit_id)
127            .cloned()
128            .unwrap_or_else(HashSet::new)
129    }
130
131    fn is_obsolete(&self, commit_id: &CommitId) -> bool {
132        self.obsolete_commits.contains(commit_id)
133    }
134
135    fn is_orphan(&self, commit_id: &CommitId) -> bool {
136        self.orphan_commits.contains(commit_id)
137    }
138
139    fn is_divergent(&self, change_id: &ChangeId) -> bool {
140        self.non_obsoletes_by_changeid
141            .get(change_id)
142            .map_or(false, |non_obsoletes| non_obsoletes.len() > 1)
143    }
144
145    fn add_commit(&mut self, commit: &Commit) {
146        self.add_commit_data(
147            commit.id(),
148            commit.change_id(),
149            &commit.parent_ids(),
150            &commit.predecessor_ids(),
151            commit.is_pruned(),
152        );
153    }
154
155    fn add_commit_data(
156        &mut self,
157        commit_id: &CommitId,
158        change_id: &ChangeId,
159        parents: &[CommitId],
160        predecessors: &[CommitId],
161        is_pruned: bool,
162    ) {
163        // TODO: Error out (or ignore?) if the root id is a predecessor or divergent
164        // (adding the root once should be fine). Perhaps this is not the right
165        // place to do that (we don't know what the root id is here).
166        for parent in parents {
167            self.children
168                .entry(parent.clone())
169                .or_default()
170                .insert(commit_id.clone());
171        }
172        if is_pruned {
173            self.pruned_commits.insert(commit_id.clone());
174        }
175        // Update the non_obsoletes_by_changeid by adding the new commit and removing
176        // the predecessors.
177        self.non_obsoletes_by_changeid
178            .entry(change_id.clone())
179            .or_default()
180            .insert(commit_id.clone());
181        for predecessor in predecessors {
182            self.successors
183                .entry(predecessor.clone())
184                .or_default()
185                .insert(commit_id.clone());
186            let became_obsolete = self
187                .non_obsoletes_by_changeid
188                .get_mut(change_id)
189                .unwrap()
190                .remove(predecessor);
191            // Mark descendants as orphans if the predecessor just became obsolete.
192            if became_obsolete {
193                assert!(self.obsolete_commits.insert(predecessor.clone()));
194
195                let mut descendants = HashSet::new();
196                for descendant in bfs(
197                    vec![predecessor.clone()],
198                    Box::new(|commit_id| commit_id.clone()),
199                    Box::new(|commit_id| {
200                        self.children
201                            .get(commit_id)
202                            .cloned()
203                            .unwrap_or_else(HashSet::new)
204                    }),
205                ) {
206                    descendants.insert(descendant);
207                }
208                descendants.remove(predecessor);
209                descendants = descendants
210                    .iter()
211                    .filter(|commit_id| {
212                        !(self.obsolete_commits.contains(commit_id)
213                            || self.pruned_commits.contains(commit_id))
214                    })
215                    .cloned()
216                    .collect();
217                self.orphan_commits.extend(descendants);
218            }
219        }
220        // Mark the new commit an orphan if any of its parents are obsolete, pruned, or
221        // orphans. Note that this has to be done late, in case a parent just got marked
222        // as obsolete or orphan above.
223        let is_orphan = parents.iter().any(|parent| {
224            self.obsolete_commits.contains(parent)
225                || self.pruned_commits.contains(parent)
226                || self.orphan_commits.contains(commit_id)
227        });
228        if is_orphan {
229            self.orphan_commits.insert(commit_id.clone());
230        }
231    }
232
233    pub fn new_parent(&self, store: &StoreWrapper, old_parent_id: &CommitId) -> HashSet<CommitId> {
234        let mut new_parents = HashSet::new();
235        if let Some(successor_ids) = self.successors.get(old_parent_id) {
236            let old_parent = store.get_commit(old_parent_id).unwrap();
237            let successors: HashSet<_> = successor_ids
238                .iter()
239                .map(|id| store.get_commit(id).unwrap())
240                .collect();
241            let mut children = HashMap::new();
242            for successor in &successors {
243                for parent in successor.parents() {
244                    if let Some(parent) = successors.get(&parent) {
245                        children
246                            .entry(parent.clone())
247                            .or_insert_with(HashSet::new)
248                            .insert(successor.clone());
249                    }
250                }
251            }
252            let mut all_candidates = HashSet::new();
253            for successor in &successors {
254                if successor.change_id() != old_parent.change_id() {
255                    continue;
256                }
257
258                // Start with the successor as candidate.
259                let mut candidates = HashSet::new();
260                candidates.insert(successor.clone());
261
262                // If the successor has children that are successors of the same
263                // commit, we consider the original commit to be a split. We then return
264                // the tip-most successor.
265                candidates = leaves(
266                    candidates,
267                    &mut |commit: &Commit| -> HashSet<Commit> {
268                        if let Some(children) = children.get(commit) {
269                            children.clone()
270                        } else {
271                            HashSet::new()
272                        }
273                    },
274                    &|commit: &Commit| -> CommitId { commit.id().clone() },
275                );
276
277                // If a successor is pruned, use its parent(s) instead.
278                candidates = leaves(
279                    candidates,
280                    &mut |commit: &Commit| -> Vec<Commit> {
281                        if commit.is_pruned() {
282                            commit.parents()
283                        } else {
284                            vec![]
285                        }
286                    },
287                    &|commit: &Commit| -> CommitId { commit.id().clone() },
288                );
289
290                for candidate in candidates {
291                    all_candidates.insert(candidate.clone());
292                }
293            }
294
295            // Filter out candidates that are ancestors of or other candidates.
296            let non_heads: Vec<_> = all_candidates
297                .iter()
298                .flat_map(|commit| commit.parents())
299                .collect();
300            for commit in walk_ancestors(non_heads) {
301                all_candidates.remove(&commit);
302            }
303
304            for candidate in all_candidates {
305                // TODO: Make this not recursive
306                for effective_successor in self.new_parent(store, candidate.id()) {
307                    new_parents.insert(effective_successor);
308                }
309            }
310        }
311        if new_parents.is_empty() {
312            // TODO: Should we go to the parents here too if the commit is pruned?
313            new_parents.insert(old_parent_id.clone());
314        }
315        new_parents
316    }
317}
318
319pub trait Evolution {
320    fn successors(&self, commit_id: &CommitId) -> HashSet<CommitId>;
321
322    fn is_obsolete(&self, commit_id: &CommitId) -> bool;
323
324    fn is_orphan(&self, commit_id: &CommitId) -> bool;
325
326    fn is_divergent(&self, change_id: &ChangeId) -> bool;
327
328    /// Given a current parent, finds the new parent candidates. If the current
329    /// parent is not obsolete, then a singleton set of that commit will be
330    /// returned.
331    ///
332    ///  * If a successor is pruned, its parent(s) will instead be included (or
333    ///    their parents if they are also pruned).
334    ///
335    ///  * If the commit has multiple live successors, the tip-most one(s) of
336    ///    them will be chosen.
337    ///
338    /// The second case is more complex than it probably seems. For example,
339    /// let's say commit A was split into B, A', and C (where A' has the same
340    /// change id as A). Then C is rebased to somewhere else and becomes C'.
341    /// We will choose that C' as effective successor even though it has a
342    /// different change id and is not a descendant of one that does.
343    fn new_parent(&self, old_parent_id: &CommitId) -> HashSet<CommitId>;
344}
345
346pub struct ReadonlyEvolution<'r> {
347    repo: &'r ReadonlyRepo,
348    state: Mutex<Option<Arc<State>>>,
349}
350
351pub trait EvolveListener {
352    fn orphan_evolved(&mut self, orphan: &Commit, new_commit: &Commit);
353    fn orphan_target_ambiguous(&mut self, orphan: &Commit);
354    fn divergent_resolved(&mut self, divergents: &[Commit], resolved: &Commit);
355    fn divergent_no_common_predecessor(&mut self, commit1: &Commit, commit2: &Commit);
356}
357
358impl Evolution for ReadonlyEvolution<'_> {
359    fn successors(&self, commit_id: &CommitId) -> HashSet<CommitId> {
360        self.get_state().successors(commit_id)
361    }
362
363    fn is_obsolete(&self, commit_id: &CommitId) -> bool {
364        self.get_state().is_obsolete(commit_id)
365    }
366
367    fn is_orphan(&self, commit_id: &CommitId) -> bool {
368        self.get_state().is_orphan(commit_id)
369    }
370
371    fn is_divergent(&self, change_id: &ChangeId) -> bool {
372        self.get_state().is_divergent(change_id)
373    }
374
375    fn new_parent(&self, old_parent_id: &CommitId) -> HashSet<CommitId> {
376        self.get_state()
377            .new_parent(self.repo.store(), old_parent_id)
378    }
379}
380
381impl<'r> ReadonlyEvolution<'r> {
382    pub fn new(repo: &'r ReadonlyRepo) -> Self {
383        ReadonlyEvolution {
384            repo,
385            state: Mutex::new(None),
386        }
387    }
388
389    fn get_state(&self) -> Arc<State> {
390        let mut locked_state = self.state.lock().unwrap();
391        if locked_state.is_none() {
392            locked_state.replace(Arc::new(State::calculate(
393                self.repo.store(),
394                self.repo.view(),
395            )));
396        }
397        locked_state.as_ref().unwrap().clone()
398    }
399
400    pub fn start_modification<'m>(&self, repo: &'m MutableRepo<'r>) -> MutableEvolution<'r, 'm> {
401        MutableEvolution {
402            repo,
403            state: self.get_state().as_ref().clone(),
404        }
405    }
406}
407
408pub struct MutableEvolution<'r: 'm, 'm> {
409    repo: &'m MutableRepo<'r>,
410    state: State,
411}
412
413impl Evolution for MutableEvolution<'_, '_> {
414    fn successors(&self, commit_id: &CommitId) -> HashSet<CommitId> {
415        self.state.successors(commit_id)
416    }
417
418    fn is_obsolete(&self, commit_id: &CommitId) -> bool {
419        self.state.is_obsolete(commit_id)
420    }
421
422    fn is_orphan(&self, commit_id: &CommitId) -> bool {
423        self.state.is_orphan(commit_id)
424    }
425
426    fn is_divergent(&self, change_id: &ChangeId) -> bool {
427        self.state.is_divergent(change_id)
428    }
429
430    fn new_parent(&self, old_parent_id: &CommitId) -> HashSet<CommitId> {
431        self.state.new_parent(self.repo.store(), old_parent_id)
432    }
433}
434
435impl MutableEvolution<'_, '_> {
436    pub fn add_commit(&mut self, commit: &Commit) {
437        self.state.add_commit(commit);
438    }
439
440    pub fn invalidate(&mut self) {
441        self.state = State::calculate(self.repo.store(), self.repo.view());
442    }
443}
444
445pub fn evolve(
446    user_settings: &UserSettings,
447    tx: &mut Transaction,
448    listener: &mut dyn EvolveListener,
449) {
450    let store = tx.store().clone();
451
452    // Resolving divergence can creates new orphans but not vice versa, so resolve
453    // divergence first.
454    let divergent_changes: Vec<_> = tx
455        .as_repo_mut()
456        .evolution_mut()
457        .state
458        .non_obsoletes_by_changeid
459        .values()
460        .filter(|non_obsoletes| non_obsoletes.len() > 1)
461        .cloned()
462        .collect();
463    for commit_ids in divergent_changes {
464        let commits: HashSet<Commit> = commit_ids
465            .iter()
466            .map(|id| store.get_commit(&id).unwrap())
467            .collect();
468        evolve_divergent_change(user_settings, &store, tx, listener, &commits);
469    }
470
471    // Dom't reuse the state from above, since the divergence-resolution may have
472    // created new orphans, or resolved existing orphans.
473    let orphans: HashSet<Commit> = tx
474        .as_repo_mut()
475        .evolution_mut()
476        .state
477        .orphan_commits
478        .iter()
479        .map(|id| store.get_commit(&id).unwrap())
480        .collect();
481    let non_heads: HashSet<Commit> = orphans.iter().flat_map(|commit| commit.parents()).collect();
482    let orphan_heads: HashSet<Commit> = orphans.difference(&non_heads).cloned().collect();
483    let mut orphans_topo_order = vec![];
484    for commit in bfs(
485        orphan_heads,
486        Box::new(|commit| commit.id().clone()),
487        Box::new(|commit| {
488            commit
489                .parents()
490                .iter()
491                .filter(|commit| orphans.contains(commit))
492                .cloned()
493                .collect::<Vec<_>>()
494        }),
495    ) {
496        orphans_topo_order.push(commit);
497    }
498
499    while !orphans_topo_order.is_empty() {
500        let orphan = orphans_topo_order.pop().unwrap();
501        let old_parents = orphan.parents();
502        let mut new_parents = vec![];
503        let mut ambiguous_new_parents = false;
504        let evolution = tx.as_repo_mut().evolution();
505        for old_parent in &old_parents {
506            let new_parent_candidates = evolution.new_parent(old_parent.id());
507            if new_parent_candidates.len() > 1 {
508                ambiguous_new_parents = true;
509                break;
510            }
511            new_parents.push(
512                store
513                    .get_commit(new_parent_candidates.iter().next().unwrap())
514                    .unwrap(),
515            );
516        }
517        if ambiguous_new_parents {
518            listener.orphan_target_ambiguous(&orphan);
519        } else {
520            let new_commit = rebase_commit(user_settings, tx, &orphan, &new_parents);
521            listener.orphan_evolved(&orphan, &new_commit);
522        }
523    }
524}
525
526fn evolve_divergent_change(
527    user_settings: &UserSettings,
528    store: &Arc<StoreWrapper>,
529    tx: &mut Transaction,
530    listener: &mut dyn EvolveListener,
531    commits: &HashSet<Commit>,
532) {
533    // Resolve divergence pair-wise, starting with the two oldest commits.
534    let mut commits: Vec<Commit> = commits.iter().cloned().collect();
535    commits.sort_by(|a: &Commit, b: &Commit| a.committer().timestamp.cmp(&b.committer().timestamp));
536    commits.reverse();
537
538    // Create a copy to pass to the listener
539    let sources = commits.clone();
540
541    while commits.len() > 1 {
542        let commit2 = commits.pop().unwrap();
543        let commit1 = commits.pop().unwrap();
544
545        let common_predecessor = closest_common_node(
546            vec![commit1.clone()],
547            vec![commit2.clone()],
548            &|commit: &Commit| commit.predecessors(),
549            &|commit: &Commit| commit.id().clone(),
550        );
551        match common_predecessor {
552            None => {
553                listener.divergent_no_common_predecessor(&commit1, &commit2);
554                return;
555            }
556            Some(common_predecessor) => {
557                let resolved_commit = evolve_two_divergent_commits(
558                    user_settings,
559                    store,
560                    tx,
561                    &common_predecessor,
562                    &commit1,
563                    &commit2,
564                );
565                commits.push(resolved_commit);
566            }
567        }
568    }
569
570    let resolved = commits.pop().unwrap();
571    listener.divergent_resolved(&sources, &resolved);
572}
573
574fn evolve_two_divergent_commits(
575    user_settings: &UserSettings,
576    store: &Arc<StoreWrapper>,
577    tx: &mut Transaction,
578    common_predecessor: &Commit,
579    commit1: &Commit,
580    commit2: &Commit,
581) -> Commit {
582    let new_parents = commit1.parents();
583    let rebased_tree2 = if commit2.parents() == new_parents {
584        commit2.tree()
585    } else {
586        let old_base_tree = merge_commit_trees(store, &commit2.parents());
587        let new_base_tree = merge_commit_trees(store, &new_parents);
588        let tree_id = merge_trees(&new_base_tree, &old_base_tree, &commit2.tree()).unwrap();
589        store.get_tree(&DirRepoPath::root(), &tree_id).unwrap()
590    };
591    let rebased_predecessor_tree = if common_predecessor.parents() == new_parents {
592        common_predecessor.tree()
593    } else {
594        let old_base_tree = merge_commit_trees(store, &common_predecessor.parents());
595        let new_base_tree = merge_commit_trees(store, &new_parents);
596        let tree_id =
597            merge_trees(&new_base_tree, &old_base_tree, &common_predecessor.tree()).unwrap();
598        store.get_tree(&DirRepoPath::root(), &tree_id).unwrap()
599    };
600
601    let resolved_tree =
602        merge_trees(&commit1.tree(), &rebased_predecessor_tree, &rebased_tree2).unwrap();
603
604    // TODO: Merge commit description and other commit metadata. How do we deal with
605    // conflicts? It's probably best to interactively ask the caller (which
606    // might ask the user in interactive use).
607    CommitBuilder::for_rewrite_from(user_settings, store, &commit1)
608        .set_tree(resolved_tree)
609        .set_predecessors(vec![commit1.id().clone(), commit2.id().clone()])
610        .write_to_transaction(tx)
611}
612
613#[cfg(test)]
614mod tests {
615    use super::*;
616
617    #[test]
618    fn add_commit_data_initial() {
619        let mut state = State::default();
620
621        let initial_commit = CommitId::from_hex("aaa111");
622        let initial_change = ChangeId::from_hex("aaa111");
623
624        state.add_commit_data(&initial_commit, &initial_change, &[], &[], false);
625        assert!(!state.is_obsolete(&initial_commit));
626        assert!(!state.is_orphan(&initial_commit));
627        assert!(!state.is_divergent(&initial_change));
628    }
629
630    #[test]
631    fn add_commit_data_pruned() {
632        let mut state = State::default();
633
634        let initial_commit = CommitId::from_hex("aaa111");
635        let initial_change = ChangeId::from_hex("aaa111");
636
637        state.add_commit_data(&initial_commit, &initial_change, &[], &[], true);
638        assert!(!state.is_obsolete(&initial_commit));
639        assert!(!state.is_orphan(&initial_commit));
640        assert!(!state.is_divergent(&initial_change));
641    }
642
643    #[test]
644    fn add_commit_data_creating_orphan() {
645        let mut state = State::default();
646
647        let initial_commit = CommitId::from_hex("aaa111");
648        let initial_change = ChangeId::from_hex("aaa111");
649        let orphan_commit1 = CommitId::from_hex("bbb111");
650        let orphan_change1 = ChangeId::from_hex("bbb111");
651        let orphan_commit2 = CommitId::from_hex("ccc111");
652        let orphan_change2 = ChangeId::from_hex("ccc111");
653        let obsolete_orphan_commit = CommitId::from_hex("ddd111");
654        let obsolete_orphan_change = ChangeId::from_hex("ddd111");
655        let pruned_orphan_commit = CommitId::from_hex("eee111");
656        let rewritten_commit = CommitId::from_hex("aaa222");
657
658        state.add_commit_data(&initial_commit, &initial_change, &[], &[], false);
659        state.add_commit_data(
660            &orphan_commit1,
661            &orphan_change1,
662            &[initial_commit.clone()],
663            &[],
664            false,
665        );
666        state.add_commit_data(
667            &orphan_commit2,
668            &orphan_change2,
669            &[orphan_commit1.clone()],
670            &[],
671            false,
672        );
673        state.add_commit_data(
674            &obsolete_orphan_commit,
675            &obsolete_orphan_change,
676            &[initial_commit.clone()],
677            &[],
678            false,
679        );
680        state.add_commit_data(
681            &pruned_orphan_commit,
682            &obsolete_orphan_change,
683            &[initial_commit.clone()],
684            &[obsolete_orphan_commit.clone()],
685            true,
686        );
687        state.add_commit_data(
688            &rewritten_commit,
689            &initial_change,
690            &[],
691            &[initial_commit.clone()],
692            false,
693        );
694        assert!(state.is_orphan(&orphan_commit1));
695        assert!(state.is_orphan(&orphan_commit2));
696        assert!(!state.is_orphan(&obsolete_orphan_commit));
697        assert!(!state.is_orphan(&pruned_orphan_commit));
698        assert!(!state.is_obsolete(&orphan_commit1));
699        assert!(!state.is_obsolete(&orphan_commit2));
700        assert!(state.is_obsolete(&obsolete_orphan_commit));
701        assert!(!state.is_obsolete(&pruned_orphan_commit));
702    }
703
704    #[test]
705    fn add_commit_data_new_commit_on_obsolete() {
706        let mut state = State::default();
707
708        let initial_commit = CommitId::from_hex("aaa111");
709        let initial_change = ChangeId::from_hex("aaa111");
710        let rewritten_commit = CommitId::from_hex("aaa222");
711        let new_commit = CommitId::from_hex("bbb111");
712        let new_change = ChangeId::from_hex("bbb111");
713
714        state.add_commit_data(&initial_commit, &initial_change, &[], &[], false);
715        state.add_commit_data(
716            &rewritten_commit,
717            &initial_change,
718            &[],
719            &[initial_commit.clone()],
720            false,
721        );
722        state.add_commit_data(
723            &new_commit,
724            &new_change,
725            &[initial_commit.clone()],
726            &[],
727            false,
728        );
729        assert!(state.is_orphan(&new_commit));
730    }
731
732    #[test]
733    fn add_commit_data_new_commit_on_orphan() {
734        let mut state = State::default();
735
736        let initial_commit = CommitId::from_hex("aaa111");
737        let initial_change = ChangeId::from_hex("aaa111");
738        let rewritten_commit = CommitId::from_hex("aaa222");
739        let orphan_commit = CommitId::from_hex("bbb111");
740        let orphan_change = ChangeId::from_hex("bbb111");
741        let new_commit = CommitId::from_hex("bbb111");
742        let new_change = ChangeId::from_hex("bbb111");
743
744        state.add_commit_data(&initial_commit, &initial_change, &[], &[], false);
745        state.add_commit_data(
746            &rewritten_commit,
747            &initial_change,
748            &[],
749            &[initial_commit.clone()],
750            false,
751        );
752        state.add_commit_data(
753            &orphan_commit,
754            &orphan_change,
755            &[initial_commit.clone()],
756            &[],
757            false,
758        );
759        state.add_commit_data(
760            &new_commit,
761            &new_change,
762            &[orphan_commit.clone()],
763            &[],
764            false,
765        );
766        assert!(state.is_orphan(&new_commit));
767    }
768
769    #[test]
770    fn add_commit_data_new_commit_on_pruned() {
771        let mut state = State::default();
772
773        let pruned_commit = CommitId::from_hex("aaa111");
774        let pruned_change = ChangeId::from_hex("aaa111");
775        let new_commit = CommitId::from_hex("bbb111");
776        let new_change = ChangeId::from_hex("bbb111");
777
778        state.add_commit_data(&pruned_commit, &pruned_change, &[], &[], true);
779        state.add_commit_data(
780            &new_commit,
781            &new_change,
782            &[pruned_commit.clone()],
783            &[],
784            false,
785        );
786        assert!(state.is_orphan(&new_commit));
787    }
788
789    #[test]
790    fn add_commit_data_rewrite_as_child() {
791        let mut state = State::default();
792
793        let initial_commit = CommitId::from_hex("aaa111");
794        let initial_change = ChangeId::from_hex("aaa111");
795        let rewritten_commit = CommitId::from_hex("aaa222");
796
797        state.add_commit_data(&initial_commit, &initial_change, &[], &[], false);
798        // The new commit is both a child and a successor of the initial commit
799        state.add_commit_data(
800            &rewritten_commit,
801            &initial_change,
802            &[initial_commit.clone()],
803            &[initial_commit.clone()],
804            false,
805        );
806        assert!(state.is_obsolete(&initial_commit));
807        assert!(!state.is_obsolete(&rewritten_commit));
808        assert!(!state.is_orphan(&initial_commit));
809        assert!(state.is_orphan(&rewritten_commit));
810        assert!(!state.is_divergent(&initial_change));
811    }
812
813    #[test]
814    fn add_commit_data_duplicates() {
815        let mut state = State::default();
816
817        let initial_commit = CommitId::from_hex("aaa111");
818        let initial_change = ChangeId::from_hex("aaa111");
819        let duplicate_commit1 = CommitId::from_hex("bbb111");
820        let duplicate_change1 = ChangeId::from_hex("bbb111");
821        let duplicate_commit2 = CommitId::from_hex("ccc111");
822        let duplicate_change2 = ChangeId::from_hex("ccc111");
823
824        state.add_commit_data(&initial_commit, &initial_change, &[], &[], false);
825        state.add_commit_data(
826            &duplicate_commit1,
827            &duplicate_change1,
828            &[],
829            &[initial_commit.clone()],
830            false,
831        );
832        state.add_commit_data(
833            &duplicate_commit2,
834            &duplicate_change2,
835            &[],
836            &[initial_commit.clone()],
837            false,
838        );
839        assert!(!state.is_obsolete(&initial_commit));
840        assert!(!state.is_obsolete(&duplicate_commit1));
841        assert!(!state.is_obsolete(&duplicate_commit2));
842        assert!(!state.is_divergent(&initial_change));
843        assert!(!state.is_divergent(&duplicate_change1));
844        assert!(!state.is_divergent(&duplicate_change2));
845        assert_eq!(
846            state.successors(&initial_commit),
847            hashset!(duplicate_commit1.clone(), duplicate_commit2.clone())
848        );
849    }
850
851    #[test]
852    fn add_commit_data_divergent() {
853        let mut state = State::default();
854
855        let initial_commit = CommitId::from_hex("aaa111");
856        let initial_change = ChangeId::from_hex("aaa111");
857        let rewritten_commit1 = CommitId::from_hex("aaa222");
858        let rewritten_commit2 = CommitId::from_hex("aaa333");
859
860        state.add_commit_data(&initial_commit, &initial_change, &[], &[], false);
861        state.add_commit_data(
862            &rewritten_commit1,
863            &initial_change,
864            &[],
865            &[initial_commit.clone()],
866            false,
867        );
868        state.add_commit_data(
869            &rewritten_commit2,
870            &initial_change,
871            &[],
872            &[initial_commit.clone()],
873            false,
874        );
875        assert!(state.is_obsolete(&initial_commit));
876        assert!(!state.is_obsolete(&rewritten_commit1));
877        assert!(!state.is_obsolete(&rewritten_commit2));
878        assert!(state.is_divergent(&initial_change));
879        assert_eq!(
880            state.successors(&initial_commit),
881            hashset!(rewritten_commit1.clone(), rewritten_commit2.clone())
882        );
883    }
884
885    #[test]
886    fn add_commit_data_divergent_pruned() {
887        let mut state = State::default();
888
889        let initial_commit = CommitId::from_hex("aaa111");
890        let initial_change = ChangeId::from_hex("aaa111");
891        let rewritten_pruned = CommitId::from_hex("aaa222");
892        let rewritten_non_pruned = CommitId::from_hex("aaa333");
893
894        state.add_commit_data(&initial_commit, &initial_change, &[], &[], false);
895        state.add_commit_data(
896            &rewritten_pruned,
897            &initial_change,
898            &[],
899            &[initial_commit.clone()],
900            true,
901        );
902        state.add_commit_data(
903            &rewritten_non_pruned,
904            &initial_change,
905            &[],
906            &[initial_commit.clone()],
907            false,
908        );
909        assert!(state.is_obsolete(&initial_commit));
910        assert!(!state.is_obsolete(&rewritten_pruned));
911        assert!(!state.is_obsolete(&rewritten_non_pruned));
912        // It's still divergent even if one side is pruned
913        assert!(state.is_divergent(&initial_change));
914        assert_eq!(
915            state.successors(&initial_commit),
916            hashset!(rewritten_pruned.clone(), rewritten_non_pruned.clone())
917        );
918    }
919
920    #[test]
921    fn add_commit_data_divergent_unrelated() {
922        let mut state = State::default();
923
924        let initial_commit = CommitId::from_hex("aaa111");
925        let initial_change = ChangeId::from_hex("aaa111");
926        let rewritten_commit = CommitId::from_hex("aaa222");
927
928        state.add_commit_data(&initial_commit, &initial_change, &[], &[], false);
929        // Same change id as the initial commit but no predecessor relationship to it
930        state.add_commit_data(&rewritten_commit, &initial_change, &[], &[], false);
931        assert!(!state.is_obsolete(&initial_commit));
932        assert!(!state.is_obsolete(&rewritten_commit));
933        assert!(state.is_divergent(&initial_change));
934        assert_eq!(state.successors(&initial_commit), hashset!());
935    }
936
937    #[test]
938    fn add_commit_data_divergent_convergent() {
939        let mut state = State::default();
940
941        let initial_commit = CommitId::from_hex("aaa111");
942        let initial_change = ChangeId::from_hex("aaa111");
943        let rewritten_commit1 = CommitId::from_hex("aaa222");
944        let rewritten_commit2 = CommitId::from_hex("aaa333");
945        let convergent_commit = CommitId::from_hex("aaa444");
946
947        state.add_commit_data(&initial_commit, &initial_change, &[], &[], false);
948        state.add_commit_data(
949            &rewritten_commit1,
950            &initial_change,
951            &[],
952            &[initial_commit.clone()],
953            false,
954        );
955        state.add_commit_data(
956            &rewritten_commit2,
957            &initial_change,
958            &[],
959            &[initial_commit.clone()],
960            false,
961        );
962        state.add_commit_data(
963            &convergent_commit,
964            &initial_change,
965            &[],
966            &[rewritten_commit1.clone(), rewritten_commit2.clone()],
967            false,
968        );
969        assert!(state.is_obsolete(&initial_commit));
970        assert!(state.is_obsolete(&rewritten_commit1));
971        assert!(state.is_obsolete(&rewritten_commit2));
972        assert!(!state.is_obsolete(&convergent_commit));
973        assert!(!state.is_divergent(&initial_change));
974        assert_eq!(
975            state.successors(&rewritten_commit1),
976            hashset!(convergent_commit.clone())
977        );
978        assert_eq!(
979            state.successors(&rewritten_commit2),
980            hashset!(convergent_commit.clone())
981        );
982    }
983}