1#![allow(missing_docs)]
16
17use std::collections::HashMap;
18use std::collections::HashSet;
19use std::sync::Arc;
20
21use futures::StreamExt as _;
22use indexmap::IndexMap;
23use indexmap::IndexSet;
24use itertools::Itertools as _;
25use pollster::FutureExt 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 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 async {
103 let mut diff_stream = source.diff_stream(destination, matcher);
105 while let Some(TreeDiffEntry {
106 path: repo_path,
107 values,
108 }) = diff_stream.next().await
109 {
110 let (source_value, _destination_value) = values?;
111 tree_builder.set_or_remove(repo_path, source_value);
112 }
113 Ok::<(), BackendError>(())
114 }
115 .block_on()?;
116 tree_builder.write_tree(destination.store())
117 }
118}
119
120pub fn rebase_commit(
121 mut_repo: &mut MutableRepo,
122 old_commit: Commit,
123 new_parents: Vec<CommitId>,
124) -> BackendResult<Commit> {
125 let rewriter = CommitRewriter::new(mut_repo, old_commit, new_parents);
126 let builder = rewriter.rebase()?;
127 builder.write()
128}
129
130pub struct CommitRewriter<'repo> {
132 mut_repo: &'repo mut MutableRepo,
133 old_commit: Commit,
134 new_parents: Vec<CommitId>,
135}
136
137impl<'repo> CommitRewriter<'repo> {
138 pub fn new(
140 mut_repo: &'repo mut MutableRepo,
141 old_commit: Commit,
142 new_parents: Vec<CommitId>,
143 ) -> Self {
144 Self {
145 mut_repo,
146 old_commit,
147 new_parents,
148 }
149 }
150
151 pub fn mut_repo(&mut self) -> &mut MutableRepo {
153 self.mut_repo
154 }
155
156 pub fn old_commit(&self) -> &Commit {
158 &self.old_commit
159 }
160
161 pub fn new_parents(&self) -> &[CommitId] {
163 &self.new_parents
164 }
165
166 pub fn set_new_parents(&mut self, new_parents: Vec<CommitId>) {
168 self.new_parents = new_parents;
169 }
170
171 pub fn set_new_rewritten_parents(&mut self, unrewritten_parents: &[CommitId]) {
174 self.new_parents = self.mut_repo.new_parents(unrewritten_parents);
175 }
176
177 pub fn replace_parent<'a>(
180 &mut self,
181 old_parent: &CommitId,
182 new_parents: impl IntoIterator<Item = &'a CommitId>,
183 ) {
184 if let Some(i) = self.new_parents.iter().position(|p| p == old_parent) {
185 self.new_parents
186 .splice(i..i + 1, new_parents.into_iter().cloned());
187 let mut unique = HashSet::new();
188 self.new_parents.retain(|p| unique.insert(p.clone()));
189 }
190 }
191
192 pub fn parents_changed(&self) -> bool {
195 self.new_parents != self.old_commit.parent_ids()
196 }
197
198 pub fn simplify_ancestor_merge(&mut self) -> Result<(), IndexError> {
201 let head_set: HashSet<_> = self
202 .mut_repo
203 .index()
204 .heads(&mut self.new_parents.iter())?
205 .into_iter()
206 .collect();
207 self.new_parents.retain(|parent| head_set.contains(parent));
208 Ok(())
209 }
210
211 pub fn abandon(self) {
215 let old_commit_id = self.old_commit.id().clone();
216 let new_parents = self.new_parents;
217 self.mut_repo
218 .record_abandoned_commit_with_parents(old_commit_id, new_parents);
219 }
220
221 pub fn rebase_with_empty_behavior(
224 self,
225 empty: EmptyBehaviour,
226 ) -> BackendResult<Option<CommitBuilder<'repo>>> {
227 let old_parents: Vec<_> = self.old_commit.parents().try_collect()?;
228 let old_parent_trees = old_parents
229 .iter()
230 .map(|parent| parent.tree_id().clone())
231 .collect_vec();
232 let new_parents: Vec<_> = self
233 .new_parents
234 .iter()
235 .map(|new_parent_id| self.mut_repo.store().get_commit(new_parent_id))
236 .try_collect()?;
237 let new_parent_trees = new_parents
238 .iter()
239 .map(|parent| parent.tree_id().clone())
240 .collect_vec();
241
242 let (was_empty, new_tree_id) = if new_parent_trees == old_parent_trees {
243 (
244 true,
247 self.old_commit.tree_id().clone(),
249 )
250 } else {
251 let old_base_tree = merge_commit_trees(self.mut_repo, &old_parents)?;
252 let new_base_tree = merge_commit_trees(self.mut_repo, &new_parents)?;
253 let old_tree = self.old_commit.tree()?;
254 (
255 old_base_tree.id() == *self.old_commit.tree_id(),
256 new_base_tree.merge(&old_base_tree, &old_tree)?.id(),
257 )
258 };
259 if let [parent] = &new_parents[..] {
262 let should_abandon = match empty {
263 EmptyBehaviour::Keep => false,
264 EmptyBehaviour::AbandonNewlyEmpty => *parent.tree_id() == new_tree_id && !was_empty,
265 EmptyBehaviour::AbandonAllEmpty => *parent.tree_id() == new_tree_id,
266 };
267 if should_abandon {
268 self.abandon();
269 return Ok(None);
270 }
271 }
272
273 let builder = self
274 .mut_repo
275 .rewrite_commit(&self.old_commit)
276 .set_parents(self.new_parents)
277 .set_tree_id(new_tree_id);
278 Ok(Some(builder))
279 }
280
281 pub fn rebase(self) -> BackendResult<CommitBuilder<'repo>> {
284 let builder = self.rebase_with_empty_behavior(EmptyBehaviour::Keep)?;
285 Ok(builder.unwrap())
286 }
287
288 pub fn reparent(self) -> CommitBuilder<'repo> {
291 self.mut_repo
292 .rewrite_commit(&self.old_commit)
293 .set_parents(self.new_parents)
294 }
295}
296
297pub enum RebasedCommit {
298 Rewritten(Commit),
299 Abandoned { parent_id: CommitId },
300}
301
302pub fn rebase_commit_with_options(
303 mut rewriter: CommitRewriter<'_>,
304 options: &RebaseOptions,
305) -> BackendResult<RebasedCommit> {
306 if options.simplify_ancestor_merge {
308 rewriter
311 .simplify_ancestor_merge()
312 .map_err(|err| BackendError::Other(err.into()))?;
313 }
314
315 let single_parent = match &rewriter.new_parents[..] {
316 [parent_id] => Some(parent_id.clone()),
317 _ => None,
318 };
319 let new_parents_len = rewriter.new_parents.len();
320 if let Some(builder) = rewriter.rebase_with_empty_behavior(options.empty)? {
321 let new_commit = builder.write()?;
322 Ok(RebasedCommit::Rewritten(new_commit))
323 } else {
324 assert_eq!(new_parents_len, 1);
325 Ok(RebasedCommit::Abandoned {
326 parent_id: single_parent.unwrap(),
327 })
328 }
329}
330
331pub fn rebase_to_dest_parent(
333 repo: &dyn Repo,
334 sources: &[Commit],
335 destination: &Commit,
336) -> BackendResult<MergedTree> {
337 if let [source] = sources {
338 if source.parent_ids() == destination.parent_ids() {
339 return source.tree();
340 }
341 }
342 sources.iter().try_fold(
343 destination.parent_tree(repo)?,
344 |destination_tree, source| {
345 let source_parent_tree = source.parent_tree(repo)?;
346 let source_tree = source.tree()?;
347 destination_tree.merge(&source_parent_tree, &source_tree)
348 },
349 )
350}
351
352#[derive(Clone, Copy, Default, PartialEq, Eq, Debug)]
353pub enum EmptyBehaviour {
354 #[default]
356 Keep,
357 AbandonNewlyEmpty,
361 AbandonAllEmpty,
365}
366
367#[derive(Clone, Debug, Default)]
374pub struct RebaseOptions {
375 pub empty: EmptyBehaviour,
376 pub rewrite_refs: RewriteRefsOptions,
377 pub simplify_ancestor_merge: bool,
380}
381
382#[derive(Clone, Debug, Default)]
384pub struct RewriteRefsOptions {
385 pub delete_abandoned_bookmarks: bool,
390}
391
392pub struct MoveCommitsStats {
393 pub num_rebased_targets: u32,
395 pub num_rebased_descendants: u32,
397 pub num_skipped_rebases: u32,
400 pub num_abandoned_empty: u32,
402 pub rebased_commits: HashMap<CommitId, RebasedCommit>,
404}
405
406#[derive(Clone, Debug)]
408pub struct MoveCommitsLocation {
409 pub new_parent_ids: Vec<CommitId>,
410 pub new_child_ids: Vec<CommitId>,
411 pub target: MoveCommitsTarget,
412}
413
414#[derive(Clone, Debug)]
415pub enum MoveCommitsTarget {
416 Commits(Vec<CommitId>),
419 Roots(Vec<CommitId>),
421}
422
423#[derive(Clone, Debug)]
424pub struct ComputedMoveCommits {
425 target_commit_ids: IndexSet<CommitId>,
426 descendants: Vec<Commit>,
427 commit_new_parents_map: HashMap<CommitId, Vec<CommitId>>,
428 to_abandon: HashSet<CommitId>,
429}
430
431impl ComputedMoveCommits {
432 fn empty() -> Self {
433 ComputedMoveCommits {
434 target_commit_ids: IndexSet::new(),
435 descendants: vec![],
436 commit_new_parents_map: HashMap::new(),
437 to_abandon: HashSet::new(),
438 }
439 }
440
441 pub fn record_to_abandon(&mut self, commit_ids: impl IntoIterator<Item = CommitId>) {
448 self.to_abandon.extend(commit_ids);
449 }
450
451 pub fn apply(
452 self,
453 mut_repo: &mut MutableRepo,
454 options: &RebaseOptions,
455 ) -> BackendResult<MoveCommitsStats> {
456 apply_move_commits(mut_repo, self, options)
457 }
458}
459
460pub fn move_commits(
469 mut_repo: &mut MutableRepo,
470 loc: &MoveCommitsLocation,
471 options: &RebaseOptions,
472) -> BackendResult<MoveCommitsStats> {
473 compute_move_commits(mut_repo, loc)?.apply(mut_repo, options)
474}
475
476pub fn compute_move_commits(
477 repo: &MutableRepo,
478 loc: &MoveCommitsLocation,
479) -> BackendResult<ComputedMoveCommits> {
480 let target_commit_ids: IndexSet<CommitId>;
481 let connected_target_commits: Vec<Commit>;
482 let connected_target_commits_internal_parents: HashMap<CommitId, Vec<CommitId>>;
483 let target_roots: HashSet<CommitId>;
484
485 match &loc.target {
486 MoveCommitsTarget::Commits(commit_ids) => {
487 if commit_ids.is_empty() {
488 return Ok(ComputedMoveCommits::empty());
489 }
490
491 target_commit_ids = commit_ids.iter().cloned().collect();
492
493 connected_target_commits = RevsetExpression::commits(commit_ids.clone())
494 .connected()
495 .evaluate(repo)
496 .map_err(|err| err.into_backend_error())?
497 .iter()
498 .commits(repo.store())
499 .try_collect()
500 .map_err(|err| err.into_backend_error())?;
501 connected_target_commits_internal_parents =
502 compute_internal_parents_within(&target_commit_ids, &connected_target_commits);
503
504 target_roots = connected_target_commits_internal_parents
505 .iter()
506 .filter(|&(commit_id, parents)| {
507 target_commit_ids.contains(commit_id) && parents.is_empty()
508 })
509 .map(|(commit_id, _)| commit_id.clone())
510 .collect();
511 }
512 MoveCommitsTarget::Roots(root_ids) => {
513 if root_ids.is_empty() {
514 return Ok(ComputedMoveCommits::empty());
515 }
516
517 target_commit_ids = RevsetExpression::commits(root_ids.clone())
518 .descendants()
519 .evaluate(repo)
520 .map_err(|err| err.into_backend_error())?
521 .iter()
522 .try_collect()
523 .map_err(|err| err.into_backend_error())?;
524
525 connected_target_commits = target_commit_ids
526 .iter()
527 .map(|id| repo.store().get_commit(id))
528 .try_collect()?;
529 connected_target_commits_internal_parents = HashMap::new();
532 target_roots = root_ids.iter().cloned().collect();
533 }
534 }
535
536 let mut target_commits_external_parents: HashMap<CommitId, IndexSet<CommitId>> = HashMap::new();
540 for id in target_commit_ids.iter().rev() {
541 let commit = repo.store().get_commit(id)?;
542 let mut new_parents = IndexSet::new();
543 for old_parent in commit.parent_ids() {
544 if let Some(parents) = target_commits_external_parents.get(old_parent) {
545 new_parents.extend(parents.iter().cloned());
546 } else {
547 new_parents.insert(old_parent.clone());
548 }
549 }
550 target_commits_external_parents.insert(commit.id().clone(), new_parents);
551 }
552
553 let new_parent_ids: Vec<_> = loc
557 .new_parent_ids
558 .iter()
559 .flat_map(|parent_id| {
560 if let Some(parent_ids) = target_commits_external_parents.get(parent_id) {
561 parent_ids.iter().cloned().collect_vec()
562 } else {
563 vec![parent_id.clone()]
564 }
565 })
566 .collect();
567
568 let new_children: Vec<_> = if loc
572 .new_child_ids
573 .iter()
574 .any(|id| target_commit_ids.contains(id))
575 {
576 let target_commits_descendants: Vec<_> =
577 RevsetExpression::commits(target_commit_ids.iter().cloned().collect_vec())
578 .union(
579 &RevsetExpression::commits(target_commit_ids.iter().cloned().collect_vec())
580 .children(),
581 )
582 .evaluate(repo)
583 .map_err(|err| err.into_backend_error())?
584 .iter()
585 .commits(repo.store())
586 .try_collect()
587 .map_err(|err| err.into_backend_error())?;
588
589 let mut target_commit_external_descendants: HashMap<CommitId, IndexSet<Commit>> =
592 HashMap::new();
593 for commit in &target_commits_descendants {
596 if !target_commit_external_descendants.contains_key(commit.id()) {
597 let children = if target_commit_ids.contains(commit.id()) {
598 IndexSet::new()
599 } else {
600 IndexSet::from([commit.clone()])
601 };
602 target_commit_external_descendants.insert(commit.id().clone(), children);
603 }
604
605 let children = target_commit_external_descendants
606 .get(commit.id())
607 .unwrap()
608 .iter()
609 .cloned()
610 .collect_vec();
611 for parent_id in commit.parent_ids() {
612 if target_commit_ids.contains(parent_id) {
613 if let Some(target_children) =
614 target_commit_external_descendants.get_mut(parent_id)
615 {
616 target_children.extend(children.iter().cloned());
617 } else {
618 target_commit_external_descendants
619 .insert(parent_id.clone(), children.iter().cloned().collect());
620 }
621 };
622 }
623 }
624
625 let mut new_children = Vec::new();
626 for id in &loc.new_child_ids {
627 if let Some(children) = target_commit_external_descendants.get(id) {
628 new_children.extend(children.iter().cloned());
629 } else {
630 new_children.push(repo.store().get_commit(id)?);
631 }
632 }
633 new_children
634 } else {
635 loc.new_child_ids
636 .iter()
637 .map(|id| repo.store().get_commit(id))
638 .try_collect()?
639 };
640
641 let new_children_parents: HashMap<_, _> = if !new_children.is_empty() {
644 let target_heads = compute_commits_heads(&target_commit_ids, &connected_target_commits);
647
648 new_children
649 .iter()
650 .map(|child_commit| {
651 let mut new_child_parent_ids = IndexSet::new();
652 for old_child_parent_id in child_commit.parent_ids() {
653 let old_child_parent_ids = if let Some(parents) =
655 target_commits_external_parents.get(old_child_parent_id)
656 {
657 parents.iter().collect_vec()
658 } else {
659 vec![old_child_parent_id]
660 };
661
662 for id in old_child_parent_ids {
666 if new_parent_ids.contains(id) {
667 new_child_parent_ids.extend(target_heads.clone());
668 } else {
669 new_child_parent_ids.insert(id.clone());
670 };
671 }
672 }
673
674 new_child_parent_ids.extend(target_heads.clone());
677
678 (
679 child_commit.id().clone(),
680 new_child_parent_ids.into_iter().collect_vec(),
681 )
682 })
683 .collect()
684 } else {
685 HashMap::new()
686 };
687
688 let mut roots = target_roots.iter().cloned().collect_vec();
691 roots.extend(new_children.iter().ids().cloned());
692
693 let descendants = repo.find_descendants_for_rebase(roots.clone())?;
694 let commit_new_parents_map = descendants
695 .iter()
696 .map(|commit| {
697 let commit_id = commit.id();
698 let new_parent_ids =
699
700 if let Some(new_child_parents) = new_children_parents.get(commit_id) {
702 new_child_parents.clone()
703 }
704 else if target_commit_ids.contains(commit_id) {
706 if target_roots.contains(commit_id) {
708 new_parent_ids.clone()
709 }
710 else {
718 let mut new_parents = vec![];
719 for parent_id in commit.parent_ids() {
720 if target_commit_ids.contains(parent_id) {
721 new_parents.push(parent_id.clone());
722 } else if let Some(parents) =
723 connected_target_commits_internal_parents.get(parent_id) {
724 new_parents.extend(parents.iter().cloned());
725 } else if !new_children.iter().any(|new_child| {
726 repo.index().is_ancestor(new_child.id(), parent_id) }) {
727 new_parents.push(parent_id.clone());
728 }
729 }
730 new_parents
731 }
732 }
733 else if commit
736 .parent_ids()
737 .iter()
738 .any(|id| target_commits_external_parents.contains_key(id))
739 {
740 let mut new_parents = vec![];
741 for parent in commit.parent_ids() {
742 if let Some(parents) = target_commits_external_parents.get(parent) {
743 new_parents.extend(parents.iter().cloned());
744 } else {
745 new_parents.push(parent.clone());
746 }
747 }
748 new_parents
749 } else {
750 commit.parent_ids().iter().cloned().collect_vec()
751 };
752 (commit.id().clone(), new_parent_ids)
753 })
754 .collect();
755
756 Ok(ComputedMoveCommits {
757 target_commit_ids,
758 descendants,
759 commit_new_parents_map,
760 to_abandon: HashSet::new(),
761 })
762}
763
764fn apply_move_commits(
765 mut_repo: &mut MutableRepo,
766 commits: ComputedMoveCommits,
767 options: &RebaseOptions,
768) -> BackendResult<MoveCommitsStats> {
769 let mut num_rebased_targets = 0;
770 let mut num_rebased_descendants = 0;
771 let mut num_skipped_rebases = 0;
772 let mut num_abandoned_empty = 0;
773
774 let rebase_descendant_options = &RebaseOptions {
776 empty: EmptyBehaviour::Keep,
777 rewrite_refs: options.rewrite_refs.clone(),
778 simplify_ancestor_merge: options.simplify_ancestor_merge,
779 };
780
781 let mut rebased_commits: HashMap<CommitId, RebasedCommit> = HashMap::new();
782 mut_repo.transform_commits(
783 commits.descendants,
784 &commits.commit_new_parents_map,
785 &options.rewrite_refs,
786 |rewriter| {
787 let old_commit_id = rewriter.old_commit().id().clone();
788 if commits.to_abandon.contains(&old_commit_id) {
789 rewriter.abandon();
790 } else if rewriter.parents_changed() {
791 let is_target_commit = commits.target_commit_ids.contains(&old_commit_id);
792 let rebased_commit = rebase_commit_with_options(
793 rewriter,
794 if is_target_commit {
795 options
796 } else {
797 rebase_descendant_options
798 },
799 )?;
800 if let RebasedCommit::Abandoned { .. } = rebased_commit {
801 num_abandoned_empty += 1;
802 } else if is_target_commit {
803 num_rebased_targets += 1;
804 } else {
805 num_rebased_descendants += 1;
806 }
807 rebased_commits.insert(old_commit_id, rebased_commit);
808 } else {
809 num_skipped_rebases += 1;
810 }
811
812 Ok(())
813 },
814 )?;
815
816 Ok(MoveCommitsStats {
817 num_rebased_targets,
818 num_rebased_descendants,
819 num_skipped_rebases,
820 num_abandoned_empty,
821 rebased_commits,
822 })
823}
824
825#[derive(Default)]
826pub struct DuplicateCommitsStats {
827 pub duplicated_commits: IndexMap<CommitId, Commit>,
829 pub num_rebased: u32,
832}
833
834pub fn duplicate_commits(
852 mut_repo: &mut MutableRepo,
853 target_commit_ids: &[CommitId],
854 target_descriptions: &HashMap<CommitId, String>,
855 parent_commit_ids: &[CommitId],
856 children_commit_ids: &[CommitId],
857) -> BackendResult<DuplicateCommitsStats> {
858 if target_commit_ids.is_empty() {
859 return Ok(DuplicateCommitsStats::default());
860 }
861
862 let mut duplicated_old_to_new: IndexMap<CommitId, Commit> = IndexMap::new();
863 let mut num_rebased = 0;
864
865 let target_commit_ids: IndexSet<_> = target_commit_ids.iter().cloned().collect();
866
867 let connected_target_commits: Vec<_> =
868 RevsetExpression::commits(target_commit_ids.iter().cloned().collect_vec())
869 .connected()
870 .evaluate(mut_repo)
871 .map_err(|err| err.into_backend_error())?
872 .iter()
873 .commits(mut_repo.store())
874 .try_collect()
875 .map_err(|err| err.into_backend_error())?;
876
877 let target_commits_internal_parents = {
884 let mut target_commits_internal_parents =
885 compute_internal_parents_within(&target_commit_ids, &connected_target_commits);
886 target_commits_internal_parents.retain(|id, _| target_commit_ids.contains(id));
887 target_commits_internal_parents
888 };
889
890 let target_root_ids: HashSet<_> = target_commits_internal_parents
892 .iter()
893 .filter(|(_, parents)| parents.is_empty())
894 .map(|(commit_id, _)| commit_id.clone())
895 .collect();
896
897 let target_head_ids = if !children_commit_ids.is_empty() {
900 compute_commits_heads(&target_commit_ids, &connected_target_commits)
901 } else {
902 vec![]
903 };
904
905 for original_commit_id in target_commit_ids.iter().rev() {
908 let original_commit = mut_repo.store().get_commit(original_commit_id)?;
909 let new_parent_ids = if target_root_ids.contains(original_commit_id) {
910 parent_commit_ids.to_vec()
911 } else {
912 target_commits_internal_parents
913 .get(original_commit_id)
914 .unwrap()
915 .iter()
916 .map(|id| {
918 duplicated_old_to_new
919 .get(id)
920 .map_or(id, |commit| commit.id())
921 .clone()
922 })
923 .collect()
924 };
925 let mut new_commit_builder = CommitRewriter::new(mut_repo, original_commit, new_parent_ids)
926 .rebase()?
927 .generate_new_change_id();
928 if let Some(desc) = target_descriptions.get(original_commit_id) {
929 new_commit_builder = new_commit_builder.set_description(desc);
930 }
931 duplicated_old_to_new.insert(original_commit_id.clone(), new_commit_builder.write()?);
932 }
933
934 let target_head_ids = target_head_ids
937 .into_iter()
938 .map(|commit_id| {
939 duplicated_old_to_new
940 .get(&commit_id)
941 .map_or(commit_id, |commit| commit.id().clone())
942 })
943 .collect_vec();
944
945 let children_commit_ids_set: HashSet<CommitId> = children_commit_ids.iter().cloned().collect();
947 mut_repo.transform_descendants(children_commit_ids.to_vec(), |mut rewriter| {
948 if children_commit_ids_set.contains(rewriter.old_commit().id()) {
949 let mut child_new_parent_ids = IndexSet::new();
950 for old_parent_id in rewriter.old_commit().parent_ids() {
951 if parent_commit_ids.contains(old_parent_id) {
956 child_new_parent_ids.extend(target_head_ids.clone());
957 } else {
958 child_new_parent_ids.insert(old_parent_id.clone());
959 }
960 }
961 child_new_parent_ids.extend(target_head_ids.clone());
964 rewriter.set_new_parents(child_new_parent_ids.into_iter().collect());
965 }
966 num_rebased += 1;
967 rewriter.rebase()?.write()?;
968 Ok(())
969 })?;
970
971 Ok(DuplicateCommitsStats {
972 duplicated_commits: duplicated_old_to_new,
973 num_rebased,
974 })
975}
976
977pub fn duplicate_commits_onto_parents(
987 mut_repo: &mut MutableRepo,
988 target_commits: &[CommitId],
989 target_descriptions: &HashMap<CommitId, String>,
990) -> BackendResult<DuplicateCommitsStats> {
991 if target_commits.is_empty() {
992 return Ok(DuplicateCommitsStats::default());
993 }
994
995 let mut duplicated_old_to_new: IndexMap<CommitId, Commit> = IndexMap::new();
996
997 for original_commit_id in target_commits.iter().rev() {
1000 let original_commit = mut_repo.store().get_commit(original_commit_id)?;
1001 let new_parent_ids = original_commit
1002 .parent_ids()
1003 .iter()
1004 .map(|id| {
1005 duplicated_old_to_new
1006 .get(id)
1007 .map_or(id, |commit| commit.id())
1008 .clone()
1009 })
1010 .collect();
1011 let mut new_commit_builder = mut_repo
1012 .rewrite_commit(&original_commit)
1013 .generate_new_change_id()
1014 .set_parents(new_parent_ids);
1015 if let Some(desc) = target_descriptions.get(original_commit_id) {
1016 new_commit_builder = new_commit_builder.set_description(desc);
1017 }
1018 duplicated_old_to_new.insert(original_commit_id.clone(), new_commit_builder.write()?);
1019 }
1020
1021 Ok(DuplicateCommitsStats {
1022 duplicated_commits: duplicated_old_to_new,
1023 num_rebased: 0,
1024 })
1025}
1026
1027fn compute_internal_parents_within(
1035 target_commit_ids: &IndexSet<CommitId>,
1036 graph_commits: &[Commit],
1037) -> HashMap<CommitId, Vec<CommitId>> {
1038 let mut internal_parents: HashMap<CommitId, Vec<CommitId>> = HashMap::new();
1039 for commit in graph_commits.iter().rev() {
1040 let mut new_parents = vec![];
1043 for old_parent in commit.parent_ids() {
1044 if target_commit_ids.contains(old_parent) {
1045 new_parents.push(old_parent.clone());
1046 } else if let Some(parents) = internal_parents.get(old_parent) {
1047 new_parents.extend(parents.iter().cloned());
1048 }
1049 }
1050 internal_parents.insert(commit.id().clone(), new_parents);
1051 }
1052 internal_parents
1053}
1054
1055fn compute_commits_heads(
1061 target_commit_ids: &IndexSet<CommitId>,
1062 connected_target_commits: &[Commit],
1063) -> Vec<CommitId> {
1064 let mut target_head_ids: HashSet<CommitId> = HashSet::new();
1065 for commit in connected_target_commits.iter().rev() {
1066 target_head_ids.insert(commit.id().clone());
1067 for old_parent in commit.parent_ids() {
1068 target_head_ids.remove(old_parent);
1069 }
1070 }
1071 connected_target_commits
1072 .iter()
1073 .rev()
1074 .filter(|commit| {
1075 target_head_ids.contains(commit.id()) && target_commit_ids.contains(commit.id())
1076 })
1077 .map(|commit| commit.id().clone())
1078 .collect_vec()
1079}
1080
1081pub struct CommitWithSelection {
1082 pub commit: Commit,
1083 pub selected_tree: MergedTree,
1084 pub parent_tree: MergedTree,
1085}
1086
1087impl CommitWithSelection {
1088 pub fn is_full_selection(&self) -> bool {
1090 &self.selected_tree.id() == self.commit.tree_id()
1091 }
1092
1093 pub fn is_empty_selection(&self) -> bool {
1099 self.selected_tree.id() == self.parent_tree.id()
1100 }
1101}
1102
1103#[must_use]
1105pub struct SquashedCommit<'repo> {
1106 pub commit_builder: CommitBuilder<'repo>,
1108 pub abandoned_commits: Vec<Commit>,
1110}
1111
1112pub fn squash_commits<'repo>(
1116 repo: &'repo mut MutableRepo,
1117 sources: &[CommitWithSelection],
1118 destination: &Commit,
1119 keep_emptied: bool,
1120) -> BackendResult<Option<SquashedCommit<'repo>>> {
1121 struct SourceCommit<'a> {
1122 commit: &'a CommitWithSelection,
1123 abandon: bool,
1124 }
1125 let mut source_commits = vec![];
1126 for source in sources {
1127 let abandon = !keep_emptied && source.is_full_selection();
1128 if !abandon && source.is_empty_selection() {
1129 continue;
1133 }
1134
1135 source_commits.push(SourceCommit {
1138 commit: source,
1139 abandon,
1140 });
1141 }
1142
1143 if source_commits.is_empty() {
1144 return Ok(None);
1145 }
1146
1147 let mut abandoned_commits = vec![];
1148 for source in &source_commits {
1149 if source.abandon {
1150 repo.record_abandoned_commit(&source.commit.commit);
1151 abandoned_commits.push(source.commit.commit.clone());
1152 } else {
1153 let source_tree = source.commit.commit.tree()?;
1154 let new_source_tree =
1156 source_tree.merge(&source.commit.selected_tree, &source.commit.parent_tree)?;
1157 repo.rewrite_commit(&source.commit.commit)
1158 .set_tree_id(new_source_tree.id().clone())
1159 .write()?;
1160 }
1161 }
1162
1163 let mut rewritten_destination = destination.clone();
1164 if sources.iter().any(|source| {
1165 repo.index()
1166 .is_ancestor(source.commit.id(), destination.id())
1167 }) {
1168 let options = RebaseOptions::default();
1173 repo.rebase_descendants_with_options(&options, |old_commit, rebased_commit| {
1174 if old_commit.id() != destination.id() {
1175 return;
1176 }
1177 rewritten_destination = match rebased_commit {
1178 RebasedCommit::Rewritten(commit) => commit,
1179 RebasedCommit::Abandoned { .. } => panic!("all commits should be kept"),
1180 };
1181 })?;
1182 }
1183 let mut destination_tree = rewritten_destination.tree()?;
1185 for source in &source_commits {
1186 destination_tree =
1187 destination_tree.merge(&source.commit.parent_tree, &source.commit.selected_tree)?;
1188 }
1189 let mut predecessors = vec![destination.id().clone()];
1190 predecessors.extend(
1191 source_commits
1192 .iter()
1193 .map(|source| source.commit.commit.id().clone()),
1194 );
1195
1196 let commit_builder = repo
1197 .rewrite_commit(&rewritten_destination)
1198 .set_tree_id(destination_tree.id().clone())
1199 .set_predecessors(predecessors);
1200 Ok(Some(SquashedCommit {
1201 commit_builder,
1202 abandoned_commits,
1203 }))
1204}
1205
1206pub fn find_duplicate_divergent_commits(
1210 repo: &dyn Repo,
1211 new_parent_ids: &[CommitId],
1212 target: &MoveCommitsTarget,
1213) -> BackendResult<Vec<Commit>> {
1214 let target_commits: Vec<Commit> = match target {
1215 MoveCommitsTarget::Commits(commit_ids) => commit_ids
1216 .iter()
1217 .map(|commit_id| repo.store().get_commit(commit_id))
1218 .try_collect()?,
1219 MoveCommitsTarget::Roots(root_ids) => RevsetExpression::commits(root_ids.clone())
1220 .descendants()
1221 .evaluate(repo)
1222 .map_err(|err| err.into_backend_error())?
1223 .iter()
1224 .commits(repo.store())
1225 .try_collect()
1226 .map_err(|err| err.into_backend_error())?,
1227 };
1228 let target_commit_ids: HashSet<&CommitId> = target_commits.iter().map(Commit::id).collect();
1229
1230 let divergent_changes: Vec<(&Commit, Vec<CommitId>)> = target_commits
1233 .iter()
1234 .map(|target_commit| {
1235 let mut ancestor_candidates = repo
1236 .resolve_change_id(target_commit.change_id())
1237 .unwrap_or_default();
1238 ancestor_candidates.retain(|commit_id| !target_commit_ids.contains(commit_id));
1239 (target_commit, ancestor_candidates)
1240 })
1241 .filter(|(_, candidates)| !candidates.is_empty())
1242 .collect();
1243 if divergent_changes.is_empty() {
1244 return Ok(Vec::new());
1245 }
1246
1247 let target_root_ids = match target {
1248 MoveCommitsTarget::Commits(commit_ids) => commit_ids,
1249 MoveCommitsTarget::Roots(root_ids) => root_ids,
1250 };
1251
1252 let is_new_ancestor = RevsetExpression::commits(target_root_ids.clone())
1255 .range(&RevsetExpression::commits(new_parent_ids.to_owned()))
1256 .evaluate(repo)
1257 .map_err(|err| err.into_backend_error())?
1258 .containing_fn();
1259
1260 let mut duplicate_divergent = Vec::new();
1261 for (target_commit, ancestor_candidates) in divergent_changes {
1266 for ancestor_candidate_id in ancestor_candidates {
1267 if !is_new_ancestor(&ancestor_candidate_id).map_err(|err| err.into_backend_error())? {
1268 continue;
1269 }
1270
1271 let ancestor_candidate = repo.store().get_commit(&ancestor_candidate_id)?;
1272 let new_tree =
1273 rebase_to_dest_parent(repo, &[target_commit.clone()], &ancestor_candidate)?;
1274 if new_tree.id() == *ancestor_candidate.tree_id() {
1277 duplicate_divergent.push(target_commit.clone());
1278 break;
1279 }
1280 }
1281 }
1282 Ok(duplicate_divergent)
1283}