1#![allow(missing_docs)]
16
17use std::collections::HashMap;
18use std::collections::HashSet;
19use std::slice;
20use std::sync::Arc;
21
22use futures::StreamExt as _;
23use indexmap::IndexMap;
24use indexmap::IndexSet;
25use itertools::Itertools as _;
26use tracing::instrument;
27
28use crate::backend::BackendError;
29use crate::backend::BackendResult;
30use crate::backend::CommitId;
31use crate::backend::MergedTreeId;
32use crate::commit::Commit;
33use crate::commit::CommitIteratorExt as _;
34use crate::commit_builder::CommitBuilder;
35use crate::index::Index;
36use crate::index::IndexError;
37use crate::matchers::Matcher;
38use crate::matchers::Visit;
39use crate::merged_tree::MergedTree;
40use crate::merged_tree::MergedTreeBuilder;
41use crate::merged_tree::TreeDiffEntry;
42use crate::repo::MutableRepo;
43use crate::repo::Repo;
44use crate::repo_path::RepoPath;
45use crate::revset::RevsetExpression;
46use crate::revset::RevsetIteratorExt as _;
47use crate::store::Store;
48
49#[instrument(skip(repo))]
51pub fn merge_commit_trees(repo: &dyn Repo, commits: &[Commit]) -> BackendResult<MergedTree> {
52 if let [commit] = commits {
53 commit.tree()
54 } else {
55 merge_commit_trees_no_resolve_without_repo(repo.store(), repo.index(), commits)?.resolve()
56 }
57}
58
59#[instrument(skip(index))]
61pub fn merge_commit_trees_no_resolve_without_repo(
62 store: &Arc<Store>,
63 index: &dyn Index,
64 commits: &[Commit],
65) -> BackendResult<MergedTree> {
66 if commits.is_empty() {
67 Ok(store.get_root_tree(&store.empty_merged_tree_id())?)
68 } else {
69 let mut new_tree = commits[0].tree()?;
70 let commit_ids = commits
71 .iter()
72 .map(|commit| commit.id().clone())
73 .collect_vec();
74 for (i, other_commit) in commits.iter().enumerate().skip(1) {
75 let ancestor_ids = index.common_ancestors(&commit_ids[0..i], &commit_ids[i..][..1]);
76 let ancestors: Vec<_> = ancestor_ids
77 .iter()
78 .map(|id| store.get_commit(id))
79 .try_collect()?;
80 let ancestor_tree =
81 merge_commit_trees_no_resolve_without_repo(store, index, &ancestors)?;
82 let other_tree = other_commit.tree()?;
83 new_tree = new_tree.merge_no_resolve(&ancestor_tree, &other_tree);
84 }
85 Ok(new_tree)
86 }
87}
88
89pub async fn restore_tree(
91 source: &MergedTree,
92 destination: &MergedTree,
93 matcher: &dyn Matcher,
94) -> BackendResult<MergedTreeId> {
95 if matcher.visit(RepoPath::root()) == Visit::AllRecursively {
96 Ok(source.id())
98 } else {
99 let mut tree_builder = MergedTreeBuilder::new(destination.id().clone());
102 let mut diff_stream = source.diff_stream(destination, matcher);
104 while let Some(TreeDiffEntry {
105 path: repo_path,
106 values,
107 }) = diff_stream.next().await
108 {
109 let (source_value, _destination_value) = values?;
110 tree_builder.set_or_remove(repo_path, source_value);
111 }
112 tree_builder.write_tree(destination.store())
113 }
114}
115
116pub fn rebase_commit(
117 mut_repo: &mut MutableRepo,
118 old_commit: Commit,
119 new_parents: Vec<CommitId>,
120) -> BackendResult<Commit> {
121 let rewriter = CommitRewriter::new(mut_repo, old_commit, new_parents);
122 let builder = rewriter.rebase()?;
123 builder.write()
124}
125
126pub struct CommitRewriter<'repo> {
128 mut_repo: &'repo mut MutableRepo,
129 old_commit: Commit,
130 new_parents: Vec<CommitId>,
131}
132
133impl<'repo> CommitRewriter<'repo> {
134 pub fn new(
136 mut_repo: &'repo mut MutableRepo,
137 old_commit: Commit,
138 new_parents: Vec<CommitId>,
139 ) -> Self {
140 Self {
141 mut_repo,
142 old_commit,
143 new_parents,
144 }
145 }
146
147 pub fn mut_repo(&mut self) -> &mut MutableRepo {
149 self.mut_repo
150 }
151
152 pub fn old_commit(&self) -> &Commit {
154 &self.old_commit
155 }
156
157 pub fn new_parents(&self) -> &[CommitId] {
159 &self.new_parents
160 }
161
162 pub fn set_new_parents(&mut self, new_parents: Vec<CommitId>) {
164 self.new_parents = new_parents;
165 }
166
167 pub fn set_new_rewritten_parents(&mut self, unrewritten_parents: &[CommitId]) {
170 self.new_parents = self.mut_repo.new_parents(unrewritten_parents);
171 }
172
173 pub fn replace_parent<'a>(
176 &mut self,
177 old_parent: &CommitId,
178 new_parents: impl IntoIterator<Item = &'a CommitId>,
179 ) {
180 if let Some(i) = self.new_parents.iter().position(|p| p == old_parent) {
181 self.new_parents
182 .splice(i..i + 1, new_parents.into_iter().cloned());
183 let mut unique = HashSet::new();
184 self.new_parents.retain(|p| unique.insert(p.clone()));
185 }
186 }
187
188 pub fn parents_changed(&self) -> bool {
191 self.new_parents != self.old_commit.parent_ids()
192 }
193
194 pub fn simplify_ancestor_merge(&mut self) -> Result<(), IndexError> {
197 let head_set: HashSet<_> = self
198 .mut_repo
199 .index()
200 .heads(&mut self.new_parents.iter())?
201 .into_iter()
202 .collect();
203 self.new_parents.retain(|parent| head_set.contains(parent));
204 Ok(())
205 }
206
207 pub fn abandon(self) {
211 let old_commit_id = self.old_commit.id().clone();
212 let new_parents = self.new_parents;
213 self.mut_repo
214 .record_abandoned_commit_with_parents(old_commit_id, new_parents);
215 }
216
217 pub fn rebase_with_empty_behavior(
220 self,
221 empty: EmptyBehaviour,
222 ) -> BackendResult<Option<CommitBuilder<'repo>>> {
223 let old_parents: Vec<_> = self.old_commit.parents().try_collect()?;
224 let old_parent_trees = old_parents
225 .iter()
226 .map(|parent| parent.tree_id().clone())
227 .collect_vec();
228 let new_parents: Vec<_> = self
229 .new_parents
230 .iter()
231 .map(|new_parent_id| self.mut_repo.store().get_commit(new_parent_id))
232 .try_collect()?;
233 let new_parent_trees = new_parents
234 .iter()
235 .map(|parent| parent.tree_id().clone())
236 .collect_vec();
237
238 let (was_empty, new_tree_id) = if new_parent_trees == old_parent_trees {
239 (
240 true,
243 self.old_commit.tree_id().clone(),
245 )
246 } else {
247 let old_base_tree = merge_commit_trees(self.mut_repo, &old_parents)?;
248 let new_base_tree = merge_commit_trees(self.mut_repo, &new_parents)?;
249 let old_tree = self.old_commit.tree()?;
250 (
251 old_base_tree.id() == *self.old_commit.tree_id(),
252 new_base_tree.merge(&old_base_tree, &old_tree)?.id(),
253 )
254 };
255 if let [parent] = &new_parents[..] {
258 let should_abandon = match empty {
259 EmptyBehaviour::Keep => false,
260 EmptyBehaviour::AbandonNewlyEmpty => *parent.tree_id() == new_tree_id && !was_empty,
261 EmptyBehaviour::AbandonAllEmpty => *parent.tree_id() == new_tree_id,
262 };
263 if should_abandon {
264 self.abandon();
265 return Ok(None);
266 }
267 }
268
269 let builder = self
270 .mut_repo
271 .rewrite_commit(&self.old_commit)
272 .set_parents(self.new_parents)
273 .set_tree_id(new_tree_id);
274 Ok(Some(builder))
275 }
276
277 pub fn rebase(self) -> BackendResult<CommitBuilder<'repo>> {
280 let builder = self.rebase_with_empty_behavior(EmptyBehaviour::Keep)?;
281 Ok(builder.unwrap())
282 }
283
284 pub fn reparent(self) -> CommitBuilder<'repo> {
287 self.mut_repo
288 .rewrite_commit(&self.old_commit)
289 .set_parents(self.new_parents)
290 }
291}
292
293pub enum RebasedCommit {
294 Rewritten(Commit),
295 Abandoned { parent_id: CommitId },
296}
297
298pub fn rebase_commit_with_options(
299 mut rewriter: CommitRewriter<'_>,
300 options: &RebaseOptions,
301) -> BackendResult<RebasedCommit> {
302 if options.simplify_ancestor_merge {
304 rewriter
307 .simplify_ancestor_merge()
308 .map_err(|err| BackendError::Other(err.into()))?;
309 }
310
311 let single_parent = match &rewriter.new_parents[..] {
312 [parent_id] => Some(parent_id.clone()),
313 _ => None,
314 };
315 let new_parents_len = rewriter.new_parents.len();
316 if let Some(builder) = rewriter.rebase_with_empty_behavior(options.empty)? {
317 let new_commit = builder.write()?;
318 Ok(RebasedCommit::Rewritten(new_commit))
319 } else {
320 assert_eq!(new_parents_len, 1);
321 Ok(RebasedCommit::Abandoned {
322 parent_id: single_parent.unwrap(),
323 })
324 }
325}
326
327pub fn rebase_to_dest_parent(
329 repo: &dyn Repo,
330 sources: &[Commit],
331 destination: &Commit,
332) -> BackendResult<MergedTree> {
333 if let [source] = sources {
334 if source.parent_ids() == destination.parent_ids() {
335 return source.tree();
336 }
337 }
338 sources.iter().try_fold(
339 destination.parent_tree(repo)?,
340 |destination_tree, source| {
341 let source_parent_tree = source.parent_tree(repo)?;
342 let source_tree = source.tree()?;
343 destination_tree.merge(&source_parent_tree, &source_tree)
344 },
345 )
346}
347
348#[derive(Clone, Copy, Default, PartialEq, Eq, Debug)]
349pub enum EmptyBehaviour {
350 #[default]
352 Keep,
353 AbandonNewlyEmpty,
357 AbandonAllEmpty,
361}
362
363#[derive(Clone, Debug, Default)]
370pub struct RebaseOptions {
371 pub empty: EmptyBehaviour,
372 pub rewrite_refs: RewriteRefsOptions,
373 pub simplify_ancestor_merge: bool,
376}
377
378#[derive(Clone, Debug, Default)]
380pub struct RewriteRefsOptions {
381 pub delete_abandoned_bookmarks: bool,
386}
387
388pub struct MoveCommitsStats {
389 pub num_rebased_targets: u32,
391 pub num_rebased_descendants: u32,
393 pub num_skipped_rebases: u32,
396 pub num_abandoned_empty: u32,
398 pub rebased_commits: HashMap<CommitId, RebasedCommit>,
400}
401
402#[derive(Clone, Debug)]
404pub struct MoveCommitsLocation {
405 pub new_parent_ids: Vec<CommitId>,
406 pub new_child_ids: Vec<CommitId>,
407 pub target: MoveCommitsTarget,
408}
409
410#[derive(Clone, Debug)]
411pub enum MoveCommitsTarget {
412 Commits(Vec<CommitId>),
415 Roots(Vec<CommitId>),
417}
418
419#[derive(Clone, Debug)]
420pub struct ComputedMoveCommits {
421 target_commit_ids: IndexSet<CommitId>,
422 descendants: Vec<Commit>,
423 commit_new_parents_map: HashMap<CommitId, Vec<CommitId>>,
424 to_abandon: HashSet<CommitId>,
425}
426
427impl ComputedMoveCommits {
428 fn empty() -> Self {
429 ComputedMoveCommits {
430 target_commit_ids: IndexSet::new(),
431 descendants: vec![],
432 commit_new_parents_map: HashMap::new(),
433 to_abandon: HashSet::new(),
434 }
435 }
436
437 pub fn record_to_abandon(&mut self, commit_ids: impl IntoIterator<Item = CommitId>) {
444 self.to_abandon.extend(commit_ids);
445 }
446
447 pub fn apply(
448 self,
449 mut_repo: &mut MutableRepo,
450 options: &RebaseOptions,
451 ) -> BackendResult<MoveCommitsStats> {
452 apply_move_commits(mut_repo, self, options)
453 }
454}
455
456pub fn move_commits(
465 mut_repo: &mut MutableRepo,
466 loc: &MoveCommitsLocation,
467 options: &RebaseOptions,
468) -> BackendResult<MoveCommitsStats> {
469 compute_move_commits(mut_repo, loc)?.apply(mut_repo, options)
470}
471
472pub fn compute_move_commits(
473 repo: &MutableRepo,
474 loc: &MoveCommitsLocation,
475) -> BackendResult<ComputedMoveCommits> {
476 let target_commit_ids: IndexSet<CommitId>;
477 let connected_target_commits: Vec<Commit>;
478 let connected_target_commits_internal_parents: HashMap<CommitId, IndexSet<CommitId>>;
479 let target_roots: HashSet<CommitId>;
480
481 match &loc.target {
482 MoveCommitsTarget::Commits(commit_ids) => {
483 if commit_ids.is_empty() {
484 return Ok(ComputedMoveCommits::empty());
485 }
486
487 target_commit_ids = commit_ids.iter().cloned().collect();
488
489 connected_target_commits = RevsetExpression::commits(commit_ids.clone())
490 .connected()
491 .evaluate(repo)
492 .map_err(|err| err.into_backend_error())?
493 .iter()
494 .commits(repo.store())
495 .try_collect()
496 .map_err(|err| err.into_backend_error())?;
497 connected_target_commits_internal_parents =
498 compute_internal_parents_within(&target_commit_ids, &connected_target_commits);
499
500 target_roots = connected_target_commits_internal_parents
501 .iter()
502 .filter(|&(commit_id, parents)| {
503 target_commit_ids.contains(commit_id) && parents.is_empty()
504 })
505 .map(|(commit_id, _)| commit_id.clone())
506 .collect();
507 }
508 MoveCommitsTarget::Roots(root_ids) => {
509 if root_ids.is_empty() {
510 return Ok(ComputedMoveCommits::empty());
511 }
512
513 target_commit_ids = RevsetExpression::commits(root_ids.clone())
514 .descendants()
515 .evaluate(repo)
516 .map_err(|err| err.into_backend_error())?
517 .iter()
518 .try_collect()
519 .map_err(|err| err.into_backend_error())?;
520
521 connected_target_commits = target_commit_ids
522 .iter()
523 .map(|id| repo.store().get_commit(id))
524 .try_collect()?;
525 connected_target_commits_internal_parents = HashMap::new();
528 target_roots = root_ids.iter().cloned().collect();
529 }
530 }
531
532 let mut target_commits_external_parents: HashMap<CommitId, IndexSet<CommitId>> = HashMap::new();
536 for id in target_commit_ids.iter().rev() {
537 let commit = repo.store().get_commit(id)?;
538 let mut new_parents = IndexSet::new();
539 for old_parent in commit.parent_ids() {
540 if let Some(parents) = target_commits_external_parents.get(old_parent) {
541 new_parents.extend(parents.iter().cloned());
542 } else {
543 new_parents.insert(old_parent.clone());
544 }
545 }
546 target_commits_external_parents.insert(commit.id().clone(), new_parents);
547 }
548
549 let new_parent_ids: Vec<_> = loc
553 .new_parent_ids
554 .iter()
555 .flat_map(|parent_id| {
556 if let Some(parent_ids) = target_commits_external_parents.get(parent_id) {
557 parent_ids.iter().cloned().collect_vec()
558 } else {
559 vec![parent_id.clone()]
560 }
561 })
562 .collect();
563
564 let new_children: Vec<_> = if loc
568 .new_child_ids
569 .iter()
570 .any(|id| target_commit_ids.contains(id))
571 {
572 let target_commits_descendants: Vec<_> =
573 RevsetExpression::commits(target_commit_ids.iter().cloned().collect_vec())
574 .union(
575 &RevsetExpression::commits(target_commit_ids.iter().cloned().collect_vec())
576 .children(),
577 )
578 .evaluate(repo)
579 .map_err(|err| err.into_backend_error())?
580 .iter()
581 .commits(repo.store())
582 .try_collect()
583 .map_err(|err| err.into_backend_error())?;
584
585 let mut target_commit_external_descendants: HashMap<CommitId, IndexSet<Commit>> =
588 HashMap::new();
589 for commit in &target_commits_descendants {
592 if !target_commit_external_descendants.contains_key(commit.id()) {
593 let children = if target_commit_ids.contains(commit.id()) {
594 IndexSet::new()
595 } else {
596 IndexSet::from([commit.clone()])
597 };
598 target_commit_external_descendants.insert(commit.id().clone(), children);
599 }
600
601 let children = target_commit_external_descendants
602 .get(commit.id())
603 .unwrap()
604 .iter()
605 .cloned()
606 .collect_vec();
607 for parent_id in commit.parent_ids() {
608 if target_commit_ids.contains(parent_id) {
609 if let Some(target_children) =
610 target_commit_external_descendants.get_mut(parent_id)
611 {
612 target_children.extend(children.iter().cloned());
613 } else {
614 target_commit_external_descendants
615 .insert(parent_id.clone(), children.iter().cloned().collect());
616 }
617 };
618 }
619 }
620
621 let mut new_children = Vec::new();
622 for id in &loc.new_child_ids {
623 if let Some(children) = target_commit_external_descendants.get(id) {
624 new_children.extend(children.iter().cloned());
625 } else {
626 new_children.push(repo.store().get_commit(id)?);
627 }
628 }
629 new_children
630 } else {
631 loc.new_child_ids
632 .iter()
633 .map(|id| repo.store().get_commit(id))
634 .try_collect()?
635 };
636
637 let new_children_parents: HashMap<_, _> = if !new_children.is_empty() {
640 let target_heads = compute_commits_heads(&target_commit_ids, &connected_target_commits);
643
644 new_children
645 .iter()
646 .map(|child_commit| {
647 let mut new_child_parent_ids = IndexSet::new();
648 for old_child_parent_id in child_commit.parent_ids() {
649 let old_child_parent_ids = if let Some(parents) =
651 target_commits_external_parents.get(old_child_parent_id)
652 {
653 parents.iter().collect_vec()
654 } else {
655 vec![old_child_parent_id]
656 };
657
658 for id in old_child_parent_ids {
662 if new_parent_ids.contains(id) {
663 new_child_parent_ids.extend(target_heads.clone());
664 } else {
665 new_child_parent_ids.insert(id.clone());
666 };
667 }
668 }
669
670 new_child_parent_ids.extend(target_heads.clone());
673
674 (
675 child_commit.id().clone(),
676 new_child_parent_ids.into_iter().collect_vec(),
677 )
678 })
679 .collect()
680 } else {
681 HashMap::new()
682 };
683
684 let mut roots = target_roots.iter().cloned().collect_vec();
687 roots.extend(new_children.iter().ids().cloned());
688
689 let descendants = repo.find_descendants_for_rebase(roots.clone())?;
690 let commit_new_parents_map = descendants
691 .iter()
692 .map(|commit| {
693 let commit_id = commit.id();
694 let new_parent_ids =
695
696 if let Some(new_child_parents) = new_children_parents.get(commit_id) {
698 new_child_parents.clone()
699 }
700 else if target_commit_ids.contains(commit_id) {
702 if target_roots.contains(commit_id) {
704 new_parent_ids.clone()
705 }
706 else {
714 let mut new_parents = vec![];
715 for parent_id in commit.parent_ids() {
716 if target_commit_ids.contains(parent_id) {
717 new_parents.push(parent_id.clone());
718 } else if let Some(parents) =
719 connected_target_commits_internal_parents.get(parent_id) {
720 new_parents.extend(parents.iter().cloned());
721 } else if !new_children.iter().any(|new_child| {
722 repo.index().is_ancestor(new_child.id(), parent_id) }) {
723 new_parents.push(parent_id.clone());
724 }
725 }
726 new_parents
727 }
728 }
729 else if commit
732 .parent_ids()
733 .iter()
734 .any(|id| target_commits_external_parents.contains_key(id))
735 {
736 let mut new_parents = vec![];
737 for parent in commit.parent_ids() {
738 if let Some(parents) = target_commits_external_parents.get(parent) {
739 new_parents.extend(parents.iter().cloned());
740 } else {
741 new_parents.push(parent.clone());
742 }
743 }
744 new_parents
745 } else {
746 commit.parent_ids().iter().cloned().collect_vec()
747 };
748 (commit.id().clone(), new_parent_ids)
749 })
750 .collect();
751
752 Ok(ComputedMoveCommits {
753 target_commit_ids,
754 descendants,
755 commit_new_parents_map,
756 to_abandon: HashSet::new(),
757 })
758}
759
760fn apply_move_commits(
761 mut_repo: &mut MutableRepo,
762 commits: ComputedMoveCommits,
763 options: &RebaseOptions,
764) -> BackendResult<MoveCommitsStats> {
765 let mut num_rebased_targets = 0;
766 let mut num_rebased_descendants = 0;
767 let mut num_skipped_rebases = 0;
768 let mut num_abandoned_empty = 0;
769
770 let rebase_descendant_options = &RebaseOptions {
772 empty: EmptyBehaviour::Keep,
773 rewrite_refs: options.rewrite_refs.clone(),
774 simplify_ancestor_merge: options.simplify_ancestor_merge,
775 };
776
777 let mut rebased_commits: HashMap<CommitId, RebasedCommit> = HashMap::new();
778 mut_repo.transform_commits(
779 commits.descendants,
780 &commits.commit_new_parents_map,
781 &options.rewrite_refs,
782 |rewriter| {
783 let old_commit_id = rewriter.old_commit().id().clone();
784 if commits.to_abandon.contains(&old_commit_id) {
785 rewriter.abandon();
786 } else if rewriter.parents_changed() {
787 let is_target_commit = commits.target_commit_ids.contains(&old_commit_id);
788 let rebased_commit = rebase_commit_with_options(
789 rewriter,
790 if is_target_commit {
791 options
792 } else {
793 rebase_descendant_options
794 },
795 )?;
796 if let RebasedCommit::Abandoned { .. } = rebased_commit {
797 num_abandoned_empty += 1;
798 } else if is_target_commit {
799 num_rebased_targets += 1;
800 } else {
801 num_rebased_descendants += 1;
802 }
803 rebased_commits.insert(old_commit_id, rebased_commit);
804 } else {
805 num_skipped_rebases += 1;
806 }
807
808 Ok(())
809 },
810 )?;
811
812 Ok(MoveCommitsStats {
813 num_rebased_targets,
814 num_rebased_descendants,
815 num_skipped_rebases,
816 num_abandoned_empty,
817 rebased_commits,
818 })
819}
820
821#[derive(Default)]
822pub struct DuplicateCommitsStats {
823 pub duplicated_commits: IndexMap<CommitId, Commit>,
825 pub num_rebased: u32,
828}
829
830pub fn duplicate_commits(
848 mut_repo: &mut MutableRepo,
849 target_commit_ids: &[CommitId],
850 target_descriptions: &HashMap<CommitId, String>,
851 parent_commit_ids: &[CommitId],
852 children_commit_ids: &[CommitId],
853) -> BackendResult<DuplicateCommitsStats> {
854 if target_commit_ids.is_empty() {
855 return Ok(DuplicateCommitsStats::default());
856 }
857
858 let mut duplicated_old_to_new: IndexMap<CommitId, Commit> = IndexMap::new();
859 let mut num_rebased = 0;
860
861 let target_commit_ids: IndexSet<_> = target_commit_ids.iter().cloned().collect();
862
863 let connected_target_commits: Vec<_> =
864 RevsetExpression::commits(target_commit_ids.iter().cloned().collect_vec())
865 .connected()
866 .evaluate(mut_repo)
867 .map_err(|err| err.into_backend_error())?
868 .iter()
869 .commits(mut_repo.store())
870 .try_collect()
871 .map_err(|err| err.into_backend_error())?;
872
873 let target_commits_internal_parents = {
880 let mut target_commits_internal_parents =
881 compute_internal_parents_within(&target_commit_ids, &connected_target_commits);
882 target_commits_internal_parents.retain(|id, _| target_commit_ids.contains(id));
883 target_commits_internal_parents
884 };
885
886 let target_root_ids: HashSet<_> = target_commits_internal_parents
888 .iter()
889 .filter(|(_, parents)| parents.is_empty())
890 .map(|(commit_id, _)| commit_id.clone())
891 .collect();
892
893 let target_head_ids = if !children_commit_ids.is_empty() {
896 compute_commits_heads(&target_commit_ids, &connected_target_commits)
897 } else {
898 vec![]
899 };
900
901 for original_commit_id in target_commit_ids.iter().rev() {
904 let original_commit = mut_repo.store().get_commit(original_commit_id)?;
905 let new_parent_ids = if target_root_ids.contains(original_commit_id) {
906 parent_commit_ids.to_vec()
907 } else {
908 target_commits_internal_parents
909 .get(original_commit_id)
910 .unwrap()
911 .iter()
912 .map(|id| {
914 duplicated_old_to_new
915 .get(id)
916 .map_or(id, |commit| commit.id())
917 .clone()
918 })
919 .collect()
920 };
921 let mut new_commit_builder = CommitRewriter::new(mut_repo, original_commit, new_parent_ids)
922 .rebase()?
923 .generate_new_change_id();
924 if let Some(desc) = target_descriptions.get(original_commit_id) {
925 new_commit_builder = new_commit_builder.set_description(desc);
926 }
927 duplicated_old_to_new.insert(original_commit_id.clone(), new_commit_builder.write()?);
928 }
929
930 let target_head_ids = target_head_ids
933 .into_iter()
934 .map(|commit_id| {
935 duplicated_old_to_new
936 .get(&commit_id)
937 .map_or(commit_id, |commit| commit.id().clone())
938 })
939 .collect_vec();
940
941 let children_commit_ids_set: HashSet<CommitId> = children_commit_ids.iter().cloned().collect();
943 mut_repo.transform_descendants(children_commit_ids.to_vec(), |mut rewriter| {
944 if children_commit_ids_set.contains(rewriter.old_commit().id()) {
945 let mut child_new_parent_ids = IndexSet::new();
946 for old_parent_id in rewriter.old_commit().parent_ids() {
947 if parent_commit_ids.contains(old_parent_id) {
952 child_new_parent_ids.extend(target_head_ids.clone());
953 } else {
954 child_new_parent_ids.insert(old_parent_id.clone());
955 }
956 }
957 child_new_parent_ids.extend(target_head_ids.clone());
960 rewriter.set_new_parents(child_new_parent_ids.into_iter().collect());
961 }
962 num_rebased += 1;
963 rewriter.rebase()?.write()?;
964 Ok(())
965 })?;
966
967 Ok(DuplicateCommitsStats {
968 duplicated_commits: duplicated_old_to_new,
969 num_rebased,
970 })
971}
972
973pub fn duplicate_commits_onto_parents(
983 mut_repo: &mut MutableRepo,
984 target_commits: &[CommitId],
985 target_descriptions: &HashMap<CommitId, String>,
986) -> BackendResult<DuplicateCommitsStats> {
987 if target_commits.is_empty() {
988 return Ok(DuplicateCommitsStats::default());
989 }
990
991 let mut duplicated_old_to_new: IndexMap<CommitId, Commit> = IndexMap::new();
992
993 for original_commit_id in target_commits.iter().rev() {
996 let original_commit = mut_repo.store().get_commit(original_commit_id)?;
997 let new_parent_ids = original_commit
998 .parent_ids()
999 .iter()
1000 .map(|id| {
1001 duplicated_old_to_new
1002 .get(id)
1003 .map_or(id, |commit| commit.id())
1004 .clone()
1005 })
1006 .collect();
1007 let mut new_commit_builder = mut_repo
1008 .rewrite_commit(&original_commit)
1009 .generate_new_change_id()
1010 .set_parents(new_parent_ids);
1011 if let Some(desc) = target_descriptions.get(original_commit_id) {
1012 new_commit_builder = new_commit_builder.set_description(desc);
1013 }
1014 duplicated_old_to_new.insert(original_commit_id.clone(), new_commit_builder.write()?);
1015 }
1016
1017 Ok(DuplicateCommitsStats {
1018 duplicated_commits: duplicated_old_to_new,
1019 num_rebased: 0,
1020 })
1021}
1022
1023fn compute_internal_parents_within(
1031 target_commit_ids: &IndexSet<CommitId>,
1032 graph_commits: &[Commit],
1033) -> HashMap<CommitId, IndexSet<CommitId>> {
1034 let mut internal_parents: HashMap<CommitId, IndexSet<CommitId>> = HashMap::new();
1035 for commit in graph_commits.iter().rev() {
1036 let mut new_parents = IndexSet::new();
1039 for old_parent in commit.parent_ids() {
1040 if target_commit_ids.contains(old_parent) {
1041 new_parents.insert(old_parent.clone());
1042 } else if let Some(parents) = internal_parents.get(old_parent) {
1043 new_parents.extend(parents.iter().cloned());
1044 }
1045 }
1046 internal_parents.insert(commit.id().clone(), new_parents);
1047 }
1048 internal_parents
1049}
1050
1051fn compute_commits_heads(
1057 target_commit_ids: &IndexSet<CommitId>,
1058 connected_target_commits: &[Commit],
1059) -> Vec<CommitId> {
1060 let mut target_head_ids: HashSet<CommitId> = HashSet::new();
1061 for commit in connected_target_commits.iter().rev() {
1062 target_head_ids.insert(commit.id().clone());
1063 for old_parent in commit.parent_ids() {
1064 target_head_ids.remove(old_parent);
1065 }
1066 }
1067 connected_target_commits
1068 .iter()
1069 .rev()
1070 .filter(|commit| {
1071 target_head_ids.contains(commit.id()) && target_commit_ids.contains(commit.id())
1072 })
1073 .map(|commit| commit.id().clone())
1074 .collect_vec()
1075}
1076
1077pub struct CommitWithSelection {
1078 pub commit: Commit,
1079 pub selected_tree: MergedTree,
1080 pub parent_tree: MergedTree,
1081}
1082
1083impl CommitWithSelection {
1084 pub fn is_full_selection(&self) -> bool {
1086 &self.selected_tree.id() == self.commit.tree_id()
1087 }
1088
1089 pub fn is_empty_selection(&self) -> bool {
1095 self.selected_tree.id() == self.parent_tree.id()
1096 }
1097}
1098
1099#[must_use]
1101pub struct SquashedCommit<'repo> {
1102 pub commit_builder: CommitBuilder<'repo>,
1104 pub abandoned_commits: Vec<Commit>,
1106}
1107
1108pub fn squash_commits<'repo>(
1112 repo: &'repo mut MutableRepo,
1113 sources: &[CommitWithSelection],
1114 destination: &Commit,
1115 keep_emptied: bool,
1116) -> BackendResult<Option<SquashedCommit<'repo>>> {
1117 struct SourceCommit<'a> {
1118 commit: &'a CommitWithSelection,
1119 abandon: bool,
1120 }
1121 let mut source_commits = vec![];
1122 for source in sources {
1123 let abandon = !keep_emptied && source.is_full_selection();
1124 if !abandon && source.is_empty_selection() {
1125 continue;
1129 }
1130
1131 source_commits.push(SourceCommit {
1134 commit: source,
1135 abandon,
1136 });
1137 }
1138
1139 if source_commits.is_empty() {
1140 return Ok(None);
1141 }
1142
1143 let mut abandoned_commits = vec![];
1144 for source in &source_commits {
1145 if source.abandon {
1146 repo.record_abandoned_commit(&source.commit.commit);
1147 abandoned_commits.push(source.commit.commit.clone());
1148 } else {
1149 let source_tree = source.commit.commit.tree()?;
1150 let new_source_tree =
1152 source_tree.merge(&source.commit.selected_tree, &source.commit.parent_tree)?;
1153 repo.rewrite_commit(&source.commit.commit)
1154 .set_tree_id(new_source_tree.id().clone())
1155 .write()?;
1156 }
1157 }
1158
1159 let mut rewritten_destination = destination.clone();
1160 if sources.iter().any(|source| {
1161 repo.index()
1162 .is_ancestor(source.commit.id(), destination.id())
1163 }) {
1164 let options = RebaseOptions::default();
1169 repo.rebase_descendants_with_options(&options, |old_commit, rebased_commit| {
1170 if old_commit.id() != destination.id() {
1171 return;
1172 }
1173 rewritten_destination = match rebased_commit {
1174 RebasedCommit::Rewritten(commit) => commit,
1175 RebasedCommit::Abandoned { .. } => panic!("all commits should be kept"),
1176 };
1177 })?;
1178 }
1179 let mut destination_tree = rewritten_destination.tree()?;
1181 for source in &source_commits {
1182 destination_tree =
1183 destination_tree.merge(&source.commit.parent_tree, &source.commit.selected_tree)?;
1184 }
1185 let mut predecessors = vec![destination.id().clone()];
1186 predecessors.extend(
1187 source_commits
1188 .iter()
1189 .map(|source| source.commit.commit.id().clone()),
1190 );
1191
1192 let commit_builder = repo
1193 .rewrite_commit(&rewritten_destination)
1194 .set_tree_id(destination_tree.id().clone())
1195 .set_predecessors(predecessors);
1196 Ok(Some(SquashedCommit {
1197 commit_builder,
1198 abandoned_commits,
1199 }))
1200}
1201
1202pub fn find_duplicate_divergent_commits(
1206 repo: &dyn Repo,
1207 new_parent_ids: &[CommitId],
1208 target: &MoveCommitsTarget,
1209) -> BackendResult<Vec<Commit>> {
1210 let target_commits: Vec<Commit> = match target {
1211 MoveCommitsTarget::Commits(commit_ids) => commit_ids
1212 .iter()
1213 .map(|commit_id| repo.store().get_commit(commit_id))
1214 .try_collect()?,
1215 MoveCommitsTarget::Roots(root_ids) => RevsetExpression::commits(root_ids.clone())
1216 .descendants()
1217 .evaluate(repo)
1218 .map_err(|err| err.into_backend_error())?
1219 .iter()
1220 .commits(repo.store())
1221 .try_collect()
1222 .map_err(|err| err.into_backend_error())?,
1223 };
1224 let target_commit_ids: HashSet<&CommitId> = target_commits.iter().map(Commit::id).collect();
1225
1226 let divergent_changes: Vec<(&Commit, Vec<CommitId>)> = target_commits
1229 .iter()
1230 .map(|target_commit| {
1231 let mut ancestor_candidates = repo
1232 .resolve_change_id(target_commit.change_id())
1233 .unwrap_or_default();
1234 ancestor_candidates.retain(|commit_id| !target_commit_ids.contains(commit_id));
1235 (target_commit, ancestor_candidates)
1236 })
1237 .filter(|(_, candidates)| !candidates.is_empty())
1238 .collect();
1239 if divergent_changes.is_empty() {
1240 return Ok(Vec::new());
1241 }
1242
1243 let target_root_ids = match target {
1244 MoveCommitsTarget::Commits(commit_ids) => commit_ids,
1245 MoveCommitsTarget::Roots(root_ids) => root_ids,
1246 };
1247
1248 let is_new_ancestor = RevsetExpression::commits(target_root_ids.clone())
1251 .range(&RevsetExpression::commits(new_parent_ids.to_owned()))
1252 .evaluate(repo)
1253 .map_err(|err| err.into_backend_error())?
1254 .containing_fn();
1255
1256 let mut duplicate_divergent = Vec::new();
1257 for (target_commit, ancestor_candidates) in divergent_changes {
1262 for ancestor_candidate_id in ancestor_candidates {
1263 if !is_new_ancestor(&ancestor_candidate_id).map_err(|err| err.into_backend_error())? {
1264 continue;
1265 }
1266
1267 let ancestor_candidate = repo.store().get_commit(&ancestor_candidate_id)?;
1268 let new_tree =
1269 rebase_to_dest_parent(repo, slice::from_ref(target_commit), &ancestor_candidate)?;
1270 if new_tree.id() == *ancestor_candidate.tree_id() {
1273 duplicate_divergent.push(target_commit.clone());
1274 break;
1275 }
1276 }
1277 }
1278 Ok(duplicate_divergent)
1279}