1#![expect(missing_docs)]
16
17use std::collections::HashMap;
18use std::collections::HashSet;
19use std::slice;
20use std::sync::Arc;
21
22use futures::StreamExt as _;
23use futures::future::try_join_all;
24use futures::try_join;
25use indexmap::IndexMap;
26use indexmap::IndexSet;
27use itertools::Itertools as _;
28use tracing::instrument;
29
30use crate::backend::BackendError;
31use crate::backend::BackendResult;
32use crate::backend::CommitId;
33use crate::commit::Commit;
34use crate::commit::CommitIteratorExt as _;
35use crate::commit::conflict_label_for_commits;
36use crate::commit_builder::CommitBuilder;
37use crate::conflict_labels::ConflictLabels;
38use crate::index::Index;
39use crate::index::IndexResult;
40use crate::index::ResolvedChangeTargets;
41use crate::iter_util::fallible_any;
42use crate::matchers::FilesMatcher;
43use crate::matchers::Matcher;
44use crate::matchers::Visit;
45use crate::merge::Diff;
46use crate::merge::Merge;
47use crate::merged_tree::MergedTree;
48use crate::merged_tree_builder::MergedTreeBuilder;
49use crate::repo::MutableRepo;
50use crate::repo::Repo;
51use crate::repo_path::RepoPath;
52use crate::revset::RevsetExpression;
53use crate::revset::RevsetIteratorExt as _;
54use crate::store::Store;
55
56#[instrument(skip(repo))]
58pub async fn merge_commit_trees(repo: &dyn Repo, commits: &[Commit]) -> BackendResult<MergedTree> {
59 if let [commit] = commits {
60 Ok(commit.tree())
61 } else {
62 merge_commit_trees_no_resolve_without_repo(repo.store(), repo.index(), commits)
63 .await?
64 .resolve()
65 .await
66 }
67}
68
69#[instrument(skip(index))]
71pub async fn merge_commit_trees_no_resolve_without_repo(
72 store: &Arc<Store>,
73 index: &dyn Index,
74 commits: &[Commit],
75) -> BackendResult<MergedTree> {
76 let commit_ids = commits
77 .iter()
78 .map(|commit| commit.id().clone())
79 .collect_vec();
80 let commit_id_merge = find_recursive_merge_commits(store, index, commit_ids)?;
81 let tree_merge: Merge<(MergedTree, String)> = commit_id_merge
82 .try_map_async(async |commit_id| {
83 let commit = store.get_commit_async(commit_id).await?;
84 Ok::<_, BackendError>((commit.tree(), commit.conflict_label()))
85 })
86 .await?;
87 Ok(MergedTree::merge_no_resolve(tree_merge))
88}
89
90pub fn find_recursive_merge_commits(
92 store: &Arc<Store>,
93 index: &dyn Index,
94 mut commit_ids: Vec<CommitId>,
95) -> BackendResult<Merge<CommitId>> {
96 if commit_ids.is_empty() {
97 Ok(Merge::resolved(store.root_commit_id().clone()))
98 } else if commit_ids.len() == 1 {
99 Ok(Merge::resolved(commit_ids.pop().unwrap()))
100 } else {
101 let mut result = Merge::resolved(commit_ids[0].clone());
102 for (i, other_commit_id) in commit_ids.iter().enumerate().skip(1) {
103 let ancestor_ids = index
104 .common_ancestors(&commit_ids[0..i], &commit_ids[i..][..1])
105 .map_err(|err| BackendError::Other(err.into()))?;
107 let ancestor_merge = find_recursive_merge_commits(store, index, ancestor_ids)?;
108 result = Merge::from_vec(vec![
109 result,
110 ancestor_merge,
111 Merge::resolved(other_commit_id.clone()),
112 ])
113 .flatten();
114 }
115 Ok(result)
116 }
117}
118
119pub async fn restore_tree(
121 source: &MergedTree,
122 destination: &MergedTree,
123 source_label: String,
124 destination_label: String,
125 matcher: &dyn Matcher,
126) -> BackendResult<MergedTree> {
127 if matcher.visit(RepoPath::root()) == Visit::AllRecursively {
128 return Ok(source.clone());
130 }
131 let mut diff_stream = source.diff_stream(destination, matcher);
132 let mut paths = Vec::new();
133 while let Some(entry) = diff_stream.next().await {
134 paths.push(entry.path);
137 }
138 let matcher = FilesMatcher::new(paths);
139
140 let select_matching =
141 async |tree: &MergedTree, labels: ConflictLabels| -> BackendResult<MergedTree> {
142 let empty_tree_ids = Merge::repeated(
143 tree.store().empty_tree_id().clone(),
144 tree.tree_ids().num_sides(),
145 );
146 let labeled_empty_tree = MergedTree::new(tree.store().clone(), empty_tree_ids, labels);
147 let mut builder = MergedTreeBuilder::new(labeled_empty_tree);
148 for (path, value) in tree.entries_matching(&matcher) {
149 builder.set_or_remove(path, value?);
152 }
153 builder.write_tree().await
154 };
155
156 const RESTORE_BASE_LABEL: &str = "base files for restore";
157
158 let base_labels = ConflictLabels::from_merge(destination.labels().as_merge().map(|label| {
161 if label.is_empty() || label.starts_with(RESTORE_BASE_LABEL) {
162 label.clone()
163 } else {
164 format!("{RESTORE_BASE_LABEL} (from {label})")
165 }
166 }));
167
168 MergedTree::merge(Merge::from_vec(vec![
180 (
181 destination.clone(),
182 format!("{destination_label} (restore destination)"),
183 ),
184 (
185 select_matching(destination, base_labels).await?,
186 format!("{RESTORE_BASE_LABEL} (from {destination_label})"),
187 ),
188 (
189 select_matching(source, source.labels().clone()).await?,
190 format!("restored files (from {source_label})"),
191 ),
192 ]))
193 .await
194}
195
196pub async fn rebase_commit(
197 mut_repo: &mut MutableRepo,
198 old_commit: Commit,
199 new_parents: Vec<CommitId>,
200) -> BackendResult<Commit> {
201 let rewriter = CommitRewriter::new(mut_repo, old_commit, new_parents);
202 let builder = rewriter.rebase().await?;
203 builder.write().await
204}
205
206pub struct CommitRewriter<'repo> {
208 mut_repo: &'repo mut MutableRepo,
209 old_commit: Commit,
210 new_parents: Vec<CommitId>,
211}
212
213impl<'repo> CommitRewriter<'repo> {
214 pub fn new(
216 mut_repo: &'repo mut MutableRepo,
217 old_commit: Commit,
218 new_parents: Vec<CommitId>,
219 ) -> Self {
220 Self {
221 mut_repo,
222 old_commit,
223 new_parents,
224 }
225 }
226
227 pub fn repo_mut(&mut self) -> &mut MutableRepo {
229 self.mut_repo
230 }
231
232 pub fn old_commit(&self) -> &Commit {
234 &self.old_commit
235 }
236
237 pub fn new_parents(&self) -> &[CommitId] {
239 &self.new_parents
240 }
241
242 pub fn set_new_parents(&mut self, new_parents: Vec<CommitId>) {
244 self.new_parents = new_parents;
245 }
246
247 pub fn set_new_rewritten_parents(&mut self, unrewritten_parents: &[CommitId]) {
250 self.new_parents = self.mut_repo.new_parents(unrewritten_parents);
251 }
252
253 pub fn replace_parent<'a>(
256 &mut self,
257 old_parent: &CommitId,
258 new_parents: impl IntoIterator<Item = &'a CommitId>,
259 ) {
260 if let Some(i) = self.new_parents.iter().position(|p| p == old_parent) {
261 self.new_parents
262 .splice(i..i + 1, new_parents.into_iter().cloned());
263 let mut unique = HashSet::new();
264 self.new_parents.retain(|p| unique.insert(p.clone()));
265 }
266 }
267
268 pub fn parents_changed(&self) -> bool {
271 self.new_parents != self.old_commit.parent_ids()
272 }
273
274 pub fn simplify_ancestor_merge(&mut self) -> IndexResult<()> {
277 let head_set: HashSet<_> = self
278 .mut_repo
279 .index()
280 .heads(&mut self.new_parents.iter())?
281 .into_iter()
282 .collect();
283 self.new_parents.retain(|parent| head_set.contains(parent));
284 Ok(())
285 }
286
287 pub fn abandon(self) {
291 let old_commit_id = self.old_commit.id().clone();
292 let new_parents = self.new_parents;
293 self.mut_repo
294 .record_abandoned_commit_with_parents(old_commit_id, new_parents);
295 }
296
297 pub async fn rebase_with_empty_behavior(
300 self,
301 empty: EmptyBehavior,
302 ) -> BackendResult<Option<CommitBuilder<'repo>>> {
303 let old_parents_fut = self.old_commit.parents();
304 let new_parents_fut = try_join_all(
305 self.new_parents
306 .iter()
307 .map(|new_parent_id| self.mut_repo.store().get_commit_async(new_parent_id)),
308 );
309 let (old_parents, new_parents) = try_join!(old_parents_fut, new_parents_fut)?;
310 let old_parent_trees = old_parents
311 .iter()
312 .map(|parent| parent.tree_ids().clone())
313 .collect_vec();
314 let new_parent_trees = new_parents
315 .iter()
316 .map(|parent| parent.tree_ids().clone())
317 .collect_vec();
318
319 let (was_empty, new_tree) = if new_parent_trees == old_parent_trees {
320 (
321 true,
324 self.old_commit.tree(),
326 )
327 } else {
328 let old_base_tree_fut = merge_commit_trees(self.mut_repo, &old_parents);
332 let new_base_tree_fut = merge_commit_trees(self.mut_repo, &new_parents);
333 let old_tree = self.old_commit.tree();
334 let (old_base_tree, new_base_tree) = try_join!(old_base_tree_fut, new_base_tree_fut)?;
335 (
336 old_base_tree.tree_ids() == self.old_commit.tree_ids(),
337 MergedTree::merge(Merge::from_vec(vec![
338 (
339 new_base_tree,
340 format!(
341 "{} (rebase destination)",
342 conflict_label_for_commits(&new_parents)
343 ),
344 ),
345 (
346 old_base_tree,
347 format!(
348 "{} (parents of rebased revision)",
349 conflict_label_for_commits(&old_parents)
350 ),
351 ),
352 (
353 old_tree,
354 format!("{} (rebased revision)", self.old_commit.conflict_label()),
355 ),
356 ]))
357 .await?,
358 )
359 };
360 if let [parent] = &new_parents[..] {
363 let should_abandon = match empty {
364 EmptyBehavior::Keep => false,
365 EmptyBehavior::AbandonNewlyEmpty => {
366 parent.tree_ids() == new_tree.tree_ids() && !was_empty
367 }
368 EmptyBehavior::AbandonAllEmpty => parent.tree_ids() == new_tree.tree_ids(),
369 };
370 if should_abandon {
371 self.abandon();
372 return Ok(None);
373 }
374 }
375
376 let builder = self
377 .mut_repo
378 .rewrite_commit(&self.old_commit)
379 .set_parents(self.new_parents)
380 .set_tree(new_tree);
381 Ok(Some(builder))
382 }
383
384 pub async fn rebase(self) -> BackendResult<CommitBuilder<'repo>> {
387 let builder = self.rebase_with_empty_behavior(EmptyBehavior::Keep).await?;
388 Ok(builder.unwrap())
389 }
390
391 pub fn reparent(self) -> CommitBuilder<'repo> {
394 self.mut_repo
395 .rewrite_commit(&self.old_commit)
396 .set_parents(self.new_parents)
397 }
398}
399
400#[derive(Debug)]
401pub enum RebasedCommit {
402 Rewritten(Commit),
403 Abandoned { parent_id: CommitId },
404}
405
406pub async fn rebase_commit_with_options(
407 mut rewriter: CommitRewriter<'_>,
408 options: &RebaseOptions,
409) -> BackendResult<RebasedCommit> {
410 if options.simplify_ancestor_merge {
412 rewriter
413 .simplify_ancestor_merge()
414 .map_err(|err| BackendError::Other(err.into()))?;
416 }
417
418 let single_parent = match &rewriter.new_parents[..] {
419 [parent_id] => Some(parent_id.clone()),
420 _ => None,
421 };
422 let new_parents_len = rewriter.new_parents.len();
423 if let Some(builder) = rewriter.rebase_with_empty_behavior(options.empty).await? {
424 let new_commit = builder.write().await?;
425 Ok(RebasedCommit::Rewritten(new_commit))
426 } else {
427 assert_eq!(new_parents_len, 1);
428 Ok(RebasedCommit::Abandoned {
429 parent_id: single_parent.unwrap(),
430 })
431 }
432}
433
434pub async fn rebase_to_dest_parent(
437 repo: &dyn Repo,
438 sources: &[Commit],
439 destination: &Commit,
440) -> BackendResult<MergedTree> {
441 if let [source] = sources
442 && source.parent_ids() == destination.parent_ids()
443 {
444 return Ok(source.tree());
445 }
446
447 let diffs: Vec<_> = try_join_all(sources.iter().map(async |source| -> BackendResult<_> {
448 Ok(Diff::new(
449 (
450 source.parent_tree(repo).await?,
451 format!(
452 "{} (original parents)",
453 source.parents_conflict_label().await?
454 ),
455 ),
456 (
457 source.tree(),
458 format!("{} (original revision)", source.conflict_label()),
459 ),
460 ))
461 }))
462 .await?;
463 MergedTree::merge(Merge::from_diffs(
464 (
465 destination.parent_tree(repo).await?,
466 format!(
467 "{} (new parents)",
468 destination.parents_conflict_label().await?
469 ),
470 ),
471 diffs,
472 ))
473 .await
474}
475
476#[derive(Clone, Copy, Default, PartialEq, Eq, Debug)]
477pub enum EmptyBehavior {
478 #[default]
480 Keep,
481 AbandonNewlyEmpty,
485 AbandonAllEmpty,
489}
490
491#[derive(Clone, Debug, Default)]
498pub struct RebaseOptions {
499 pub empty: EmptyBehavior,
500 pub rewrite_refs: RewriteRefsOptions,
501 pub simplify_ancestor_merge: bool,
504}
505
506#[derive(Clone, Debug, Default)]
508pub struct RewriteRefsOptions {
509 pub delete_abandoned_bookmarks: bool,
514}
515
516#[derive(Debug)]
517pub struct MoveCommitsStats {
518 pub num_rebased_targets: u32,
520 pub num_rebased_descendants: u32,
522 pub num_skipped_rebases: u32,
525 pub num_abandoned_empty: u32,
527 pub rebased_commits: HashMap<CommitId, RebasedCommit>,
529}
530
531#[derive(Clone, Debug)]
533pub struct MoveCommitsLocation {
534 pub new_parent_ids: Vec<CommitId>,
535 pub new_child_ids: Vec<CommitId>,
536 pub target: MoveCommitsTarget,
537}
538
539#[derive(Clone, Debug)]
540pub enum MoveCommitsTarget {
541 Commits(Vec<CommitId>),
544 Roots(Vec<CommitId>),
546}
547
548#[derive(Clone, Debug)]
549pub struct ComputedMoveCommits {
550 target_commit_ids: IndexSet<CommitId>,
551 descendants: Vec<Commit>,
552 commit_new_parents_map: HashMap<CommitId, Vec<CommitId>>,
553 to_abandon: HashSet<CommitId>,
554}
555
556impl ComputedMoveCommits {
557 fn empty() -> Self {
558 Self {
559 target_commit_ids: IndexSet::new(),
560 descendants: vec![],
561 commit_new_parents_map: HashMap::new(),
562 to_abandon: HashSet::new(),
563 }
564 }
565
566 pub fn record_to_abandon(&mut self, commit_ids: impl IntoIterator<Item = CommitId>) {
573 self.to_abandon.extend(commit_ids);
574 }
575
576 pub async fn apply(
577 self,
578 mut_repo: &mut MutableRepo,
579 options: &RebaseOptions,
580 ) -> BackendResult<MoveCommitsStats> {
581 apply_move_commits(mut_repo, self, options).await
582 }
583}
584
585pub async fn move_commits(
594 mut_repo: &mut MutableRepo,
595 loc: &MoveCommitsLocation,
596 options: &RebaseOptions,
597) -> BackendResult<MoveCommitsStats> {
598 compute_move_commits(mut_repo, loc)?
599 .apply(mut_repo, options)
600 .await
601}
602
603pub fn compute_move_commits(
604 repo: &MutableRepo,
605 loc: &MoveCommitsLocation,
606) -> BackendResult<ComputedMoveCommits> {
607 let target_commit_ids: IndexSet<CommitId>;
608 let connected_target_commits: Vec<Commit>;
609 let connected_target_commits_internal_parents: HashMap<CommitId, IndexSet<CommitId>>;
610 let target_roots: HashSet<CommitId>;
611
612 match &loc.target {
613 MoveCommitsTarget::Commits(commit_ids) => {
614 if commit_ids.is_empty() {
615 return Ok(ComputedMoveCommits::empty());
616 }
617
618 target_commit_ids = commit_ids.iter().cloned().collect();
619
620 connected_target_commits = RevsetExpression::commits(commit_ids.clone())
621 .connected()
622 .evaluate(repo)
623 .map_err(|err| err.into_backend_error())?
624 .iter()
625 .commits(repo.store())
626 .try_collect()
627 .map_err(|err| err.into_backend_error())?;
628 connected_target_commits_internal_parents =
629 compute_internal_parents_within(&target_commit_ids, &connected_target_commits);
630
631 target_roots = connected_target_commits_internal_parents
632 .iter()
633 .filter(|&(commit_id, parents)| {
634 target_commit_ids.contains(commit_id) && parents.is_empty()
635 })
636 .map(|(commit_id, _)| commit_id.clone())
637 .collect();
638 }
639 MoveCommitsTarget::Roots(root_ids) => {
640 if root_ids.is_empty() {
641 return Ok(ComputedMoveCommits::empty());
642 }
643
644 target_commit_ids = RevsetExpression::commits(root_ids.clone())
645 .descendants()
646 .evaluate(repo)
647 .map_err(|err| err.into_backend_error())?
648 .iter()
649 .try_collect()
650 .map_err(|err| err.into_backend_error())?;
651
652 connected_target_commits = target_commit_ids
653 .iter()
654 .map(|id| repo.store().get_commit(id))
655 .try_collect()?;
656 connected_target_commits_internal_parents = HashMap::new();
659 target_roots = root_ids.iter().cloned().collect();
660 }
661 }
662
663 let mut target_commits_external_parents: HashMap<CommitId, IndexSet<CommitId>> = HashMap::new();
667 for id in target_commit_ids.iter().rev() {
668 let commit = repo.store().get_commit(id)?;
669 let mut new_parents = IndexSet::new();
670 for old_parent in commit.parent_ids() {
671 if let Some(parents) = target_commits_external_parents.get(old_parent) {
672 new_parents.extend(parents.iter().cloned());
673 } else {
674 new_parents.insert(old_parent.clone());
675 }
676 }
677 target_commits_external_parents.insert(commit.id().clone(), new_parents);
678 }
679
680 let new_parent_ids: Vec<_> = loc
684 .new_parent_ids
685 .iter()
686 .flat_map(|parent_id| {
687 if let Some(parent_ids) = target_commits_external_parents.get(parent_id) {
688 parent_ids.iter().cloned().collect_vec()
689 } else {
690 vec![parent_id.clone()]
691 }
692 })
693 .collect();
694
695 let new_children: Vec<_> = if loc
699 .new_child_ids
700 .iter()
701 .any(|id| target_commit_ids.contains(id))
702 {
703 let target_commits_descendants: Vec<_> =
704 RevsetExpression::commits(target_commit_ids.iter().cloned().collect_vec())
705 .union(
706 &RevsetExpression::commits(target_commit_ids.iter().cloned().collect_vec())
707 .children(),
708 )
709 .evaluate(repo)
710 .map_err(|err| err.into_backend_error())?
711 .iter()
712 .commits(repo.store())
713 .try_collect()
714 .map_err(|err| err.into_backend_error())?;
715
716 let mut target_commit_external_descendants: HashMap<CommitId, IndexSet<Commit>> =
719 HashMap::new();
720 for commit in &target_commits_descendants {
723 if !target_commit_external_descendants.contains_key(commit.id()) {
724 let children = if target_commit_ids.contains(commit.id()) {
725 IndexSet::new()
726 } else {
727 IndexSet::from([commit.clone()])
728 };
729 target_commit_external_descendants.insert(commit.id().clone(), children);
730 }
731
732 let children = target_commit_external_descendants
733 .get(commit.id())
734 .unwrap()
735 .iter()
736 .cloned()
737 .collect_vec();
738 for parent_id in commit.parent_ids() {
739 if target_commit_ids.contains(parent_id) {
740 if let Some(target_children) =
741 target_commit_external_descendants.get_mut(parent_id)
742 {
743 target_children.extend(children.iter().cloned());
744 } else {
745 target_commit_external_descendants
746 .insert(parent_id.clone(), children.iter().cloned().collect());
747 }
748 }
749 }
750 }
751
752 let mut new_children = Vec::new();
753 for id in &loc.new_child_ids {
754 if let Some(children) = target_commit_external_descendants.get(id) {
755 new_children.extend(children.iter().cloned());
756 } else {
757 new_children.push(repo.store().get_commit(id)?);
758 }
759 }
760 new_children
761 } else {
762 loc.new_child_ids
763 .iter()
764 .map(|id| repo.store().get_commit(id))
765 .try_collect()?
766 };
767
768 let new_children_parents: HashMap<_, _> = if !new_children.is_empty() {
771 let target_heads = compute_commits_heads(&target_commit_ids, &connected_target_commits);
774
775 new_children
776 .iter()
777 .map(|child_commit| {
778 let mut new_child_parent_ids = IndexSet::new();
779 for old_child_parent_id in child_commit.parent_ids() {
780 let old_child_parent_ids = if let Some(parents) =
782 target_commits_external_parents.get(old_child_parent_id)
783 {
784 parents.iter().collect_vec()
785 } else {
786 vec![old_child_parent_id]
787 };
788
789 for id in old_child_parent_ids {
793 if new_parent_ids.contains(id) {
794 new_child_parent_ids.extend(target_heads.clone());
795 } else {
796 new_child_parent_ids.insert(id.clone());
797 }
798 }
799 }
800
801 new_child_parent_ids.extend(target_heads.clone());
804
805 (
806 child_commit.id().clone(),
807 new_child_parent_ids.into_iter().collect_vec(),
808 )
809 })
810 .collect()
811 } else {
812 HashMap::new()
813 };
814
815 let mut roots = target_roots.iter().cloned().collect_vec();
818 roots.extend(new_children.iter().ids().cloned());
819
820 let descendants = repo.find_descendants_for_rebase(roots.clone())?;
821 let commit_new_parents_map = descendants
822 .iter()
823 .map(|commit| -> BackendResult<_> {
824 let commit_id = commit.id();
825 let new_parent_ids =
826 if let Some(new_child_parents) = new_children_parents.get(commit_id) {
827 new_child_parents.clone()
829 } else if target_commit_ids.contains(commit_id) {
830 if target_roots.contains(commit_id) {
832 new_parent_ids.clone()
835 } else {
836 let mut new_parents = vec![];
844 for parent_id in commit.parent_ids() {
845 if target_commit_ids.contains(parent_id) {
846 new_parents.push(parent_id.clone());
847 } else if let Some(parents) =
848 connected_target_commits_internal_parents.get(parent_id)
849 {
850 new_parents.extend(parents.iter().cloned());
851 } else if !fallible_any(&new_children, |child| {
852 repo.index().is_ancestor(child.id(), parent_id)
853 })
854 .map_err(|err| BackendError::Other(err.into()))?
856 {
857 new_parents.push(parent_id.clone());
858 }
859 }
860 new_parents
861 }
862 } else if commit
863 .parent_ids()
864 .iter()
865 .any(|id| target_commits_external_parents.contains_key(id))
866 {
867 let mut new_parents = vec![];
870 for parent in commit.parent_ids() {
871 if let Some(parents) = target_commits_external_parents.get(parent) {
872 new_parents.extend(parents.iter().cloned());
873 } else {
874 new_parents.push(parent.clone());
875 }
876 }
877 new_parents
878 } else {
879 commit.parent_ids().iter().cloned().collect_vec()
880 };
881 Ok((commit.id().clone(), new_parent_ids))
882 })
883 .try_collect()?;
884
885 Ok(ComputedMoveCommits {
886 target_commit_ids,
887 descendants,
888 commit_new_parents_map,
889 to_abandon: HashSet::new(),
890 })
891}
892
893async fn apply_move_commits(
894 mut_repo: &mut MutableRepo,
895 commits: ComputedMoveCommits,
896 options: &RebaseOptions,
897) -> BackendResult<MoveCommitsStats> {
898 let mut num_rebased_targets = 0;
899 let mut num_rebased_descendants = 0;
900 let mut num_skipped_rebases = 0;
901 let mut num_abandoned_empty = 0;
902
903 let rebase_descendant_options = &RebaseOptions {
906 empty: EmptyBehavior::Keep,
907 rewrite_refs: options.rewrite_refs.clone(),
908 simplify_ancestor_merge: false,
909 };
910
911 let mut rebased_commits: HashMap<CommitId, RebasedCommit> = HashMap::new();
912 mut_repo
913 .transform_commits(
914 commits.descendants,
915 &commits.commit_new_parents_map,
916 &options.rewrite_refs,
917 async |rewriter| {
918 let old_commit_id = rewriter.old_commit().id().clone();
919 if commits.to_abandon.contains(&old_commit_id) {
920 rewriter.abandon();
921 } else if rewriter.parents_changed() {
922 let is_target_commit = commits.target_commit_ids.contains(&old_commit_id);
923 let rebased_commit = rebase_commit_with_options(
924 rewriter,
925 if is_target_commit {
926 options
927 } else {
928 rebase_descendant_options
929 },
930 )
931 .await?;
932 if let RebasedCommit::Abandoned { .. } = rebased_commit {
933 num_abandoned_empty += 1;
934 } else if is_target_commit {
935 num_rebased_targets += 1;
936 } else {
937 num_rebased_descendants += 1;
938 }
939 rebased_commits.insert(old_commit_id, rebased_commit);
940 } else {
941 num_skipped_rebases += 1;
942 }
943
944 Ok(())
945 },
946 )
947 .await?;
948
949 Ok(MoveCommitsStats {
950 num_rebased_targets,
951 num_rebased_descendants,
952 num_skipped_rebases,
953 num_abandoned_empty,
954 rebased_commits,
955 })
956}
957
958#[derive(Debug, Default)]
959pub struct DuplicateCommitsStats {
960 pub duplicated_commits: IndexMap<CommitId, Commit>,
962 pub num_rebased: u32,
965}
966
967pub async fn duplicate_commits(
985 mut_repo: &mut MutableRepo,
986 target_commit_ids: &[CommitId],
987 target_descriptions: &HashMap<CommitId, String>,
988 parent_commit_ids: &[CommitId],
989 children_commit_ids: &[CommitId],
990) -> BackendResult<DuplicateCommitsStats> {
991 if target_commit_ids.is_empty() {
992 return Ok(DuplicateCommitsStats::default());
993 }
994
995 let mut duplicated_old_to_new: IndexMap<CommitId, Commit> = IndexMap::new();
996 let mut num_rebased = 0;
997
998 let target_commit_ids: IndexSet<_> = target_commit_ids.iter().cloned().collect();
999
1000 let connected_target_commits: Vec<_> =
1001 RevsetExpression::commits(target_commit_ids.iter().cloned().collect_vec())
1002 .connected()
1003 .evaluate(mut_repo)
1004 .map_err(|err| err.into_backend_error())?
1005 .iter()
1006 .commits(mut_repo.store())
1007 .try_collect()
1008 .map_err(|err| err.into_backend_error())?;
1009
1010 let target_commits_internal_parents = {
1017 let mut target_commits_internal_parents =
1018 compute_internal_parents_within(&target_commit_ids, &connected_target_commits);
1019 target_commits_internal_parents.retain(|id, _| target_commit_ids.contains(id));
1020 target_commits_internal_parents
1021 };
1022
1023 let target_root_ids: HashSet<_> = target_commits_internal_parents
1025 .iter()
1026 .filter(|(_, parents)| parents.is_empty())
1027 .map(|(commit_id, _)| commit_id.clone())
1028 .collect();
1029
1030 let target_head_ids = if !children_commit_ids.is_empty() {
1033 compute_commits_heads(&target_commit_ids, &connected_target_commits)
1034 } else {
1035 vec![]
1036 };
1037
1038 for original_commit_id in target_commit_ids.iter().rev() {
1041 let original_commit = mut_repo
1042 .store()
1043 .get_commit_async(original_commit_id)
1044 .await?;
1045 let new_parent_ids = if target_root_ids.contains(original_commit_id) {
1046 parent_commit_ids.to_vec()
1047 } else {
1048 target_commits_internal_parents
1049 .get(original_commit_id)
1050 .unwrap()
1051 .iter()
1052 .map(|id| {
1054 duplicated_old_to_new
1055 .get(id)
1056 .map_or(id, |commit| commit.id())
1057 .clone()
1058 })
1059 .collect()
1060 };
1061 let mut new_commit_builder = CommitRewriter::new(mut_repo, original_commit, new_parent_ids)
1062 .rebase()
1063 .await?
1064 .clear_rewrite_source()
1065 .generate_new_change_id();
1066 if let Some(desc) = target_descriptions.get(original_commit_id) {
1067 new_commit_builder = new_commit_builder.set_description(desc);
1068 }
1069 duplicated_old_to_new.insert(
1070 original_commit_id.clone(),
1071 new_commit_builder.write().await?,
1072 );
1073 }
1074
1075 let target_head_ids = target_head_ids
1078 .into_iter()
1079 .map(|commit_id| {
1080 duplicated_old_to_new
1081 .get(&commit_id)
1082 .map_or(commit_id, |commit| commit.id().clone())
1083 })
1084 .collect_vec();
1085
1086 let children_commit_ids_set: HashSet<CommitId> = children_commit_ids.iter().cloned().collect();
1088 mut_repo
1089 .transform_descendants(children_commit_ids.to_vec(), async |mut rewriter| {
1090 if children_commit_ids_set.contains(rewriter.old_commit().id()) {
1091 let mut child_new_parent_ids = IndexSet::new();
1092 for old_parent_id in rewriter.old_commit().parent_ids() {
1093 if parent_commit_ids.contains(old_parent_id) {
1098 child_new_parent_ids.extend(target_head_ids.clone());
1099 } else {
1100 child_new_parent_ids.insert(old_parent_id.clone());
1101 }
1102 }
1103 child_new_parent_ids.extend(target_head_ids.clone());
1106 rewriter.set_new_parents(child_new_parent_ids.into_iter().collect());
1107 }
1108 num_rebased += 1;
1109 rewriter.rebase().await?.write().await?;
1110 Ok(())
1111 })
1112 .await?;
1113
1114 Ok(DuplicateCommitsStats {
1115 duplicated_commits: duplicated_old_to_new,
1116 num_rebased,
1117 })
1118}
1119
1120pub async fn duplicate_commits_onto_parents(
1130 mut_repo: &mut MutableRepo,
1131 target_commits: &[CommitId],
1132 target_descriptions: &HashMap<CommitId, String>,
1133) -> BackendResult<DuplicateCommitsStats> {
1134 if target_commits.is_empty() {
1135 return Ok(DuplicateCommitsStats::default());
1136 }
1137
1138 let mut duplicated_old_to_new: IndexMap<CommitId, Commit> = IndexMap::new();
1139
1140 for original_commit_id in target_commits.iter().rev() {
1143 let original_commit = mut_repo.store().get_commit(original_commit_id)?;
1144 let new_parent_ids = original_commit
1145 .parent_ids()
1146 .iter()
1147 .map(|id| {
1148 duplicated_old_to_new
1149 .get(id)
1150 .map_or(id, |commit| commit.id())
1151 .clone()
1152 })
1153 .collect();
1154 let mut new_commit_builder = mut_repo
1155 .rewrite_commit(&original_commit)
1156 .clear_rewrite_source()
1157 .generate_new_change_id()
1158 .set_parents(new_parent_ids);
1159 if let Some(desc) = target_descriptions.get(original_commit_id) {
1160 new_commit_builder = new_commit_builder.set_description(desc);
1161 }
1162 duplicated_old_to_new.insert(
1163 original_commit_id.clone(),
1164 new_commit_builder.write().await?,
1165 );
1166 }
1167
1168 Ok(DuplicateCommitsStats {
1169 duplicated_commits: duplicated_old_to_new,
1170 num_rebased: 0,
1171 })
1172}
1173
1174fn compute_internal_parents_within(
1182 target_commit_ids: &IndexSet<CommitId>,
1183 graph_commits: &[Commit],
1184) -> HashMap<CommitId, IndexSet<CommitId>> {
1185 let mut internal_parents: HashMap<CommitId, IndexSet<CommitId>> = HashMap::new();
1186 for commit in graph_commits.iter().rev() {
1187 let mut new_parents = IndexSet::new();
1190 for old_parent in commit.parent_ids() {
1191 if target_commit_ids.contains(old_parent) {
1192 new_parents.insert(old_parent.clone());
1193 } else if let Some(parents) = internal_parents.get(old_parent) {
1194 new_parents.extend(parents.iter().cloned());
1195 }
1196 }
1197 internal_parents.insert(commit.id().clone(), new_parents);
1198 }
1199 internal_parents
1200}
1201
1202fn compute_commits_heads(
1208 target_commit_ids: &IndexSet<CommitId>,
1209 connected_target_commits: &[Commit],
1210) -> Vec<CommitId> {
1211 let mut target_head_ids: HashSet<CommitId> = HashSet::new();
1212 for commit in connected_target_commits.iter().rev() {
1213 target_head_ids.insert(commit.id().clone());
1214 for old_parent in commit.parent_ids() {
1215 target_head_ids.remove(old_parent);
1216 }
1217 }
1218 connected_target_commits
1219 .iter()
1220 .rev()
1221 .filter(|commit| {
1222 target_head_ids.contains(commit.id()) && target_commit_ids.contains(commit.id())
1223 })
1224 .map(|commit| commit.id().clone())
1225 .collect_vec()
1226}
1227
1228#[derive(Debug)]
1229pub struct CommitWithSelection {
1230 pub commit: Commit,
1231 pub selected_tree: MergedTree,
1232 pub parent_tree: MergedTree,
1233}
1234
1235impl CommitWithSelection {
1236 pub fn is_full_selection(&self) -> bool {
1238 self.selected_tree.tree_ids() == self.commit.tree_ids()
1239 }
1240
1241 pub fn is_empty_selection(&self) -> bool {
1247 self.selected_tree.tree_ids() == self.parent_tree.tree_ids()
1248 }
1249
1250 pub async fn diff_with_labels(
1254 &self,
1255 parent_tree_label: &str,
1256 selected_tree_label: &str,
1257 full_selection_label: &str,
1258 ) -> BackendResult<Diff<(MergedTree, String)>> {
1259 let parent_tree_label = format!(
1260 "{} ({parent_tree_label})",
1261 self.commit.parents_conflict_label().await?
1262 );
1263
1264 let commit_label = self.commit.conflict_label();
1265 let selected_tree_label = if self.is_full_selection() {
1266 format!("{commit_label} ({full_selection_label})")
1267 } else {
1268 format!("{selected_tree_label} (from {commit_label})")
1269 };
1270
1271 Ok(Diff::new(
1272 (self.parent_tree.clone(), parent_tree_label),
1273 (self.selected_tree.clone(), selected_tree_label),
1274 ))
1275 }
1276}
1277
1278#[must_use]
1280pub struct SquashedCommit<'repo> {
1281 pub commit_builder: CommitBuilder<'repo>,
1283 pub abandoned_commits: Vec<Commit>,
1285}
1286
1287pub async fn squash_commits<'repo>(
1291 repo: &'repo mut MutableRepo,
1292 sources: &[CommitWithSelection],
1293 destination: &Commit,
1294 keep_emptied: bool,
1295) -> BackendResult<Option<SquashedCommit<'repo>>> {
1296 struct SourceCommit<'a> {
1297 commit: &'a CommitWithSelection,
1298 diff: Diff<(MergedTree, String)>,
1299 abandon: bool,
1300 }
1301 let mut source_commits = vec![];
1302 for source in sources {
1303 let abandon = !keep_emptied && source.is_full_selection();
1304 if !abandon && source.is_empty_selection() {
1305 continue;
1309 }
1310
1311 source_commits.push(SourceCommit {
1314 commit: source,
1315 diff: source
1316 .diff_with_labels(
1317 "parents of squashed revision",
1318 "selected changes for squash",
1319 "squashed revision",
1320 )
1321 .await?,
1322 abandon,
1323 });
1324 }
1325
1326 if source_commits.is_empty() {
1327 return Ok(None);
1328 }
1329
1330 let mut abandoned_commits = vec![];
1331 for source in &source_commits {
1332 if source.abandon {
1333 repo.record_abandoned_commit(&source.commit.commit);
1334 abandoned_commits.push(source.commit.commit.clone());
1335 } else {
1336 let source_tree = source.commit.commit.tree();
1337 let new_source_tree = MergedTree::merge(Merge::from_diffs(
1339 (source_tree, source.commit.commit.conflict_label()),
1340 [source.diff.clone().invert()],
1341 ))
1342 .await?;
1343 repo.rewrite_commit(&source.commit.commit)
1344 .set_tree(new_source_tree)
1345 .write()
1346 .await?;
1347 }
1348 }
1349
1350 let mut rewritten_destination = destination.clone();
1351 if fallible_any(sources, |source| {
1352 repo.index()
1353 .is_ancestor(source.commit.id(), destination.id())
1354 })
1355 .map_err(|err| BackendError::Other(err.into()))?
1357 {
1358 let options = RebaseOptions::default();
1363 repo.rebase_descendants_with_options(&options, |old_commit, rebased_commit| {
1364 if old_commit.id() != destination.id() {
1365 return;
1366 }
1367 rewritten_destination = match rebased_commit {
1368 RebasedCommit::Rewritten(commit) => commit,
1369 RebasedCommit::Abandoned { .. } => panic!("all commits should be kept"),
1370 };
1371 })
1372 .await?;
1373 }
1374 let mut predecessors = vec![destination.id().clone()];
1375 predecessors.extend(
1376 source_commits
1377 .iter()
1378 .map(|source| source.commit.commit.id().clone()),
1379 );
1380 let destination_tree = MergedTree::merge(Merge::from_diffs(
1382 (
1383 rewritten_destination.tree(),
1384 format!("{} (squash destination)", destination.conflict_label()),
1385 ),
1386 source_commits.into_iter().map(|source| source.diff),
1387 ))
1388 .await?;
1389
1390 let commit_builder = repo
1391 .rewrite_commit(&rewritten_destination)
1392 .set_tree(destination_tree)
1393 .set_predecessors(predecessors);
1394 Ok(Some(SquashedCommit {
1395 commit_builder,
1396 abandoned_commits,
1397 }))
1398}
1399
1400pub async fn find_duplicate_divergent_commits(
1404 repo: &dyn Repo,
1405 new_parent_ids: &[CommitId],
1406 target: &MoveCommitsTarget,
1407) -> BackendResult<Vec<Commit>> {
1408 let target_commits: Vec<Commit> = match target {
1409 MoveCommitsTarget::Commits(commit_ids) => commit_ids
1410 .iter()
1411 .map(|commit_id| repo.store().get_commit(commit_id))
1412 .try_collect()?,
1413 MoveCommitsTarget::Roots(root_ids) => RevsetExpression::commits(root_ids.clone())
1414 .descendants()
1415 .evaluate(repo)
1416 .map_err(|err| err.into_backend_error())?
1417 .iter()
1418 .commits(repo.store())
1419 .try_collect()
1420 .map_err(|err| err.into_backend_error())?,
1421 };
1422 let target_commit_ids: HashSet<&CommitId> = target_commits.iter().map(Commit::id).collect();
1423
1424 let divergent_changes: Vec<(&Commit, Vec<CommitId>)> = target_commits
1427 .iter()
1428 .map(|target_commit| -> Result<_, BackendError> {
1429 let mut ancestor_candidates = repo
1430 .resolve_change_id(target_commit.change_id())
1431 .map_err(|err| BackendError::Other(err.into()))?
1433 .and_then(ResolvedChangeTargets::into_visible)
1434 .unwrap_or_default();
1435 ancestor_candidates.retain(|commit_id| !target_commit_ids.contains(commit_id));
1436 Ok((target_commit, ancestor_candidates))
1437 })
1438 .filter_ok(|(_, candidates)| !candidates.is_empty())
1439 .try_collect()?;
1440 if divergent_changes.is_empty() {
1441 return Ok(Vec::new());
1442 }
1443
1444 let target_root_ids = match target {
1445 MoveCommitsTarget::Commits(commit_ids) => commit_ids,
1446 MoveCommitsTarget::Roots(root_ids) => root_ids,
1447 };
1448
1449 let is_new_ancestor = RevsetExpression::commits(target_root_ids.clone())
1452 .range(&RevsetExpression::commits(new_parent_ids.to_owned()))
1453 .evaluate(repo)
1454 .map_err(|err| err.into_backend_error())?
1455 .containing_fn();
1456
1457 let mut duplicate_divergent = Vec::new();
1458 for (target_commit, ancestor_candidates) in divergent_changes {
1463 for ancestor_candidate_id in ancestor_candidates {
1464 if !is_new_ancestor(&ancestor_candidate_id).map_err(|err| err.into_backend_error())? {
1465 continue;
1466 }
1467
1468 let ancestor_candidate = repo.store().get_commit(&ancestor_candidate_id)?;
1469 let new_tree =
1470 rebase_to_dest_parent(repo, slice::from_ref(target_commit), &ancestor_candidate)
1471 .await?;
1472 if new_tree.tree_ids() == ancestor_candidate.tree_ids() {
1475 duplicate_divergent.push(target_commit.clone());
1476 break;
1477 }
1478 }
1479 }
1480 Ok(duplicate_divergent)
1481}