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::TryStreamExt as _;
24use futures::future::try_join_all;
25use futures::try_join;
26use indexmap::IndexMap;
27use indexmap::IndexSet;
28use itertools::Itertools as _;
29use tracing::instrument;
30
31use crate::backend::BackendError;
32use crate::backend::BackendResult;
33use crate::backend::CommitId;
34use crate::commit::Commit;
35use crate::commit::CommitIteratorExt as _;
36use crate::commit::conflict_label_for_commits;
37use crate::commit_builder::CommitBuilder;
38use crate::conflict_labels::ConflictLabels;
39use crate::index::Index;
40use crate::index::IndexResult;
41use crate::index::ResolvedChangeTargets;
42use crate::iter_util::fallible_any;
43use crate::matchers::FilesMatcher;
44use crate::matchers::Matcher;
45use crate::matchers::Visit;
46use crate::merge::Diff;
47use crate::merge::Merge;
48use crate::merged_tree::MergedTree;
49use crate::merged_tree_builder::MergedTreeBuilder;
50use crate::repo::MutableRepo;
51use crate::repo::Repo;
52use crate::repo_path::RepoPath;
53use crate::revset::RevsetExpression;
54use crate::revset::RevsetStreamExt as _;
55use crate::store::Store;
56
57#[instrument(skip(repo))]
59pub async fn merge_commit_trees(repo: &dyn Repo, commits: &[Commit]) -> BackendResult<MergedTree> {
60 if let [commit] = commits {
61 Ok(commit.tree())
62 } else {
63 merge_commit_trees_no_resolve_without_repo(repo.store(), repo.index(), commits)
64 .await?
65 .resolve()
66 .await
67 }
68}
69
70#[instrument(skip(index))]
72pub async fn merge_commit_trees_no_resolve_without_repo(
73 store: &Arc<Store>,
74 index: &dyn Index,
75 commits: &[Commit],
76) -> BackendResult<MergedTree> {
77 let commit_ids = commits
78 .iter()
79 .map(|commit| commit.id().clone())
80 .collect_vec();
81 let commit_id_merge = find_recursive_merge_commits(store, index, commit_ids)?;
82 let tree_merge: Merge<(MergedTree, String)> = commit_id_merge
83 .try_map_async(async |commit_id| {
84 let commit = store.get_commit_async(commit_id).await?;
85 Ok::<_, BackendError>((commit.tree(), commit.conflict_label()))
86 })
87 .await?;
88 Ok(MergedTree::merge_no_resolve(tree_merge))
89}
90
91pub fn find_recursive_merge_commits(
93 store: &Arc<Store>,
94 index: &dyn Index,
95 mut commit_ids: Vec<CommitId>,
96) -> BackendResult<Merge<CommitId>> {
97 if commit_ids.is_empty() {
98 Ok(Merge::resolved(store.root_commit_id().clone()))
99 } else if commit_ids.len() == 1 {
100 Ok(Merge::resolved(commit_ids.pop().unwrap()))
101 } else {
102 let mut result = Merge::resolved(commit_ids[0].clone());
103 for (i, other_commit_id) in commit_ids.iter().enumerate().skip(1) {
104 let ancestor_ids = index
105 .common_ancestors(&commit_ids[0..i], &commit_ids[i..][..1])
106 .map_err(|err| BackendError::Other(err.into()))?;
108 let ancestor_merge = find_recursive_merge_commits(store, index, ancestor_ids)?;
109 result = Merge::from_vec(vec![
110 result,
111 ancestor_merge,
112 Merge::resolved(other_commit_id.clone()),
113 ])
114 .flatten();
115 }
116 Ok(result)
117 }
118}
119
120pub async fn restore_tree(
122 source: &MergedTree,
123 destination: &MergedTree,
124 source_label: String,
125 destination_label: String,
126 matcher: &dyn Matcher,
127) -> BackendResult<MergedTree> {
128 if matcher.visit(RepoPath::root()) == Visit::AllRecursively {
129 return Ok(source.clone());
131 }
132 let mut diff_stream = source.diff_stream(destination, matcher);
133 let mut paths = Vec::new();
134 while let Some(entry) = diff_stream.next().await {
135 paths.push(entry.path);
138 }
139 let matcher = FilesMatcher::new(paths);
140
141 let select_matching =
142 async |tree: &MergedTree, labels: ConflictLabels| -> BackendResult<MergedTree> {
143 let empty_tree_ids = Merge::repeated(
144 tree.store().empty_tree_id().clone(),
145 tree.tree_ids().num_sides(),
146 );
147 let labeled_empty_tree = MergedTree::new(tree.store().clone(), empty_tree_ids, labels);
148 let mut builder = MergedTreeBuilder::new(labeled_empty_tree);
149 for (path, value) in tree.entries_matching(&matcher) {
150 builder.set_or_remove(path, value?);
153 }
154 builder.write_tree().await
155 };
156
157 const RESTORE_BASE_LABEL: &str = "base files for restore";
158
159 let base_labels = ConflictLabels::from_merge(destination.labels().as_merge().map(|label| {
162 if label.is_empty() || label.starts_with(RESTORE_BASE_LABEL) {
163 label.clone()
164 } else {
165 format!("{RESTORE_BASE_LABEL} (from {label})")
166 }
167 }));
168
169 MergedTree::merge(Merge::from_vec(vec![
181 (
182 destination.clone(),
183 format!("{destination_label} (restore destination)"),
184 ),
185 (
186 select_matching(destination, base_labels).await?,
187 format!("{RESTORE_BASE_LABEL} (from {destination_label})"),
188 ),
189 (
190 select_matching(source, source.labels().clone()).await?,
191 format!("restored files (from {source_label})"),
192 ),
193 ]))
194 .await
195}
196
197pub async fn rebase_commit(
198 mut_repo: &mut MutableRepo,
199 old_commit: Commit,
200 new_parents: Vec<CommitId>,
201) -> BackendResult<Commit> {
202 let rewriter = CommitRewriter::new(mut_repo, old_commit, new_parents);
203 let builder = rewriter.rebase().await?;
204 builder.write().await
205}
206
207pub struct CommitRewriter<'repo> {
209 mut_repo: &'repo mut MutableRepo,
210 old_commit: Commit,
211 new_parents: Vec<CommitId>,
212}
213
214impl<'repo> CommitRewriter<'repo> {
215 pub fn new(
217 mut_repo: &'repo mut MutableRepo,
218 old_commit: Commit,
219 new_parents: Vec<CommitId>,
220 ) -> Self {
221 Self {
222 mut_repo,
223 old_commit,
224 new_parents,
225 }
226 }
227
228 pub fn repo_mut(&mut self) -> &mut MutableRepo {
230 self.mut_repo
231 }
232
233 pub fn old_commit(&self) -> &Commit {
235 &self.old_commit
236 }
237
238 pub fn new_parents(&self) -> &[CommitId] {
240 &self.new_parents
241 }
242
243 pub fn set_new_parents(&mut self, new_parents: Vec<CommitId>) {
245 self.new_parents = new_parents;
246 }
247
248 pub fn set_new_rewritten_parents(&mut self, unrewritten_parents: &[CommitId]) {
251 self.new_parents = self.mut_repo.new_parents(unrewritten_parents);
252 }
253
254 pub fn replace_parent<'a>(
257 &mut self,
258 old_parent: &CommitId,
259 new_parents: impl IntoIterator<Item = &'a CommitId>,
260 ) {
261 if let Some(i) = self.new_parents.iter().position(|p| p == old_parent) {
262 self.new_parents
263 .splice(i..i + 1, new_parents.into_iter().cloned());
264 let mut unique = HashSet::new();
265 self.new_parents.retain(|p| unique.insert(p.clone()));
266 }
267 }
268
269 pub fn parents_changed(&self) -> bool {
272 self.new_parents != self.old_commit.parent_ids()
273 }
274
275 pub fn simplify_ancestor_merge(&mut self) -> IndexResult<()> {
278 let head_set: HashSet<_> = self
279 .mut_repo
280 .index()
281 .heads(&mut self.new_parents.iter())?
282 .into_iter()
283 .collect();
284 self.new_parents.retain(|parent| head_set.contains(parent));
285 Ok(())
286 }
287
288 pub fn abandon(self) {
292 let old_commit_id = self.old_commit.id().clone();
293 let new_parents = self.new_parents;
294 self.mut_repo
295 .record_abandoned_commit_with_parents(old_commit_id, new_parents);
296 }
297
298 pub async fn rebase_with_empty_behavior(
301 self,
302 empty: EmptyBehavior,
303 ) -> BackendResult<Option<CommitBuilder<'repo>>> {
304 let old_parents_fut = self.old_commit.parents();
305 let new_parents_fut = try_join_all(
306 self.new_parents
307 .iter()
308 .map(|new_parent_id| self.mut_repo.store().get_commit_async(new_parent_id)),
309 );
310 let (old_parents, new_parents) = try_join!(old_parents_fut, new_parents_fut)?;
311 let old_parent_trees = old_parents
312 .iter()
313 .map(|parent| parent.tree_ids().clone())
314 .collect_vec();
315 let new_parent_trees = new_parents
316 .iter()
317 .map(|parent| parent.tree_ids().clone())
318 .collect_vec();
319
320 let (was_empty, new_tree) = if new_parent_trees == old_parent_trees {
321 (
322 true,
325 self.old_commit.tree(),
327 )
328 } else {
329 let old_base_tree_fut = merge_commit_trees(self.mut_repo, &old_parents);
333 let new_base_tree_fut = merge_commit_trees(self.mut_repo, &new_parents);
334 let old_tree = self.old_commit.tree();
335 let (old_base_tree, new_base_tree) = try_join!(old_base_tree_fut, new_base_tree_fut)?;
336 (
337 old_base_tree.tree_ids() == self.old_commit.tree_ids(),
338 MergedTree::merge(Merge::from_vec(vec![
339 (
340 new_base_tree,
341 format!(
342 "{} (rebase destination)",
343 conflict_label_for_commits(&new_parents)
344 ),
345 ),
346 (
347 old_base_tree,
348 format!(
349 "{} (parents of rebased revision)",
350 conflict_label_for_commits(&old_parents)
351 ),
352 ),
353 (
354 old_tree,
355 format!("{} (rebased revision)", self.old_commit.conflict_label()),
356 ),
357 ]))
358 .await?,
359 )
360 };
361 if let [parent] = &new_parents[..] {
364 let should_abandon = match empty {
365 EmptyBehavior::Keep => false,
366 EmptyBehavior::AbandonNewlyEmpty => {
367 parent.tree_ids() == new_tree.tree_ids() && !was_empty
368 }
369 EmptyBehavior::AbandonAllEmpty => parent.tree_ids() == new_tree.tree_ids(),
370 };
371 if should_abandon {
372 self.abandon();
373 return Ok(None);
374 }
375 }
376
377 let builder = self
378 .mut_repo
379 .rewrite_commit(&self.old_commit)
380 .set_parents(self.new_parents)
381 .set_tree(new_tree);
382 Ok(Some(builder))
383 }
384
385 pub async fn rebase(self) -> BackendResult<CommitBuilder<'repo>> {
388 let builder = self.rebase_with_empty_behavior(EmptyBehavior::Keep).await?;
389 Ok(builder.unwrap())
390 }
391
392 pub fn reparent(self) -> CommitBuilder<'repo> {
395 self.mut_repo
396 .rewrite_commit(&self.old_commit)
397 .set_parents(self.new_parents)
398 }
399}
400
401#[derive(Debug)]
402pub enum RebasedCommit {
403 Rewritten(Commit),
404 Abandoned { parent_id: CommitId },
405}
406
407pub async fn rebase_commit_with_options(
408 mut rewriter: CommitRewriter<'_>,
409 options: &RebaseOptions,
410) -> BackendResult<RebasedCommit> {
411 if options.simplify_ancestor_merge {
413 rewriter
414 .simplify_ancestor_merge()
415 .map_err(|err| BackendError::Other(err.into()))?;
417 }
418
419 let single_parent = match &rewriter.new_parents[..] {
420 [parent_id] => Some(parent_id.clone()),
421 _ => None,
422 };
423 let new_parents_len = rewriter.new_parents.len();
424 if let Some(builder) = rewriter.rebase_with_empty_behavior(options.empty).await? {
425 let new_commit = builder.write().await?;
426 Ok(RebasedCommit::Rewritten(new_commit))
427 } else {
428 assert_eq!(new_parents_len, 1);
429 Ok(RebasedCommit::Abandoned {
430 parent_id: single_parent.unwrap(),
431 })
432 }
433}
434
435pub async fn rebase_to_dest_parent(
438 repo: &dyn Repo,
439 sources: &[Commit],
440 destination: &Commit,
441) -> BackendResult<MergedTree> {
442 if let [source] = sources
443 && source.parent_ids() == destination.parent_ids()
444 {
445 return Ok(source.tree());
446 }
447
448 let diffs: Vec<_> = try_join_all(sources.iter().map(async |source| -> BackendResult<_> {
449 Ok(Diff::new(
450 (
451 source.parent_tree(repo).await?,
452 format!(
453 "{} (original parents)",
454 source.parents_conflict_label().await?
455 ),
456 ),
457 (
458 source.tree(),
459 format!("{} (original revision)", source.conflict_label()),
460 ),
461 ))
462 }))
463 .await?;
464 MergedTree::merge(Merge::from_diffs(
465 (
466 destination.parent_tree(repo).await?,
467 format!(
468 "{} (new parents)",
469 destination.parents_conflict_label().await?
470 ),
471 ),
472 diffs,
473 ))
474 .await
475}
476
477#[derive(Clone, Copy, Default, PartialEq, Eq, Debug)]
478pub enum EmptyBehavior {
479 #[default]
481 Keep,
482 AbandonNewlyEmpty,
486 AbandonAllEmpty,
490}
491
492#[derive(Clone, Debug, Default)]
499pub struct RebaseOptions {
500 pub empty: EmptyBehavior,
501 pub rewrite_refs: RewriteRefsOptions,
502 pub simplify_ancestor_merge: bool,
505}
506
507#[derive(Clone, Debug, Default)]
509pub struct RewriteRefsOptions {
510 pub delete_abandoned_bookmarks: bool,
515}
516
517#[derive(Debug)]
518pub struct MoveCommitsStats {
519 pub num_rebased_targets: u32,
521 pub num_rebased_descendants: u32,
523 pub num_skipped_rebases: u32,
526 pub num_abandoned_empty: u32,
528 pub rebased_commits: HashMap<CommitId, RebasedCommit>,
530}
531
532#[derive(Clone, Debug)]
534pub struct MoveCommitsLocation {
535 pub new_parent_ids: Vec<CommitId>,
536 pub new_child_ids: Vec<CommitId>,
537 pub target: MoveCommitsTarget,
538}
539
540#[derive(Clone, Debug)]
541pub enum MoveCommitsTarget {
542 Commits(Vec<CommitId>),
545 Roots(Vec<CommitId>),
547}
548
549#[derive(Clone, Debug)]
550pub struct ComputedMoveCommits {
551 target_commit_ids: IndexSet<CommitId>,
552 descendants: Vec<Commit>,
553 commit_new_parents_map: HashMap<CommitId, Vec<CommitId>>,
554 to_abandon: HashSet<CommitId>,
555}
556
557impl ComputedMoveCommits {
558 fn empty() -> Self {
559 Self {
560 target_commit_ids: IndexSet::new(),
561 descendants: vec![],
562 commit_new_parents_map: HashMap::new(),
563 to_abandon: HashSet::new(),
564 }
565 }
566
567 pub fn record_to_abandon(&mut self, commit_ids: impl IntoIterator<Item = CommitId>) {
574 self.to_abandon.extend(commit_ids);
575 }
576
577 pub async fn apply(
578 self,
579 mut_repo: &mut MutableRepo,
580 options: &RebaseOptions,
581 ) -> BackendResult<MoveCommitsStats> {
582 apply_move_commits(mut_repo, self, options).await
583 }
584}
585
586pub async fn move_commits(
595 mut_repo: &mut MutableRepo,
596 loc: &MoveCommitsLocation,
597 options: &RebaseOptions,
598) -> BackendResult<MoveCommitsStats> {
599 compute_move_commits(mut_repo, loc)
600 .await?
601 .apply(mut_repo, options)
602 .await
603}
604
605pub async fn compute_move_commits(
606 repo: &MutableRepo,
607 loc: &MoveCommitsLocation,
608) -> BackendResult<ComputedMoveCommits> {
609 let target_commit_ids: IndexSet<CommitId>;
610 let connected_target_commits: Vec<Commit>;
611 let connected_target_commits_internal_parents: HashMap<CommitId, IndexSet<CommitId>>;
612 let target_roots: HashSet<CommitId>;
613
614 match &loc.target {
615 MoveCommitsTarget::Commits(commit_ids) => {
616 if commit_ids.is_empty() {
617 return Ok(ComputedMoveCommits::empty());
618 }
619
620 target_commit_ids = commit_ids.iter().cloned().collect();
621
622 connected_target_commits = RevsetExpression::commits(commit_ids.clone())
623 .connected()
624 .evaluate(repo)
625 .map_err(|err| err.into_backend_error())?
626 .stream()
627 .commits(repo.store())
628 .try_collect()
629 .await
630 .map_err(|err| err.into_backend_error())?;
631 connected_target_commits_internal_parents =
632 compute_internal_parents_within(&target_commit_ids, &connected_target_commits);
633
634 target_roots = connected_target_commits_internal_parents
635 .iter()
636 .filter(|&(commit_id, parents)| {
637 target_commit_ids.contains(commit_id) && parents.is_empty()
638 })
639 .map(|(commit_id, _)| commit_id.clone())
640 .collect();
641 }
642 MoveCommitsTarget::Roots(root_ids) => {
643 if root_ids.is_empty() {
644 return Ok(ComputedMoveCommits::empty());
645 }
646
647 target_commit_ids = RevsetExpression::commits(root_ids.clone())
648 .descendants()
649 .evaluate(repo)
650 .map_err(|err| err.into_backend_error())?
651 .stream()
652 .try_collect()
653 .await
654 .map_err(|err| err.into_backend_error())?;
655
656 connected_target_commits = try_join_all(
657 target_commit_ids
658 .iter()
659 .map(|id| repo.store().get_commit_async(id)),
660 )
661 .await?;
662 connected_target_commits_internal_parents = HashMap::new();
665 target_roots = root_ids.iter().cloned().collect();
666 }
667 }
668
669 let mut target_commits_external_parents: HashMap<CommitId, IndexSet<CommitId>> = HashMap::new();
673 for id in target_commit_ids.iter().rev() {
674 let commit = repo.store().get_commit_async(id).await?;
675 let mut new_parents = IndexSet::new();
676 for old_parent in commit.parent_ids() {
677 if let Some(parents) = target_commits_external_parents.get(old_parent) {
678 new_parents.extend(parents.iter().cloned());
679 } else {
680 new_parents.insert(old_parent.clone());
681 }
682 }
683 target_commits_external_parents.insert(commit.id().clone(), new_parents);
684 }
685
686 let new_parent_ids: Vec<_> = loc
690 .new_parent_ids
691 .iter()
692 .flat_map(|parent_id| {
693 if let Some(parent_ids) = target_commits_external_parents.get(parent_id) {
694 parent_ids.iter().cloned().collect_vec()
695 } else {
696 vec![parent_id.clone()]
697 }
698 })
699 .collect();
700
701 let new_children: Vec<_> = if loc
705 .new_child_ids
706 .iter()
707 .any(|id| target_commit_ids.contains(id))
708 {
709 let target_commits_descendants: Vec<_> =
710 RevsetExpression::commits(target_commit_ids.iter().cloned().collect_vec())
711 .union(
712 &RevsetExpression::commits(target_commit_ids.iter().cloned().collect_vec())
713 .children(),
714 )
715 .evaluate(repo)
716 .map_err(|err| err.into_backend_error())?
717 .stream()
718 .commits(repo.store())
719 .try_collect()
720 .await
721 .map_err(|err| err.into_backend_error())?;
722
723 let mut target_commit_external_descendants: HashMap<CommitId, IndexSet<Commit>> =
726 HashMap::new();
727 for commit in &target_commits_descendants {
730 if !target_commit_external_descendants.contains_key(commit.id()) {
731 let children = if target_commit_ids.contains(commit.id()) {
732 IndexSet::new()
733 } else {
734 IndexSet::from([commit.clone()])
735 };
736 target_commit_external_descendants.insert(commit.id().clone(), children);
737 }
738
739 let children = target_commit_external_descendants
740 .get(commit.id())
741 .unwrap()
742 .iter()
743 .cloned()
744 .collect_vec();
745 for parent_id in commit.parent_ids() {
746 if target_commit_ids.contains(parent_id) {
747 if let Some(target_children) =
748 target_commit_external_descendants.get_mut(parent_id)
749 {
750 target_children.extend(children.iter().cloned());
751 } else {
752 target_commit_external_descendants
753 .insert(parent_id.clone(), children.iter().cloned().collect());
754 }
755 }
756 }
757 }
758
759 let mut new_children = Vec::new();
760 for id in &loc.new_child_ids {
761 if let Some(children) = target_commit_external_descendants.get(id) {
762 new_children.extend(children.iter().cloned());
763 } else {
764 new_children.push(repo.store().get_commit_async(id).await?);
765 }
766 }
767 new_children
768 } else {
769 try_join_all(
770 loc.new_child_ids
771 .iter()
772 .map(|id| repo.store().get_commit_async(id)),
773 )
774 .await?
775 };
776
777 let new_children_parents: HashMap<_, _> = if !new_children.is_empty() {
780 let target_heads = compute_commits_heads(&target_commit_ids, &connected_target_commits);
783
784 new_children
785 .iter()
786 .map(|child_commit| {
787 let mut new_child_parent_ids = IndexSet::new();
788 for old_child_parent_id in child_commit.parent_ids() {
789 let old_child_parent_ids = if let Some(parents) =
791 target_commits_external_parents.get(old_child_parent_id)
792 {
793 parents.iter().collect_vec()
794 } else {
795 vec![old_child_parent_id]
796 };
797
798 for id in old_child_parent_ids {
802 if new_parent_ids.contains(id) {
803 new_child_parent_ids.extend(target_heads.clone());
804 } else {
805 new_child_parent_ids.insert(id.clone());
806 }
807 }
808 }
809
810 new_child_parent_ids.extend(target_heads.clone());
813
814 (
815 child_commit.id().clone(),
816 new_child_parent_ids.into_iter().collect_vec(),
817 )
818 })
819 .collect()
820 } else {
821 HashMap::new()
822 };
823
824 let mut roots = target_roots.iter().cloned().collect_vec();
827 roots.extend(new_children.iter().ids().cloned());
828
829 let descendants = repo.find_descendants_for_rebase(roots.clone()).await?;
830 let commit_new_parents_map = descendants
831 .iter()
832 .map(|commit| -> BackendResult<_> {
833 let commit_id = commit.id();
834 let new_parent_ids =
835 if let Some(new_child_parents) = new_children_parents.get(commit_id) {
836 new_child_parents.clone()
838 } else if target_commit_ids.contains(commit_id) {
839 if target_roots.contains(commit_id) {
841 new_parent_ids.clone()
844 } else {
845 let mut new_parents = vec![];
853 for parent_id in commit.parent_ids() {
854 if target_commit_ids.contains(parent_id) {
855 new_parents.push(parent_id.clone());
856 } else if let Some(parents) =
857 connected_target_commits_internal_parents.get(parent_id)
858 {
859 new_parents.extend(parents.iter().cloned());
860 } else if !fallible_any(&new_children, |child| {
861 repo.index().is_ancestor(child.id(), parent_id)
862 })
863 .map_err(|err| BackendError::Other(err.into()))?
865 {
866 new_parents.push(parent_id.clone());
867 }
868 }
869 new_parents
870 }
871 } else if commit
872 .parent_ids()
873 .iter()
874 .any(|id| target_commits_external_parents.contains_key(id))
875 {
876 let mut new_parents = vec![];
879 for parent in commit.parent_ids() {
880 if let Some(parents) = target_commits_external_parents.get(parent) {
881 new_parents.extend(parents.iter().cloned());
882 } else {
883 new_parents.push(parent.clone());
884 }
885 }
886 new_parents
887 } else {
888 commit.parent_ids().iter().cloned().collect_vec()
889 };
890 Ok((commit.id().clone(), new_parent_ids))
891 })
892 .try_collect()?;
893
894 Ok(ComputedMoveCommits {
895 target_commit_ids,
896 descendants,
897 commit_new_parents_map,
898 to_abandon: HashSet::new(),
899 })
900}
901
902async fn apply_move_commits(
903 mut_repo: &mut MutableRepo,
904 commits: ComputedMoveCommits,
905 options: &RebaseOptions,
906) -> BackendResult<MoveCommitsStats> {
907 let mut num_rebased_targets = 0;
908 let mut num_rebased_descendants = 0;
909 let mut num_skipped_rebases = 0;
910 let mut num_abandoned_empty = 0;
911
912 let rebase_descendant_options = &RebaseOptions {
915 empty: EmptyBehavior::Keep,
916 rewrite_refs: options.rewrite_refs.clone(),
917 simplify_ancestor_merge: false,
918 };
919
920 let mut rebased_commits: HashMap<CommitId, RebasedCommit> = HashMap::new();
921 mut_repo
922 .transform_commits(
923 commits.descendants,
924 &commits.commit_new_parents_map,
925 &options.rewrite_refs,
926 async |rewriter| {
927 let old_commit_id = rewriter.old_commit().id().clone();
928 if commits.to_abandon.contains(&old_commit_id) {
929 rewriter.abandon();
930 } else if rewriter.parents_changed() {
931 let is_target_commit = commits.target_commit_ids.contains(&old_commit_id);
932 let rebased_commit = rebase_commit_with_options(
933 rewriter,
934 if is_target_commit {
935 options
936 } else {
937 rebase_descendant_options
938 },
939 )
940 .await?;
941 if let RebasedCommit::Abandoned { .. } = rebased_commit {
942 num_abandoned_empty += 1;
943 } else if is_target_commit {
944 num_rebased_targets += 1;
945 } else {
946 num_rebased_descendants += 1;
947 }
948 rebased_commits.insert(old_commit_id, rebased_commit);
949 } else {
950 num_skipped_rebases += 1;
951 }
952
953 Ok(())
954 },
955 )
956 .await?;
957
958 Ok(MoveCommitsStats {
959 num_rebased_targets,
960 num_rebased_descendants,
961 num_skipped_rebases,
962 num_abandoned_empty,
963 rebased_commits,
964 })
965}
966
967#[derive(Debug, Default)]
968pub struct DuplicateCommitsStats {
969 pub duplicated_commits: IndexMap<CommitId, Commit>,
971 pub num_rebased: u32,
974}
975
976pub async fn duplicate_commits(
994 mut_repo: &mut MutableRepo,
995 target_commit_ids: &[CommitId],
996 target_descriptions: &HashMap<CommitId, String>,
997 parent_commit_ids: &[CommitId],
998 children_commit_ids: &[CommitId],
999) -> BackendResult<DuplicateCommitsStats> {
1000 if target_commit_ids.is_empty() {
1001 return Ok(DuplicateCommitsStats::default());
1002 }
1003
1004 let mut duplicated_old_to_new: IndexMap<CommitId, Commit> = IndexMap::new();
1005 let mut num_rebased = 0;
1006
1007 let target_commit_ids: IndexSet<_> = target_commit_ids.iter().cloned().collect();
1008
1009 let connected_target_commits: Vec<_> =
1010 RevsetExpression::commits(target_commit_ids.iter().cloned().collect_vec())
1011 .connected()
1012 .evaluate(mut_repo)
1013 .map_err(|err| err.into_backend_error())?
1014 .stream()
1015 .commits(mut_repo.store())
1016 .try_collect()
1017 .await
1018 .map_err(|err| err.into_backend_error())?;
1019
1020 let target_commits_internal_parents = {
1027 let mut target_commits_internal_parents =
1028 compute_internal_parents_within(&target_commit_ids, &connected_target_commits);
1029 target_commits_internal_parents.retain(|id, _| target_commit_ids.contains(id));
1030 target_commits_internal_parents
1031 };
1032
1033 let target_root_ids: HashSet<_> = target_commits_internal_parents
1035 .iter()
1036 .filter(|(_, parents)| parents.is_empty())
1037 .map(|(commit_id, _)| commit_id.clone())
1038 .collect();
1039
1040 let target_head_ids = if !children_commit_ids.is_empty() {
1043 compute_commits_heads(&target_commit_ids, &connected_target_commits)
1044 } else {
1045 vec![]
1046 };
1047
1048 for original_commit_id in target_commit_ids.iter().rev() {
1051 let original_commit = mut_repo
1052 .store()
1053 .get_commit_async(original_commit_id)
1054 .await?;
1055 let new_parent_ids = if target_root_ids.contains(original_commit_id) {
1056 parent_commit_ids.to_vec()
1057 } else {
1058 target_commits_internal_parents
1059 .get(original_commit_id)
1060 .unwrap()
1061 .iter()
1062 .map(|id| {
1064 duplicated_old_to_new
1065 .get(id)
1066 .map_or(id, |commit| commit.id())
1067 .clone()
1068 })
1069 .collect()
1070 };
1071 let mut new_commit_builder = CommitRewriter::new(mut_repo, original_commit, new_parent_ids)
1072 .rebase()
1073 .await?
1074 .clear_rewrite_source()
1075 .generate_new_change_id();
1076 if let Some(desc) = target_descriptions.get(original_commit_id) {
1077 new_commit_builder = new_commit_builder.set_description(desc);
1078 }
1079 duplicated_old_to_new.insert(
1080 original_commit_id.clone(),
1081 new_commit_builder.write().await?,
1082 );
1083 }
1084
1085 let target_head_ids = target_head_ids
1088 .into_iter()
1089 .map(|commit_id| {
1090 duplicated_old_to_new
1091 .get(&commit_id)
1092 .map_or(commit_id, |commit| commit.id().clone())
1093 })
1094 .collect_vec();
1095
1096 let children_commit_ids_set: HashSet<CommitId> = children_commit_ids.iter().cloned().collect();
1098 mut_repo
1099 .transform_descendants(children_commit_ids.to_vec(), async |mut rewriter| {
1100 if children_commit_ids_set.contains(rewriter.old_commit().id()) {
1101 let mut child_new_parent_ids = IndexSet::new();
1102 for old_parent_id in rewriter.old_commit().parent_ids() {
1103 if parent_commit_ids.contains(old_parent_id) {
1108 child_new_parent_ids.extend(target_head_ids.clone());
1109 } else {
1110 child_new_parent_ids.insert(old_parent_id.clone());
1111 }
1112 }
1113 child_new_parent_ids.extend(target_head_ids.clone());
1116 rewriter.set_new_parents(child_new_parent_ids.into_iter().collect());
1117 }
1118 num_rebased += 1;
1119 rewriter.rebase().await?.write().await?;
1120 Ok(())
1121 })
1122 .await?;
1123
1124 Ok(DuplicateCommitsStats {
1125 duplicated_commits: duplicated_old_to_new,
1126 num_rebased,
1127 })
1128}
1129
1130pub async fn duplicate_commits_onto_parents(
1140 mut_repo: &mut MutableRepo,
1141 target_commits: &[CommitId],
1142 target_descriptions: &HashMap<CommitId, String>,
1143) -> BackendResult<DuplicateCommitsStats> {
1144 if target_commits.is_empty() {
1145 return Ok(DuplicateCommitsStats::default());
1146 }
1147
1148 let mut duplicated_old_to_new: IndexMap<CommitId, Commit> = IndexMap::new();
1149
1150 for original_commit_id in target_commits.iter().rev() {
1153 let original_commit = mut_repo
1154 .store()
1155 .get_commit_async(original_commit_id)
1156 .await?;
1157 let new_parent_ids = original_commit
1158 .parent_ids()
1159 .iter()
1160 .map(|id| {
1161 duplicated_old_to_new
1162 .get(id)
1163 .map_or(id, |commit| commit.id())
1164 .clone()
1165 })
1166 .collect();
1167 let mut new_commit_builder = mut_repo
1168 .rewrite_commit(&original_commit)
1169 .clear_rewrite_source()
1170 .generate_new_change_id()
1171 .set_parents(new_parent_ids);
1172 if let Some(desc) = target_descriptions.get(original_commit_id) {
1173 new_commit_builder = new_commit_builder.set_description(desc);
1174 }
1175 duplicated_old_to_new.insert(
1176 original_commit_id.clone(),
1177 new_commit_builder.write().await?,
1178 );
1179 }
1180
1181 Ok(DuplicateCommitsStats {
1182 duplicated_commits: duplicated_old_to_new,
1183 num_rebased: 0,
1184 })
1185}
1186
1187fn compute_internal_parents_within(
1195 target_commit_ids: &IndexSet<CommitId>,
1196 graph_commits: &[Commit],
1197) -> HashMap<CommitId, IndexSet<CommitId>> {
1198 let mut internal_parents: HashMap<CommitId, IndexSet<CommitId>> = HashMap::new();
1199 for commit in graph_commits.iter().rev() {
1200 let mut new_parents = IndexSet::new();
1203 for old_parent in commit.parent_ids() {
1204 if target_commit_ids.contains(old_parent) {
1205 new_parents.insert(old_parent.clone());
1206 } else if let Some(parents) = internal_parents.get(old_parent) {
1207 new_parents.extend(parents.iter().cloned());
1208 }
1209 }
1210 internal_parents.insert(commit.id().clone(), new_parents);
1211 }
1212 internal_parents
1213}
1214
1215fn compute_commits_heads(
1221 target_commit_ids: &IndexSet<CommitId>,
1222 connected_target_commits: &[Commit],
1223) -> Vec<CommitId> {
1224 let mut target_head_ids: HashSet<CommitId> = HashSet::new();
1225 for commit in connected_target_commits.iter().rev() {
1226 target_head_ids.insert(commit.id().clone());
1227 for old_parent in commit.parent_ids() {
1228 target_head_ids.remove(old_parent);
1229 }
1230 }
1231 connected_target_commits
1232 .iter()
1233 .rev()
1234 .filter(|commit| {
1235 target_head_ids.contains(commit.id()) && target_commit_ids.contains(commit.id())
1236 })
1237 .map(|commit| commit.id().clone())
1238 .collect_vec()
1239}
1240
1241#[derive(Debug)]
1242pub struct CommitWithSelection {
1243 pub commit: Commit,
1244 pub selected_tree: MergedTree,
1245 pub parent_tree: MergedTree,
1246}
1247
1248impl CommitWithSelection {
1249 pub fn is_full_selection(&self) -> bool {
1251 self.selected_tree.tree_ids() == self.commit.tree_ids()
1252 }
1253
1254 pub fn is_empty_selection(&self) -> bool {
1260 self.selected_tree.tree_ids() == self.parent_tree.tree_ids()
1261 }
1262
1263 pub async fn diff_with_labels(
1267 &self,
1268 parent_tree_label: &str,
1269 selected_tree_label: &str,
1270 full_selection_label: &str,
1271 ) -> BackendResult<Diff<(MergedTree, String)>> {
1272 let parent_tree_label = format!(
1273 "{} ({parent_tree_label})",
1274 self.commit.parents_conflict_label().await?
1275 );
1276
1277 let commit_label = self.commit.conflict_label();
1278 let selected_tree_label = if self.is_full_selection() {
1279 format!("{commit_label} ({full_selection_label})")
1280 } else {
1281 format!("{selected_tree_label} (from {commit_label})")
1282 };
1283
1284 Ok(Diff::new(
1285 (self.parent_tree.clone(), parent_tree_label),
1286 (self.selected_tree.clone(), selected_tree_label),
1287 ))
1288 }
1289}
1290
1291#[must_use]
1293pub struct SquashedCommit<'repo> {
1294 pub commit_builder: CommitBuilder<'repo>,
1296 pub abandoned_commits: Vec<Commit>,
1298}
1299
1300pub async fn squash_commits<'repo>(
1304 repo: &'repo mut MutableRepo,
1305 sources: &[CommitWithSelection],
1306 destination: &Commit,
1307 keep_emptied: bool,
1308) -> BackendResult<Option<SquashedCommit<'repo>>> {
1309 struct SourceCommit<'a> {
1310 commit: &'a CommitWithSelection,
1311 diff: Diff<(MergedTree, String)>,
1312 abandon: bool,
1313 }
1314 let mut source_commits = vec![];
1315 for source in sources {
1316 let abandon = !keep_emptied && source.is_full_selection();
1317 if !abandon && source.is_empty_selection() {
1318 continue;
1322 }
1323
1324 source_commits.push(SourceCommit {
1327 commit: source,
1328 diff: source
1329 .diff_with_labels(
1330 "parents of squashed revision",
1331 "selected changes for squash",
1332 "squashed revision",
1333 )
1334 .await?,
1335 abandon,
1336 });
1337 }
1338
1339 if source_commits.is_empty() {
1340 return Ok(None);
1341 }
1342
1343 let mut abandoned_commits = vec![];
1344 for source in &source_commits {
1345 if source.abandon {
1346 repo.record_abandoned_commit(&source.commit.commit);
1347 abandoned_commits.push(source.commit.commit.clone());
1348 } else {
1349 let source_tree = source.commit.commit.tree();
1350 let new_source_tree = MergedTree::merge(Merge::from_diffs(
1352 (source_tree, source.commit.commit.conflict_label()),
1353 [source.diff.clone().invert()],
1354 ))
1355 .await?;
1356 repo.rewrite_commit(&source.commit.commit)
1357 .set_tree(new_source_tree)
1358 .write()
1359 .await?;
1360 }
1361 }
1362
1363 let mut rewritten_destination = destination.clone();
1364 if fallible_any(sources, |source| {
1365 repo.index()
1366 .is_ancestor(source.commit.id(), destination.id())
1367 })
1368 .map_err(|err| BackendError::Other(err.into()))?
1370 {
1371 let options = RebaseOptions::default();
1376 repo.rebase_descendants_with_options(&options, |old_commit, rebased_commit| {
1377 if old_commit.id() != destination.id() {
1378 return;
1379 }
1380 rewritten_destination = match rebased_commit {
1381 RebasedCommit::Rewritten(commit) => commit,
1382 RebasedCommit::Abandoned { .. } => panic!("all commits should be kept"),
1383 };
1384 })
1385 .await?;
1386 }
1387 let mut predecessors = vec![destination.id().clone()];
1388 predecessors.extend(
1389 source_commits
1390 .iter()
1391 .map(|source| source.commit.commit.id().clone()),
1392 );
1393 let destination_tree = MergedTree::merge(Merge::from_diffs(
1395 (
1396 rewritten_destination.tree(),
1397 format!("{} (squash destination)", destination.conflict_label()),
1398 ),
1399 source_commits.into_iter().map(|source| source.diff),
1400 ))
1401 .await?;
1402
1403 let commit_builder = repo
1404 .rewrite_commit(&rewritten_destination)
1405 .set_tree(destination_tree)
1406 .set_predecessors(predecessors);
1407 Ok(Some(SquashedCommit {
1408 commit_builder,
1409 abandoned_commits,
1410 }))
1411}
1412
1413pub async fn find_duplicate_divergent_commits(
1417 repo: &dyn Repo,
1418 new_parent_ids: &[CommitId],
1419 target: &MoveCommitsTarget,
1420) -> BackendResult<Vec<Commit>> {
1421 let target_commits: Vec<Commit> = match target {
1422 MoveCommitsTarget::Commits(commit_ids) => {
1423 try_join_all(
1424 commit_ids
1425 .iter()
1426 .map(|commit_id| repo.store().get_commit_async(commit_id)),
1427 )
1428 .await?
1429 }
1430 MoveCommitsTarget::Roots(root_ids) => RevsetExpression::commits(root_ids.clone())
1431 .descendants()
1432 .evaluate(repo)
1433 .map_err(|err| err.into_backend_error())?
1434 .stream()
1435 .commits(repo.store())
1436 .try_collect()
1437 .await
1438 .map_err(|err| err.into_backend_error())?,
1439 };
1440 let target_commit_ids: HashSet<&CommitId> = target_commits.iter().map(Commit::id).collect();
1441
1442 let divergent_changes: Vec<(&Commit, Vec<CommitId>)> = target_commits
1445 .iter()
1446 .map(|target_commit| -> Result<_, BackendError> {
1447 let mut ancestor_candidates = repo
1448 .resolve_change_id(target_commit.change_id())
1449 .map_err(|err| BackendError::Other(err.into()))?
1451 .and_then(ResolvedChangeTargets::into_visible)
1452 .unwrap_or_default();
1453 ancestor_candidates.retain(|commit_id| !target_commit_ids.contains(commit_id));
1454 Ok((target_commit, ancestor_candidates))
1455 })
1456 .filter_ok(|(_, candidates)| !candidates.is_empty())
1457 .try_collect()?;
1458 if divergent_changes.is_empty() {
1459 return Ok(Vec::new());
1460 }
1461
1462 let target_root_ids = match target {
1463 MoveCommitsTarget::Commits(commit_ids) => commit_ids,
1464 MoveCommitsTarget::Roots(root_ids) => root_ids,
1465 };
1466
1467 let is_new_ancestor = RevsetExpression::commits(target_root_ids.clone())
1470 .range(&RevsetExpression::commits(new_parent_ids.to_owned()))
1471 .evaluate(repo)
1472 .map_err(|err| err.into_backend_error())?
1473 .containing_fn();
1474
1475 let mut duplicate_divergent = Vec::new();
1476 for (target_commit, ancestor_candidates) in divergent_changes {
1481 for ancestor_candidate_id in ancestor_candidates {
1482 if !is_new_ancestor(&ancestor_candidate_id).map_err(|err| err.into_backend_error())? {
1483 continue;
1484 }
1485
1486 let ancestor_candidate = repo
1487 .store()
1488 .get_commit_async(&ancestor_candidate_id)
1489 .await?;
1490 let new_tree =
1491 rebase_to_dest_parent(repo, slice::from_ref(target_commit), &ancestor_candidate)
1492 .await?;
1493 if new_tree.tree_ids() == ancestor_candidate.tree_ids() {
1496 duplicate_divergent.push(target_commit.clone());
1497 break;
1498 }
1499 }
1500 }
1501 Ok(duplicate_divergent)
1502}