1use 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#[derive(Debug, Clone, Default)]
34struct State {
35 children: HashMap<CommitId, HashSet<CommitId>>,
36 successors: HashMap<CommitId, HashSet<CommitId>>,
38 obsolete_commits: HashSet<CommitId>,
41 pruned_commits: HashSet<CommitId>,
42 orphan_commits: HashSet<CommitId>,
43 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 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 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 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 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 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 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 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 let mut candidates = HashSet::new();
260 candidates.insert(successor.clone());
261
262 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 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 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 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 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 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 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 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 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 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 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 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 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 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}