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(repo, commits)
64 .await?
65 .resolve()
66 .await
67 }
68}
69
70pub async fn merge_commit_trees_no_resolve(
72 repo: &dyn Repo,
73 commits: &[Commit],
74) -> BackendResult<MergedTree> {
75 if let [commit] = commits {
76 Ok(commit.tree())
77 } else {
78 merge_commit_trees_no_resolve_without_repo(repo.store(), repo.index(), commits).await
79 }
80}
81
82#[instrument(skip(index))]
84pub async fn merge_commit_trees_no_resolve_without_repo(
85 store: &Arc<Store>,
86 index: &dyn Index,
87 commits: &[Commit],
88) -> BackendResult<MergedTree> {
89 let commit_ids = commits
90 .iter()
91 .map(|commit| commit.id().clone())
92 .collect_vec();
93 let commit_id_merge = find_recursive_merge_commits(store, index, commit_ids)?;
94 let tree_merge: Merge<(MergedTree, String)> = commit_id_merge
95 .try_map_async(async |commit_id| {
96 let commit = store.get_commit_async(commit_id).await?;
97 Ok::<_, BackendError>((commit.tree(), commit.conflict_label()))
98 })
99 .await?;
100 Ok(MergedTree::merge_no_resolve(tree_merge))
101}
102
103pub fn find_recursive_merge_commits(
105 store: &Arc<Store>,
106 index: &dyn Index,
107 mut commit_ids: Vec<CommitId>,
108) -> BackendResult<Merge<CommitId>> {
109 if commit_ids.is_empty() {
110 Ok(Merge::resolved(store.root_commit_id().clone()))
111 } else if commit_ids.len() == 1 {
112 Ok(Merge::resolved(commit_ids.pop().unwrap()))
113 } else {
114 let mut result = Merge::resolved(commit_ids[0].clone());
115 for (i, other_commit_id) in commit_ids.iter().enumerate().skip(1) {
116 let ancestor_ids = index
117 .common_ancestors(&commit_ids[0..i], &commit_ids[i..][..1])
118 .map_err(|err| BackendError::Other(err.into()))?;
120 let ancestor_merge = find_recursive_merge_commits(store, index, ancestor_ids)?;
121 result = Merge::from_vec(vec![
122 result,
123 ancestor_merge,
124 Merge::resolved(other_commit_id.clone()),
125 ])
126 .flatten();
127 }
128 Ok(result)
129 }
130}
131
132pub async fn restore_tree(
134 source: &MergedTree,
135 destination: &MergedTree,
136 source_label: String,
137 destination_label: String,
138 matcher: &dyn Matcher,
139) -> BackendResult<MergedTree> {
140 if matcher.visit(RepoPath::root()) == Visit::AllRecursively {
141 return Ok(source.clone());
143 }
144 let mut diff_stream = source.diff_stream(destination, matcher);
145 let mut paths = Vec::new();
146 while let Some(entry) = diff_stream.next().await {
147 paths.push(entry.path);
150 }
151 let matcher = FilesMatcher::new(paths);
152
153 let select_matching =
154 async |tree: &MergedTree, labels: ConflictLabels| -> BackendResult<MergedTree> {
155 let empty_tree_ids = Merge::repeated(
156 tree.store().empty_tree_id().clone(),
157 tree.tree_ids().num_sides(),
158 );
159 let labeled_empty_tree = MergedTree::new(tree.store().clone(), empty_tree_ids, labels);
160 let mut builder = MergedTreeBuilder::new(labeled_empty_tree);
161 for (path, value) in tree.entries_matching(&matcher) {
162 builder.set_or_remove(path, value?);
165 }
166 builder.write_tree().await
167 };
168
169 const RESTORE_BASE_LABEL: &str = "base files for restore";
170
171 let base_labels = ConflictLabels::from_merge(destination.labels().as_merge().map(|label| {
174 if label.is_empty() || label.starts_with(RESTORE_BASE_LABEL) {
175 label.clone()
176 } else {
177 format!("{RESTORE_BASE_LABEL} (from {label})")
178 }
179 }));
180
181 MergedTree::merge(Merge::from_vec(vec![
193 (
194 destination.clone(),
195 format!("{destination_label} (restore destination)"),
196 ),
197 (
198 select_matching(destination, base_labels).await?,
199 format!("{RESTORE_BASE_LABEL} (from {destination_label})"),
200 ),
201 (
202 select_matching(source, source.labels().clone()).await?,
203 format!("restored files (from {source_label})"),
204 ),
205 ]))
206 .await
207}
208
209pub async fn rebase_commit(
210 mut_repo: &mut MutableRepo,
211 old_commit: Commit,
212 new_parents: Vec<CommitId>,
213) -> BackendResult<Commit> {
214 let rewriter = CommitRewriter::new(mut_repo, old_commit, new_parents);
215 let builder = rewriter.rebase().await?;
216 builder.write().await
217}
218
219pub struct CommitRewriter<'repo> {
221 mut_repo: &'repo mut MutableRepo,
222 old_commit: Commit,
223 new_parents: Vec<CommitId>,
224}
225
226impl<'repo> CommitRewriter<'repo> {
227 pub fn new(
229 mut_repo: &'repo mut MutableRepo,
230 old_commit: Commit,
231 new_parents: Vec<CommitId>,
232 ) -> Self {
233 Self {
234 mut_repo,
235 old_commit,
236 new_parents,
237 }
238 }
239
240 pub fn repo_mut(&mut self) -> &mut MutableRepo {
242 self.mut_repo
243 }
244
245 pub fn old_commit(&self) -> &Commit {
247 &self.old_commit
248 }
249
250 pub fn new_parents(&self) -> &[CommitId] {
252 &self.new_parents
253 }
254
255 pub fn set_new_parents(&mut self, new_parents: Vec<CommitId>) {
257 self.new_parents = new_parents;
258 }
259
260 pub fn set_new_rewritten_parents(&mut self, unrewritten_parents: &[CommitId]) {
263 self.new_parents = self.mut_repo.new_parents(unrewritten_parents);
264 }
265
266 pub fn replace_parent<'a>(
269 &mut self,
270 old_parent: &CommitId,
271 new_parents: impl IntoIterator<Item = &'a CommitId>,
272 ) {
273 if let Some(i) = self.new_parents.iter().position(|p| p == old_parent) {
274 self.new_parents
275 .splice(i..i + 1, new_parents.into_iter().cloned());
276 let mut unique = HashSet::new();
277 self.new_parents.retain(|p| unique.insert(p.clone()));
278 }
279 }
280
281 pub fn parents_changed(&self) -> bool {
284 self.new_parents != self.old_commit.parent_ids()
285 }
286
287 pub async fn simplify_ancestor_merge(&mut self) -> IndexResult<()> {
290 let head_set: HashSet<_> = self
291 .mut_repo
292 .index()
293 .heads(&mut self.new_parents.iter())
294 .await?
295 .into_iter()
296 .collect();
297 self.new_parents.retain(|parent| head_set.contains(parent));
298 Ok(())
299 }
300
301 pub fn abandon(self) {
305 let old_commit_id = self.old_commit.id().clone();
306 let new_parents = self.new_parents;
307 self.mut_repo
308 .record_abandoned_commit_with_parents(old_commit_id, new_parents);
309 }
310
311 pub async fn rebase_with_empty_behavior(
314 self,
315 empty: EmptyBehavior,
316 ) -> BackendResult<Option<CommitBuilder<'repo>>> {
317 let old_parents_fut = self.old_commit.parents();
318 let new_parents_fut = try_join_all(
319 self.new_parents
320 .iter()
321 .map(|new_parent_id| self.mut_repo.store().get_commit_async(new_parent_id)),
322 );
323 let (old_parents, new_parents) = try_join!(old_parents_fut, new_parents_fut)?;
324 let old_parent_trees = old_parents
325 .iter()
326 .map(|parent| parent.tree_ids().clone())
327 .collect_vec();
328 let new_parent_trees = new_parents
329 .iter()
330 .map(|parent| parent.tree_ids().clone())
331 .collect_vec();
332
333 let (was_empty, new_tree) = if new_parent_trees == old_parent_trees {
334 (
335 true,
338 self.old_commit.tree(),
340 )
341 } else {
342 let old_base_tree_fut = merge_commit_trees(self.mut_repo, &old_parents);
346 let new_base_tree_fut = merge_commit_trees(self.mut_repo, &new_parents);
347 let old_tree = self.old_commit.tree();
348 let (old_base_tree, new_base_tree) = try_join!(old_base_tree_fut, new_base_tree_fut)?;
349 (
350 old_base_tree.tree_ids() == self.old_commit.tree_ids(),
351 MergedTree::merge(Merge::from_vec(vec![
352 (
353 new_base_tree,
354 format!(
355 "{} (rebase destination)",
356 conflict_label_for_commits(&new_parents)
357 ),
358 ),
359 (
360 old_base_tree,
361 format!(
362 "{} (parents of rebased revision)",
363 conflict_label_for_commits(&old_parents)
364 ),
365 ),
366 (
367 old_tree,
368 format!("{} (rebased revision)", self.old_commit.conflict_label()),
369 ),
370 ]))
371 .await?,
372 )
373 };
374 if let [parent] = &new_parents[..] {
377 let should_abandon = match empty {
378 EmptyBehavior::Keep => false,
379 EmptyBehavior::AbandonNewlyEmpty => {
380 parent.tree_ids() == new_tree.tree_ids() && !was_empty
381 }
382 EmptyBehavior::AbandonAllEmpty => parent.tree_ids() == new_tree.tree_ids(),
383 };
384 if should_abandon {
385 self.abandon();
386 return Ok(None);
387 }
388 }
389
390 let builder = self
391 .mut_repo
392 .rewrite_commit(&self.old_commit)
393 .set_parents(self.new_parents)
394 .set_tree(new_tree);
395 Ok(Some(builder))
396 }
397
398 pub async fn rebase(self) -> BackendResult<CommitBuilder<'repo>> {
401 let builder = self.rebase_with_empty_behavior(EmptyBehavior::Keep).await?;
402 Ok(builder.unwrap())
403 }
404
405 pub fn reparent(self) -> CommitBuilder<'repo> {
408 self.mut_repo
409 .rewrite_commit(&self.old_commit)
410 .set_parents(self.new_parents)
411 }
412}
413
414#[derive(Debug)]
415pub enum RebasedCommit {
416 Rewritten(Commit),
417 Abandoned { parent_id: CommitId },
418}
419
420pub async fn rebase_commit_with_options(
421 mut rewriter: CommitRewriter<'_>,
422 options: &RebaseOptions,
423) -> BackendResult<RebasedCommit> {
424 if options.simplify_ancestor_merge {
426 rewriter
427 .simplify_ancestor_merge()
428 .await
429 .map_err(|err| BackendError::Other(err.into()))?;
431 }
432
433 let single_parent = match &rewriter.new_parents[..] {
434 [parent_id] => Some(parent_id.clone()),
435 _ => None,
436 };
437 let new_parents_len = rewriter.new_parents.len();
438 if let Some(builder) = rewriter.rebase_with_empty_behavior(options.empty).await? {
439 let new_commit = builder.write().await?;
440 Ok(RebasedCommit::Rewritten(new_commit))
441 } else {
442 assert_eq!(new_parents_len, 1);
443 Ok(RebasedCommit::Abandoned {
444 parent_id: single_parent.unwrap(),
445 })
446 }
447}
448
449pub async fn rebase_to_dest_parent(
452 repo: &dyn Repo,
453 sources: &[Commit],
454 destination: &Commit,
455) -> BackendResult<MergedTree> {
456 if let [source] = sources
457 && source.parent_ids() == destination.parent_ids()
458 {
459 return Ok(source.tree());
460 }
461
462 let diffs: Vec<_> = try_join_all(sources.iter().map(async |source| -> BackendResult<_> {
463 Ok(Diff::new(
464 (
465 source.parent_tree(repo).await?,
466 format!(
467 "{} (original parents)",
468 source.parents_conflict_label().await?
469 ),
470 ),
471 (
472 source.tree(),
473 format!("{} (original revision)", source.conflict_label()),
474 ),
475 ))
476 }))
477 .await?;
478 MergedTree::merge(Merge::from_diffs(
479 (
480 destination.parent_tree(repo).await?,
481 format!(
482 "{} (new parents)",
483 destination.parents_conflict_label().await?
484 ),
485 ),
486 diffs,
487 ))
488 .await
489}
490
491#[derive(Clone, Copy, Default, PartialEq, Eq, Debug)]
492pub enum EmptyBehavior {
493 #[default]
495 Keep,
496 AbandonNewlyEmpty,
500 AbandonAllEmpty,
504}
505
506#[derive(Clone, Debug, Default)]
513pub struct RebaseOptions {
514 pub empty: EmptyBehavior,
515 pub rewrite_refs: RewriteRefsOptions,
516 pub simplify_ancestor_merge: bool,
519}
520
521#[derive(Clone, Debug, Default)]
523pub struct RewriteRefsOptions {
524 pub delete_abandoned_bookmarks: bool,
529}
530
531#[derive(Debug)]
532pub struct MoveCommitsStats {
533 pub num_rebased_targets: u32,
535 pub num_rebased_descendants: u32,
537 pub num_skipped_rebases: u32,
540 pub num_abandoned_empty: u32,
542 pub rebased_commits: HashMap<CommitId, RebasedCommit>,
544}
545
546#[derive(Clone, Debug)]
548pub struct MoveCommitsLocation {
549 pub new_parent_ids: Vec<CommitId>,
550 pub new_child_ids: Vec<CommitId>,
551 pub target: MoveCommitsTarget,
552}
553
554#[derive(Clone, Debug)]
555pub enum MoveCommitsTarget {
556 Commits(Vec<CommitId>),
559 Roots(Vec<CommitId>),
561}
562
563#[derive(Clone, Debug)]
564pub struct ComputedMoveCommits {
565 target_commit_ids: IndexSet<CommitId>,
566 descendants: Vec<Commit>,
567 commit_new_parents_map: HashMap<CommitId, Vec<CommitId>>,
568 to_abandon: HashSet<CommitId>,
569}
570
571impl ComputedMoveCommits {
572 fn empty() -> Self {
573 Self {
574 target_commit_ids: IndexSet::new(),
575 descendants: vec![],
576 commit_new_parents_map: HashMap::new(),
577 to_abandon: HashSet::new(),
578 }
579 }
580
581 pub fn record_to_abandon(&mut self, commit_ids: impl IntoIterator<Item = CommitId>) {
588 self.to_abandon.extend(commit_ids);
589 }
590
591 pub async fn apply(
592 self,
593 mut_repo: &mut MutableRepo,
594 options: &RebaseOptions,
595 ) -> BackendResult<MoveCommitsStats> {
596 apply_move_commits(mut_repo, self, options).await
597 }
598}
599
600pub async fn move_commits(
609 mut_repo: &mut MutableRepo,
610 loc: &MoveCommitsLocation,
611 options: &RebaseOptions,
612) -> BackendResult<MoveCommitsStats> {
613 compute_move_commits(mut_repo, loc)
614 .await?
615 .apply(mut_repo, options)
616 .await
617}
618
619pub async fn compute_move_commits(
620 repo: &MutableRepo,
621 loc: &MoveCommitsLocation,
622) -> BackendResult<ComputedMoveCommits> {
623 let target_commit_ids: IndexSet<CommitId>;
624 let connected_target_commits: Vec<Commit>;
625 let connected_target_commits_internal_parents: HashMap<CommitId, IndexSet<CommitId>>;
626 let target_roots: HashSet<CommitId>;
627
628 match &loc.target {
629 MoveCommitsTarget::Commits(commit_ids) => {
630 if commit_ids.is_empty() {
631 return Ok(ComputedMoveCommits::empty());
632 }
633
634 target_commit_ids = commit_ids.iter().cloned().collect();
635
636 connected_target_commits = RevsetExpression::commits(commit_ids.clone())
637 .connected()
638 .evaluate(repo)
639 .map_err(|err| err.into_backend_error())?
640 .stream()
641 .commits(repo.store())
642 .try_collect()
643 .await
644 .map_err(|err| err.into_backend_error())?;
645 connected_target_commits_internal_parents =
646 compute_internal_parents_within(&target_commit_ids, &connected_target_commits);
647
648 target_roots = connected_target_commits_internal_parents
649 .iter()
650 .filter(|&(commit_id, parents)| {
651 target_commit_ids.contains(commit_id) && parents.is_empty()
652 })
653 .map(|(commit_id, _)| commit_id.clone())
654 .collect();
655 }
656 MoveCommitsTarget::Roots(root_ids) => {
657 if root_ids.is_empty() {
658 return Ok(ComputedMoveCommits::empty());
659 }
660
661 target_commit_ids = RevsetExpression::commits(root_ids.clone())
662 .descendants()
663 .evaluate(repo)
664 .map_err(|err| err.into_backend_error())?
665 .stream()
666 .try_collect()
667 .await
668 .map_err(|err| err.into_backend_error())?;
669
670 connected_target_commits = try_join_all(
671 target_commit_ids
672 .iter()
673 .map(|id| repo.store().get_commit_async(id)),
674 )
675 .await?;
676 connected_target_commits_internal_parents = HashMap::new();
679 target_roots = root_ids.iter().cloned().collect();
680 }
681 }
682
683 let mut target_commits_external_parents: HashMap<CommitId, IndexSet<CommitId>> = HashMap::new();
687 for id in target_commit_ids.iter().rev() {
688 let commit = repo.store().get_commit_async(id).await?;
689 let mut new_parents = IndexSet::new();
690 for old_parent in commit.parent_ids() {
691 if let Some(parents) = target_commits_external_parents.get(old_parent) {
692 new_parents.extend(parents.iter().cloned());
693 } else {
694 new_parents.insert(old_parent.clone());
695 }
696 }
697 target_commits_external_parents.insert(commit.id().clone(), new_parents);
698 }
699
700 let new_parent_ids: Vec<_> = loc
704 .new_parent_ids
705 .iter()
706 .flat_map(|parent_id| {
707 if let Some(parent_ids) = target_commits_external_parents.get(parent_id) {
708 parent_ids.iter().cloned().collect_vec()
709 } else {
710 vec![parent_id.clone()]
711 }
712 })
713 .collect();
714
715 let new_children: Vec<_> = if loc
719 .new_child_ids
720 .iter()
721 .any(|id| target_commit_ids.contains(id))
722 {
723 let target_commits_descendants: Vec<_> =
724 RevsetExpression::commits(target_commit_ids.iter().cloned().collect_vec())
725 .union(
726 &RevsetExpression::commits(target_commit_ids.iter().cloned().collect_vec())
727 .children(),
728 )
729 .evaluate(repo)
730 .map_err(|err| err.into_backend_error())?
731 .stream()
732 .commits(repo.store())
733 .try_collect()
734 .await
735 .map_err(|err| err.into_backend_error())?;
736
737 let mut target_commit_external_descendants: HashMap<CommitId, IndexSet<Commit>> =
740 HashMap::new();
741 for commit in &target_commits_descendants {
744 if !target_commit_external_descendants.contains_key(commit.id()) {
745 let children = if target_commit_ids.contains(commit.id()) {
746 IndexSet::new()
747 } else {
748 IndexSet::from([commit.clone()])
749 };
750 target_commit_external_descendants.insert(commit.id().clone(), children);
751 }
752
753 let children = target_commit_external_descendants
754 .get(commit.id())
755 .unwrap()
756 .iter()
757 .cloned()
758 .collect_vec();
759 for parent_id in commit.parent_ids() {
760 if target_commit_ids.contains(parent_id) {
761 if let Some(target_children) =
762 target_commit_external_descendants.get_mut(parent_id)
763 {
764 target_children.extend(children.iter().cloned());
765 } else {
766 target_commit_external_descendants
767 .insert(parent_id.clone(), children.iter().cloned().collect());
768 }
769 }
770 }
771 }
772
773 let mut new_children = Vec::new();
774 for id in &loc.new_child_ids {
775 if let Some(children) = target_commit_external_descendants.get(id) {
776 new_children.extend(children.iter().cloned());
777 } else {
778 new_children.push(repo.store().get_commit_async(id).await?);
779 }
780 }
781 new_children
782 } else {
783 try_join_all(
784 loc.new_child_ids
785 .iter()
786 .map(|id| repo.store().get_commit_async(id)),
787 )
788 .await?
789 };
790
791 let new_children_parents: HashMap<_, _> = if !new_children.is_empty() {
794 let target_heads = compute_commits_heads(&target_commit_ids, &connected_target_commits);
797
798 new_children
799 .iter()
800 .map(|child_commit| {
801 let mut new_child_parent_ids = IndexSet::new();
802 for old_child_parent_id in child_commit.parent_ids() {
803 let old_child_parent_ids = if let Some(parents) =
805 target_commits_external_parents.get(old_child_parent_id)
806 {
807 parents.iter().collect_vec()
808 } else {
809 vec![old_child_parent_id]
810 };
811
812 for id in old_child_parent_ids {
816 if new_parent_ids.contains(id) {
817 new_child_parent_ids.extend(target_heads.clone());
818 } else {
819 new_child_parent_ids.insert(id.clone());
820 }
821 }
822 }
823
824 new_child_parent_ids.extend(target_heads.clone());
827
828 (
829 child_commit.id().clone(),
830 new_child_parent_ids.into_iter().collect_vec(),
831 )
832 })
833 .collect()
834 } else {
835 HashMap::new()
836 };
837
838 let mut roots = target_roots.iter().cloned().collect_vec();
841 roots.extend(new_children.iter().ids().cloned());
842
843 let descendants = repo
844 .find_descendants_for_rebase(roots.clone(), &RevsetExpression::none())
845 .await?;
846 let commit_new_parents_entries =
847 try_join_all(descendants.iter().map(async |commit| -> BackendResult<_> {
848 let commit_id = commit.id();
849 let new_parent_ids =
850 if let Some(new_child_parents) = new_children_parents.get(commit_id) {
851 new_child_parents.clone()
853 } else if target_commit_ids.contains(commit_id) {
854 if target_roots.contains(commit_id) {
856 new_parent_ids.clone()
859 } else {
860 let mut new_parents = vec![];
868 for parent_id in commit.parent_ids() {
869 if target_commit_ids.contains(parent_id) {
870 new_parents.push(parent_id.clone());
871 } else if let Some(parents) =
872 connected_target_commits_internal_parents.get(parent_id)
873 {
874 new_parents.extend(parents.iter().cloned());
875 } else if !fallible_any(&new_children, async |child| {
876 repo.index().is_ancestor(child.id(), parent_id).await
877 })
878 .await
879 .map_err(|err| BackendError::Other(err.into()))?
881 {
882 new_parents.push(parent_id.clone());
883 }
884 }
885 new_parents
886 }
887 } else if commit
888 .parent_ids()
889 .iter()
890 .any(|id| target_commits_external_parents.contains_key(id))
891 {
892 let mut new_parents = vec![];
895 for parent in commit.parent_ids() {
896 if let Some(parents) = target_commits_external_parents.get(parent) {
897 new_parents.extend(parents.iter().cloned());
898 } else {
899 new_parents.push(parent.clone());
900 }
901 }
902 new_parents
903 } else {
904 commit.parent_ids().iter().cloned().collect_vec()
905 };
906 Ok((commit.id().clone(), new_parent_ids))
907 }))
908 .await?;
909 let commit_new_parents_map = commit_new_parents_entries.into_iter().collect();
910
911 Ok(ComputedMoveCommits {
912 target_commit_ids,
913 descendants,
914 commit_new_parents_map,
915 to_abandon: HashSet::new(),
916 })
917}
918
919async fn apply_move_commits(
920 mut_repo: &mut MutableRepo,
921 commits: ComputedMoveCommits,
922 options: &RebaseOptions,
923) -> BackendResult<MoveCommitsStats> {
924 let mut num_rebased_targets = 0;
925 let mut num_rebased_descendants = 0;
926 let mut num_skipped_rebases = 0;
927 let mut num_abandoned_empty = 0;
928
929 let rebase_descendant_options = &RebaseOptions {
932 empty: EmptyBehavior::Keep,
933 rewrite_refs: options.rewrite_refs.clone(),
934 simplify_ancestor_merge: false,
935 };
936
937 let mut rebased_commits: HashMap<CommitId, RebasedCommit> = HashMap::new();
938 mut_repo
939 .transform_commits(
940 commits.descendants,
941 &commits.commit_new_parents_map,
942 &options.rewrite_refs,
943 async |rewriter| {
944 let old_commit_id = rewriter.old_commit().id().clone();
945 if commits.to_abandon.contains(&old_commit_id) {
946 rewriter.abandon();
947 } else if rewriter.parents_changed() {
948 let is_target_commit = commits.target_commit_ids.contains(&old_commit_id);
949 let rebased_commit = rebase_commit_with_options(
950 rewriter,
951 if is_target_commit {
952 options
953 } else {
954 rebase_descendant_options
955 },
956 )
957 .await?;
958 if let RebasedCommit::Abandoned { .. } = rebased_commit {
959 num_abandoned_empty += 1;
960 } else if is_target_commit {
961 num_rebased_targets += 1;
962 } else {
963 num_rebased_descendants += 1;
964 }
965 rebased_commits.insert(old_commit_id, rebased_commit);
966 } else {
967 num_skipped_rebases += 1;
968 }
969
970 Ok(())
971 },
972 )
973 .await?;
974
975 Ok(MoveCommitsStats {
976 num_rebased_targets,
977 num_rebased_descendants,
978 num_skipped_rebases,
979 num_abandoned_empty,
980 rebased_commits,
981 })
982}
983
984#[derive(Debug, Default)]
985pub struct DuplicateCommitsStats {
986 pub duplicated_commits: IndexMap<CommitId, Commit>,
988 pub num_rebased: u32,
991}
992
993pub async fn duplicate_commits(
1011 mut_repo: &mut MutableRepo,
1012 target_commit_ids: &[CommitId],
1013 target_descriptions: &HashMap<CommitId, String>,
1014 parent_commit_ids: &[CommitId],
1015 children_commit_ids: &[CommitId],
1016) -> BackendResult<DuplicateCommitsStats> {
1017 if target_commit_ids.is_empty() {
1018 return Ok(DuplicateCommitsStats::default());
1019 }
1020
1021 let mut duplicated_old_to_new: IndexMap<CommitId, Commit> = IndexMap::new();
1022 let mut num_rebased = 0;
1023
1024 let target_commit_ids: IndexSet<_> = target_commit_ids.iter().cloned().collect();
1025
1026 let connected_target_commits: Vec<_> =
1027 RevsetExpression::commits(target_commit_ids.iter().cloned().collect_vec())
1028 .connected()
1029 .evaluate(mut_repo)
1030 .map_err(|err| err.into_backend_error())?
1031 .stream()
1032 .commits(mut_repo.store())
1033 .try_collect()
1034 .await
1035 .map_err(|err| err.into_backend_error())?;
1036
1037 let target_commits_internal_parents = {
1044 let mut target_commits_internal_parents =
1045 compute_internal_parents_within(&target_commit_ids, &connected_target_commits);
1046 target_commits_internal_parents.retain(|id, _| target_commit_ids.contains(id));
1047 target_commits_internal_parents
1048 };
1049
1050 let target_root_ids: HashSet<_> = target_commits_internal_parents
1052 .iter()
1053 .filter(|(_, parents)| parents.is_empty())
1054 .map(|(commit_id, _)| commit_id.clone())
1055 .collect();
1056
1057 let target_head_ids = if !children_commit_ids.is_empty() {
1060 compute_commits_heads(&target_commit_ids, &connected_target_commits)
1061 } else {
1062 vec![]
1063 };
1064
1065 for original_commit_id in target_commit_ids.iter().rev() {
1068 let original_commit = mut_repo
1069 .store()
1070 .get_commit_async(original_commit_id)
1071 .await?;
1072 let new_parent_ids = if target_root_ids.contains(original_commit_id) {
1073 parent_commit_ids.to_vec()
1074 } else {
1075 target_commits_internal_parents
1076 .get(original_commit_id)
1077 .unwrap()
1078 .iter()
1079 .map(|id| {
1081 duplicated_old_to_new
1082 .get(id)
1083 .map_or(id, |commit| commit.id())
1084 .clone()
1085 })
1086 .collect()
1087 };
1088 let mut new_commit_builder = CommitRewriter::new(mut_repo, original_commit, new_parent_ids)
1089 .rebase()
1090 .await?
1091 .clear_rewrite_source()
1092 .generate_new_change_id();
1093 if let Some(desc) = target_descriptions.get(original_commit_id) {
1094 new_commit_builder = new_commit_builder.set_description(desc);
1095 }
1096 duplicated_old_to_new.insert(
1097 original_commit_id.clone(),
1098 new_commit_builder.write().await?,
1099 );
1100 }
1101
1102 let target_head_ids = target_head_ids
1105 .into_iter()
1106 .map(|commit_id| {
1107 duplicated_old_to_new
1108 .get(&commit_id)
1109 .map_or(commit_id, |commit| commit.id().clone())
1110 })
1111 .collect_vec();
1112
1113 let children_commit_ids_set: HashSet<CommitId> = children_commit_ids.iter().cloned().collect();
1115 mut_repo
1116 .transform_descendants(children_commit_ids.to_vec(), async |mut rewriter| {
1117 if children_commit_ids_set.contains(rewriter.old_commit().id()) {
1118 let mut child_new_parent_ids = IndexSet::new();
1119 for old_parent_id in rewriter.old_commit().parent_ids() {
1120 if parent_commit_ids.contains(old_parent_id) {
1125 child_new_parent_ids.extend(target_head_ids.clone());
1126 } else {
1127 child_new_parent_ids.insert(old_parent_id.clone());
1128 }
1129 }
1130 child_new_parent_ids.extend(target_head_ids.clone());
1133 rewriter.set_new_parents(child_new_parent_ids.into_iter().collect());
1134 }
1135 num_rebased += 1;
1136 rewriter.rebase().await?.write().await?;
1137 Ok(())
1138 })
1139 .await?;
1140
1141 Ok(DuplicateCommitsStats {
1142 duplicated_commits: duplicated_old_to_new,
1143 num_rebased,
1144 })
1145}
1146
1147pub async fn duplicate_commits_onto_parents(
1157 mut_repo: &mut MutableRepo,
1158 target_commits: &[CommitId],
1159 target_descriptions: &HashMap<CommitId, String>,
1160) -> BackendResult<DuplicateCommitsStats> {
1161 if target_commits.is_empty() {
1162 return Ok(DuplicateCommitsStats::default());
1163 }
1164
1165 let mut duplicated_old_to_new: IndexMap<CommitId, Commit> = IndexMap::new();
1166
1167 for original_commit_id in target_commits.iter().rev() {
1170 let original_commit = mut_repo
1171 .store()
1172 .get_commit_async(original_commit_id)
1173 .await?;
1174 let new_parent_ids = original_commit
1175 .parent_ids()
1176 .iter()
1177 .map(|id| {
1178 duplicated_old_to_new
1179 .get(id)
1180 .map_or(id, |commit| commit.id())
1181 .clone()
1182 })
1183 .collect();
1184 let mut new_commit_builder = mut_repo
1185 .rewrite_commit(&original_commit)
1186 .clear_rewrite_source()
1187 .generate_new_change_id()
1188 .set_parents(new_parent_ids);
1189 if let Some(desc) = target_descriptions.get(original_commit_id) {
1190 new_commit_builder = new_commit_builder.set_description(desc);
1191 }
1192 duplicated_old_to_new.insert(
1193 original_commit_id.clone(),
1194 new_commit_builder.write().await?,
1195 );
1196 }
1197
1198 Ok(DuplicateCommitsStats {
1199 duplicated_commits: duplicated_old_to_new,
1200 num_rebased: 0,
1201 })
1202}
1203
1204fn compute_internal_parents_within(
1212 target_commit_ids: &IndexSet<CommitId>,
1213 graph_commits: &[Commit],
1214) -> HashMap<CommitId, IndexSet<CommitId>> {
1215 let mut internal_parents: HashMap<CommitId, IndexSet<CommitId>> = HashMap::new();
1216 for commit in graph_commits.iter().rev() {
1217 let mut new_parents = IndexSet::new();
1220 for old_parent in commit.parent_ids() {
1221 if target_commit_ids.contains(old_parent) {
1222 new_parents.insert(old_parent.clone());
1223 } else if let Some(parents) = internal_parents.get(old_parent) {
1224 new_parents.extend(parents.iter().cloned());
1225 }
1226 }
1227 internal_parents.insert(commit.id().clone(), new_parents);
1228 }
1229 internal_parents
1230}
1231
1232fn compute_commits_heads(
1238 target_commit_ids: &IndexSet<CommitId>,
1239 connected_target_commits: &[Commit],
1240) -> Vec<CommitId> {
1241 let mut target_head_ids: HashSet<CommitId> = HashSet::new();
1242 for commit in connected_target_commits.iter().rev() {
1243 target_head_ids.insert(commit.id().clone());
1244 for old_parent in commit.parent_ids() {
1245 target_head_ids.remove(old_parent);
1246 }
1247 }
1248 connected_target_commits
1249 .iter()
1250 .rev()
1251 .filter(|commit| {
1252 target_head_ids.contains(commit.id()) && target_commit_ids.contains(commit.id())
1253 })
1254 .map(|commit| commit.id().clone())
1255 .collect_vec()
1256}
1257
1258#[derive(Debug)]
1259pub struct CommitWithSelection {
1260 pub commit: Commit,
1261 pub selected_tree: MergedTree,
1262 pub parent_tree: MergedTree,
1263}
1264
1265impl CommitWithSelection {
1266 pub fn is_full_selection(&self) -> bool {
1268 self.selected_tree.tree_ids() == self.commit.tree_ids()
1269 }
1270
1271 pub fn is_empty_selection(&self) -> bool {
1277 self.selected_tree.tree_ids() == self.parent_tree.tree_ids()
1278 }
1279
1280 pub async fn diff_with_labels(
1284 &self,
1285 parent_tree_label: &str,
1286 selected_tree_label: &str,
1287 full_selection_label: &str,
1288 ) -> BackendResult<Diff<(MergedTree, String)>> {
1289 let parent_tree_label = format!(
1290 "{} ({parent_tree_label})",
1291 self.commit.parents_conflict_label().await?
1292 );
1293
1294 let commit_label = self.commit.conflict_label();
1295 let selected_tree_label = if self.is_full_selection() {
1296 format!("{commit_label} ({full_selection_label})")
1297 } else {
1298 format!("{selected_tree_label} (from {commit_label})")
1299 };
1300
1301 Ok(Diff::new(
1302 (self.parent_tree.clone(), parent_tree_label),
1303 (self.selected_tree.clone(), selected_tree_label),
1304 ))
1305 }
1306}
1307
1308#[must_use]
1310pub struct SquashedCommit<'repo> {
1311 pub commit_builder: CommitBuilder<'repo>,
1313 pub abandoned_commits: Vec<Commit>,
1315}
1316
1317pub async fn squash_commits<'repo>(
1321 repo: &'repo mut MutableRepo,
1322 sources: &[CommitWithSelection],
1323 destination: &Commit,
1324 keep_emptied: bool,
1325) -> BackendResult<Option<SquashedCommit<'repo>>> {
1326 struct SourceCommit<'a> {
1327 commit: &'a CommitWithSelection,
1328 diff: Diff<(MergedTree, String)>,
1329 abandon: bool,
1330 }
1331 let mut source_commits = vec![];
1332 for source in sources {
1333 let abandon = !keep_emptied && source.is_full_selection();
1334 if !abandon && source.is_empty_selection() {
1335 continue;
1339 }
1340
1341 source_commits.push(SourceCommit {
1344 commit: source,
1345 diff: source
1346 .diff_with_labels(
1347 "parents of squashed revision",
1348 "selected changes for squash",
1349 "squashed revision",
1350 )
1351 .await?,
1352 abandon,
1353 });
1354 }
1355
1356 if source_commits.is_empty() {
1357 return Ok(None);
1358 }
1359
1360 let mut abandoned_commits = vec![];
1361 for source in &source_commits {
1362 if source.abandon {
1363 repo.record_abandoned_commit(&source.commit.commit);
1364 abandoned_commits.push(source.commit.commit.clone());
1365 } else {
1366 let source_tree = source.commit.commit.tree();
1367 let new_source_tree = MergedTree::merge(Merge::from_diffs(
1369 (source_tree, source.commit.commit.conflict_label()),
1370 [source.diff.clone().invert()],
1371 ))
1372 .await?;
1373 repo.rewrite_commit(&source.commit.commit)
1374 .set_tree(new_source_tree)
1375 .write()
1376 .await?;
1377 }
1378 }
1379
1380 let mut rewritten_destination = destination.clone();
1381 if fallible_any(sources, async |source| {
1382 repo.index()
1383 .is_ancestor(source.commit.id(), destination.id())
1384 .await
1385 })
1386 .await
1387 .map_err(|err| BackendError::Other(err.into()))?
1389 {
1390 let immutable = RevsetExpression::none();
1395 let options = RebaseOptions::default();
1396 repo.rebase_descendants_with_options(&immutable, &options, |old_commit, rebased_commit| {
1397 if old_commit.id() != destination.id() {
1398 return;
1399 }
1400 rewritten_destination = match rebased_commit {
1401 RebasedCommit::Rewritten(commit) => commit,
1402 RebasedCommit::Abandoned { .. } => panic!("all commits should be kept"),
1403 };
1404 })
1405 .await?;
1406 }
1407 let mut predecessors = vec![destination.id().clone()];
1408 predecessors.extend(
1409 source_commits
1410 .iter()
1411 .map(|source| source.commit.commit.id().clone()),
1412 );
1413 let destination_tree = MergedTree::merge(Merge::from_diffs(
1415 (
1416 rewritten_destination.tree(),
1417 format!("{} (squash destination)", destination.conflict_label()),
1418 ),
1419 source_commits.into_iter().map(|source| source.diff),
1420 ))
1421 .await?;
1422
1423 let commit_builder = repo
1424 .rewrite_commit(&rewritten_destination)
1425 .set_tree(destination_tree)
1426 .set_predecessors(predecessors);
1427 Ok(Some(SquashedCommit {
1428 commit_builder,
1429 abandoned_commits,
1430 }))
1431}
1432
1433pub async fn find_duplicate_divergent_commits(
1437 repo: &dyn Repo,
1438 new_parent_ids: &[CommitId],
1439 target: &MoveCommitsTarget,
1440) -> BackendResult<Vec<Commit>> {
1441 let target_commits: Vec<Commit> = match target {
1442 MoveCommitsTarget::Commits(commit_ids) => {
1443 try_join_all(
1444 commit_ids
1445 .iter()
1446 .map(|commit_id| repo.store().get_commit_async(commit_id)),
1447 )
1448 .await?
1449 }
1450 MoveCommitsTarget::Roots(root_ids) => RevsetExpression::commits(root_ids.clone())
1451 .descendants()
1452 .evaluate(repo)
1453 .map_err(|err| err.into_backend_error())?
1454 .stream()
1455 .commits(repo.store())
1456 .try_collect()
1457 .await
1458 .map_err(|err| err.into_backend_error())?,
1459 };
1460 let target_commit_ids: HashSet<&CommitId> = target_commits.iter().map(Commit::id).collect();
1461
1462 let divergent_changes: Vec<(&Commit, Vec<CommitId>)> = target_commits
1465 .iter()
1466 .map(|target_commit| -> Result<_, BackendError> {
1467 let mut ancestor_candidates = repo
1468 .resolve_change_id(target_commit.change_id())
1469 .map_err(|err| BackendError::Other(err.into()))?
1471 .and_then(ResolvedChangeTargets::into_visible)
1472 .unwrap_or_default();
1473 ancestor_candidates.retain(|commit_id| !target_commit_ids.contains(commit_id));
1474 Ok((target_commit, ancestor_candidates))
1475 })
1476 .filter_ok(|(_, candidates)| !candidates.is_empty())
1477 .try_collect()?;
1478 if divergent_changes.is_empty() {
1479 return Ok(Vec::new());
1480 }
1481
1482 let target_root_ids = match target {
1483 MoveCommitsTarget::Commits(commit_ids) => commit_ids,
1484 MoveCommitsTarget::Roots(root_ids) => root_ids,
1485 };
1486
1487 let is_new_ancestor = RevsetExpression::commits(target_root_ids.clone())
1490 .range(&RevsetExpression::commits(new_parent_ids.to_owned()))
1491 .evaluate(repo)
1492 .map_err(|err| err.into_backend_error())?
1493 .containing_fn();
1494
1495 let mut duplicate_divergent = Vec::new();
1496 for (target_commit, ancestor_candidates) in divergent_changes {
1501 for ancestor_candidate_id in ancestor_candidates {
1502 if !is_new_ancestor(&ancestor_candidate_id)
1503 .await
1504 .map_err(|err| err.into_backend_error())?
1505 {
1506 continue;
1507 }
1508
1509 let ancestor_candidate = repo
1510 .store()
1511 .get_commit_async(&ancestor_candidate_id)
1512 .await?;
1513 let new_tree =
1514 rebase_to_dest_parent(repo, slice::from_ref(target_commit), &ancestor_candidate)
1515 .await?;
1516 if new_tree.tree_ids() == ancestor_candidate.tree_ids() {
1519 duplicate_divergent.push(target_commit.clone());
1520 break;
1521 }
1522 }
1523 }
1524 Ok(duplicate_divergent)
1525}