1#![expect(missing_docs)]
16
17use std::collections::HashMap;
18use std::collections::HashSet;
19use std::slice;
20use std::sync::Arc;
21
22use futures::future::try_join_all;
23use futures::try_join;
24use indexmap::IndexMap;
25use indexmap::IndexSet;
26use itertools::Itertools as _;
27use pollster::FutureExt 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::Matcher;
43use crate::matchers::Visit;
44use crate::merge::Diff;
45use crate::merge::Merge;
46use crate::merged_tree::MergedTree;
47use crate::merged_tree_builder::MergedTreeBuilder;
48use crate::repo::MutableRepo;
49use crate::repo::Repo;
50use crate::repo_path::RepoPath;
51use crate::revset::RevsetExpression;
52use crate::revset::RevsetIteratorExt as _;
53use crate::store::Store;
54
55#[instrument(skip(repo))]
57pub async fn merge_commit_trees(repo: &dyn Repo, commits: &[Commit]) -> BackendResult<MergedTree> {
58 if let [commit] = commits {
59 Ok(commit.tree())
60 } else {
61 merge_commit_trees_no_resolve_without_repo(repo.store(), repo.index(), commits)
62 .await?
63 .resolve()
64 .await
65 }
66}
67
68#[instrument(skip(index))]
70pub async fn merge_commit_trees_no_resolve_without_repo(
71 store: &Arc<Store>,
72 index: &dyn Index,
73 commits: &[Commit],
74) -> BackendResult<MergedTree> {
75 let commit_ids = commits
76 .iter()
77 .map(|commit| commit.id().clone())
78 .collect_vec();
79 let commit_id_merge = find_recursive_merge_commits(store, index, commit_ids)?;
80 let tree_merge: Merge<(MergedTree, String)> = commit_id_merge
81 .try_map_async(async |commit_id| {
82 let commit = store.get_commit_async(commit_id).await?;
83 Ok::<_, BackendError>((commit.tree(), commit.conflict_label()))
84 })
85 .await?;
86 Ok(MergedTree::merge_no_resolve(tree_merge))
87}
88
89pub fn find_recursive_merge_commits(
91 store: &Arc<Store>,
92 index: &dyn Index,
93 mut commit_ids: Vec<CommitId>,
94) -> BackendResult<Merge<CommitId>> {
95 if commit_ids.is_empty() {
96 Ok(Merge::resolved(store.root_commit_id().clone()))
97 } else if commit_ids.len() == 1 {
98 Ok(Merge::resolved(commit_ids.pop().unwrap()))
99 } else {
100 let mut result = Merge::resolved(commit_ids[0].clone());
101 for (i, other_commit_id) in commit_ids.iter().enumerate().skip(1) {
102 let ancestor_ids = index
103 .common_ancestors(&commit_ids[0..i], &commit_ids[i..][..1])
104 .map_err(|err| BackendError::Other(err.into()))?;
106 let ancestor_merge = find_recursive_merge_commits(store, index, ancestor_ids)?;
107 result = Merge::from_vec(vec![
108 result,
109 ancestor_merge,
110 Merge::resolved(other_commit_id.clone()),
111 ])
112 .flatten();
113 }
114 Ok(result)
115 }
116}
117
118pub async fn restore_tree(
120 source: &MergedTree,
121 destination: &MergedTree,
122 source_label: String,
123 destination_label: String,
124 matcher: &dyn Matcher,
125) -> BackendResult<MergedTree> {
126 if matcher.visit(RepoPath::root()) == Visit::AllRecursively {
127 return Ok(source.clone());
129 }
130
131 let select_matching =
132 |tree: &MergedTree, labels: ConflictLabels| -> BackendResult<MergedTree> {
133 let empty_tree_ids = Merge::repeated(
134 tree.store().empty_tree_id().clone(),
135 tree.tree_ids().num_sides(),
136 );
137 let labeled_empty_tree = MergedTree::new(tree.store().clone(), empty_tree_ids, labels);
138 let mut builder = MergedTreeBuilder::new(labeled_empty_tree);
141 for (path, value) in tree.entries_matching(matcher) {
142 builder.set_or_remove(path, value?);
145 }
146 builder.write_tree()
147 };
148
149 const RESTORE_BASE_LABEL: &str = "base files for restore";
150
151 let base_labels = ConflictLabels::from_merge(destination.labels().as_merge().map(|label| {
154 if label.is_empty() || label.starts_with(RESTORE_BASE_LABEL) {
155 label.clone()
156 } else {
157 format!("{RESTORE_BASE_LABEL} (from {label})")
158 }
159 }));
160
161 MergedTree::merge(Merge::from_vec(vec![
173 (
174 destination.clone(),
175 format!("{destination_label} (restore destination)"),
176 ),
177 (
178 select_matching(destination, base_labels)?,
179 format!("{RESTORE_BASE_LABEL} (from {destination_label})"),
180 ),
181 (
182 select_matching(source, source.labels().clone())?,
183 format!("restored files (from {source_label})"),
184 ),
185 ]))
186 .await
187}
188
189pub async fn rebase_commit(
190 mut_repo: &mut MutableRepo,
191 old_commit: Commit,
192 new_parents: Vec<CommitId>,
193) -> BackendResult<Commit> {
194 let rewriter = CommitRewriter::new(mut_repo, old_commit, new_parents);
195 let builder = rewriter.rebase().await?;
196 builder.write()
197}
198
199pub struct CommitRewriter<'repo> {
201 mut_repo: &'repo mut MutableRepo,
202 old_commit: Commit,
203 new_parents: Vec<CommitId>,
204}
205
206impl<'repo> CommitRewriter<'repo> {
207 pub fn new(
209 mut_repo: &'repo mut MutableRepo,
210 old_commit: Commit,
211 new_parents: Vec<CommitId>,
212 ) -> Self {
213 Self {
214 mut_repo,
215 old_commit,
216 new_parents,
217 }
218 }
219
220 pub fn repo_mut(&mut self) -> &mut MutableRepo {
222 self.mut_repo
223 }
224
225 pub fn old_commit(&self) -> &Commit {
227 &self.old_commit
228 }
229
230 pub fn new_parents(&self) -> &[CommitId] {
232 &self.new_parents
233 }
234
235 pub fn set_new_parents(&mut self, new_parents: Vec<CommitId>) {
237 self.new_parents = new_parents;
238 }
239
240 pub fn set_new_rewritten_parents(&mut self, unrewritten_parents: &[CommitId]) {
243 self.new_parents = self.mut_repo.new_parents(unrewritten_parents);
244 }
245
246 pub fn replace_parent<'a>(
249 &mut self,
250 old_parent: &CommitId,
251 new_parents: impl IntoIterator<Item = &'a CommitId>,
252 ) {
253 if let Some(i) = self.new_parents.iter().position(|p| p == old_parent) {
254 self.new_parents
255 .splice(i..i + 1, new_parents.into_iter().cloned());
256 let mut unique = HashSet::new();
257 self.new_parents.retain(|p| unique.insert(p.clone()));
258 }
259 }
260
261 pub fn parents_changed(&self) -> bool {
264 self.new_parents != self.old_commit.parent_ids()
265 }
266
267 pub fn simplify_ancestor_merge(&mut self) -> IndexResult<()> {
270 let head_set: HashSet<_> = self
271 .mut_repo
272 .index()
273 .heads(&mut self.new_parents.iter())?
274 .into_iter()
275 .collect();
276 self.new_parents.retain(|parent| head_set.contains(parent));
277 Ok(())
278 }
279
280 pub fn abandon(self) {
284 let old_commit_id = self.old_commit.id().clone();
285 let new_parents = self.new_parents;
286 self.mut_repo
287 .record_abandoned_commit_with_parents(old_commit_id, new_parents);
288 }
289
290 pub async fn rebase_with_empty_behavior(
293 self,
294 empty: EmptyBehavior,
295 ) -> BackendResult<Option<CommitBuilder<'repo>>> {
296 let old_parents_fut = self.old_commit.parents_async();
297 let new_parents_fut = try_join_all(
298 self.new_parents
299 .iter()
300 .map(|new_parent_id| self.mut_repo.store().get_commit_async(new_parent_id)),
301 );
302 let (old_parents, new_parents) = try_join!(old_parents_fut, new_parents_fut)?;
303 let old_parent_trees = old_parents
304 .iter()
305 .map(|parent| parent.tree_ids().clone())
306 .collect_vec();
307 let new_parent_trees = new_parents
308 .iter()
309 .map(|parent| parent.tree_ids().clone())
310 .collect_vec();
311
312 let (was_empty, new_tree) = if new_parent_trees == old_parent_trees {
313 (
314 true,
317 self.old_commit.tree(),
319 )
320 } else {
321 let old_base_tree_fut = merge_commit_trees(self.mut_repo, &old_parents);
325 let new_base_tree_fut = merge_commit_trees(self.mut_repo, &new_parents);
326 let old_tree = self.old_commit.tree();
327 let (old_base_tree, new_base_tree) = try_join!(old_base_tree_fut, new_base_tree_fut)?;
328 (
329 old_base_tree.tree_ids() == self.old_commit.tree_ids(),
330 MergedTree::merge(Merge::from_vec(vec![
331 (
332 new_base_tree,
333 format!(
334 "{} (rebase destination)",
335 conflict_label_for_commits(&new_parents)
336 ),
337 ),
338 (
339 old_base_tree,
340 format!(
341 "{} (parents of rebased revision)",
342 conflict_label_for_commits(&old_parents)
343 ),
344 ),
345 (
346 old_tree,
347 format!("{} (rebased revision)", self.old_commit.conflict_label()),
348 ),
349 ]))
350 .await?,
351 )
352 };
353 if let [parent] = &new_parents[..] {
356 let should_abandon = match empty {
357 EmptyBehavior::Keep => false,
358 EmptyBehavior::AbandonNewlyEmpty => {
359 parent.tree_ids() == new_tree.tree_ids() && !was_empty
360 }
361 EmptyBehavior::AbandonAllEmpty => parent.tree_ids() == new_tree.tree_ids(),
362 };
363 if should_abandon {
364 self.abandon();
365 return Ok(None);
366 }
367 }
368
369 let builder = self
370 .mut_repo
371 .rewrite_commit(&self.old_commit)
372 .set_parents(self.new_parents)
373 .set_tree(new_tree);
374 Ok(Some(builder))
375 }
376
377 pub async fn rebase(self) -> BackendResult<CommitBuilder<'repo>> {
380 let builder = self.rebase_with_empty_behavior(EmptyBehavior::Keep).await?;
381 Ok(builder.unwrap())
382 }
383
384 pub fn reparent(self) -> CommitBuilder<'repo> {
387 self.mut_repo
388 .rewrite_commit(&self.old_commit)
389 .set_parents(self.new_parents)
390 }
391}
392
393#[derive(Debug)]
394pub enum RebasedCommit {
395 Rewritten(Commit),
396 Abandoned { parent_id: CommitId },
397}
398
399pub fn rebase_commit_with_options(
400 mut rewriter: CommitRewriter<'_>,
401 options: &RebaseOptions,
402) -> BackendResult<RebasedCommit> {
403 if options.simplify_ancestor_merge {
405 rewriter
406 .simplify_ancestor_merge()
407 .map_err(|err| BackendError::Other(err.into()))?;
409 }
410
411 let single_parent = match &rewriter.new_parents[..] {
412 [parent_id] => Some(parent_id.clone()),
413 _ => None,
414 };
415 let new_parents_len = rewriter.new_parents.len();
416 if let Some(builder) = rewriter
417 .rebase_with_empty_behavior(options.empty)
418 .block_on()?
419 {
420 let new_commit = builder.write()?;
421 Ok(RebasedCommit::Rewritten(new_commit))
422 } else {
423 assert_eq!(new_parents_len, 1);
424 Ok(RebasedCommit::Abandoned {
425 parent_id: single_parent.unwrap(),
426 })
427 }
428}
429
430pub fn rebase_to_dest_parent(
433 repo: &dyn Repo,
434 sources: &[Commit],
435 destination: &Commit,
436) -> BackendResult<MergedTree> {
437 if let [source] = sources
438 && source.parent_ids() == destination.parent_ids()
439 {
440 return Ok(source.tree());
441 }
442
443 let diffs: Vec<_> = sources
444 .iter()
445 .map(|source| -> BackendResult<_> {
446 Ok(Diff::new(
447 (
448 source.parent_tree(repo)?,
449 format!("{} (original parents)", source.parents_conflict_label()?),
450 ),
451 (
452 source.tree(),
453 format!("{} (original revision)", source.conflict_label()),
454 ),
455 ))
456 })
457 .try_collect()?;
458 MergedTree::merge(Merge::from_diffs(
459 (
460 destination.parent_tree(repo)?,
461 format!("{} (new parents)", destination.parents_conflict_label()?),
462 ),
463 diffs,
464 ))
465 .block_on()
466}
467
468#[derive(Clone, Copy, Default, PartialEq, Eq, Debug)]
469pub enum EmptyBehavior {
470 #[default]
472 Keep,
473 AbandonNewlyEmpty,
477 AbandonAllEmpty,
481}
482
483#[derive(Clone, Debug, Default)]
490pub struct RebaseOptions {
491 pub empty: EmptyBehavior,
492 pub rewrite_refs: RewriteRefsOptions,
493 pub simplify_ancestor_merge: bool,
496}
497
498#[derive(Clone, Debug, Default)]
500pub struct RewriteRefsOptions {
501 pub delete_abandoned_bookmarks: bool,
506}
507
508#[derive(Debug)]
509pub struct MoveCommitsStats {
510 pub num_rebased_targets: u32,
512 pub num_rebased_descendants: u32,
514 pub num_skipped_rebases: u32,
517 pub num_abandoned_empty: u32,
519 pub rebased_commits: HashMap<CommitId, RebasedCommit>,
521}
522
523#[derive(Clone, Debug)]
525pub struct MoveCommitsLocation {
526 pub new_parent_ids: Vec<CommitId>,
527 pub new_child_ids: Vec<CommitId>,
528 pub target: MoveCommitsTarget,
529}
530
531#[derive(Clone, Debug)]
532pub enum MoveCommitsTarget {
533 Commits(Vec<CommitId>),
536 Roots(Vec<CommitId>),
538}
539
540#[derive(Clone, Debug)]
541pub struct ComputedMoveCommits {
542 target_commit_ids: IndexSet<CommitId>,
543 descendants: Vec<Commit>,
544 commit_new_parents_map: HashMap<CommitId, Vec<CommitId>>,
545 to_abandon: HashSet<CommitId>,
546}
547
548impl ComputedMoveCommits {
549 fn empty() -> Self {
550 Self {
551 target_commit_ids: IndexSet::new(),
552 descendants: vec![],
553 commit_new_parents_map: HashMap::new(),
554 to_abandon: HashSet::new(),
555 }
556 }
557
558 pub fn record_to_abandon(&mut self, commit_ids: impl IntoIterator<Item = CommitId>) {
565 self.to_abandon.extend(commit_ids);
566 }
567
568 pub fn apply(
569 self,
570 mut_repo: &mut MutableRepo,
571 options: &RebaseOptions,
572 ) -> BackendResult<MoveCommitsStats> {
573 apply_move_commits(mut_repo, self, options)
574 }
575}
576
577pub fn move_commits(
586 mut_repo: &mut MutableRepo,
587 loc: &MoveCommitsLocation,
588 options: &RebaseOptions,
589) -> BackendResult<MoveCommitsStats> {
590 compute_move_commits(mut_repo, loc)?.apply(mut_repo, options)
591}
592
593pub fn compute_move_commits(
594 repo: &MutableRepo,
595 loc: &MoveCommitsLocation,
596) -> BackendResult<ComputedMoveCommits> {
597 let target_commit_ids: IndexSet<CommitId>;
598 let connected_target_commits: Vec<Commit>;
599 let connected_target_commits_internal_parents: HashMap<CommitId, IndexSet<CommitId>>;
600 let target_roots: HashSet<CommitId>;
601
602 match &loc.target {
603 MoveCommitsTarget::Commits(commit_ids) => {
604 if commit_ids.is_empty() {
605 return Ok(ComputedMoveCommits::empty());
606 }
607
608 target_commit_ids = commit_ids.iter().cloned().collect();
609
610 connected_target_commits = RevsetExpression::commits(commit_ids.clone())
611 .connected()
612 .evaluate(repo)
613 .map_err(|err| err.into_backend_error())?
614 .iter()
615 .commits(repo.store())
616 .try_collect()
617 .map_err(|err| err.into_backend_error())?;
618 connected_target_commits_internal_parents =
619 compute_internal_parents_within(&target_commit_ids, &connected_target_commits);
620
621 target_roots = connected_target_commits_internal_parents
622 .iter()
623 .filter(|&(commit_id, parents)| {
624 target_commit_ids.contains(commit_id) && parents.is_empty()
625 })
626 .map(|(commit_id, _)| commit_id.clone())
627 .collect();
628 }
629 MoveCommitsTarget::Roots(root_ids) => {
630 if root_ids.is_empty() {
631 return Ok(ComputedMoveCommits::empty());
632 }
633
634 target_commit_ids = RevsetExpression::commits(root_ids.clone())
635 .descendants()
636 .evaluate(repo)
637 .map_err(|err| err.into_backend_error())?
638 .iter()
639 .try_collect()
640 .map_err(|err| err.into_backend_error())?;
641
642 connected_target_commits = target_commit_ids
643 .iter()
644 .map(|id| repo.store().get_commit(id))
645 .try_collect()?;
646 connected_target_commits_internal_parents = HashMap::new();
649 target_roots = root_ids.iter().cloned().collect();
650 }
651 }
652
653 let mut target_commits_external_parents: HashMap<CommitId, IndexSet<CommitId>> = HashMap::new();
657 for id in target_commit_ids.iter().rev() {
658 let commit = repo.store().get_commit(id)?;
659 let mut new_parents = IndexSet::new();
660 for old_parent in commit.parent_ids() {
661 if let Some(parents) = target_commits_external_parents.get(old_parent) {
662 new_parents.extend(parents.iter().cloned());
663 } else {
664 new_parents.insert(old_parent.clone());
665 }
666 }
667 target_commits_external_parents.insert(commit.id().clone(), new_parents);
668 }
669
670 let new_parent_ids: Vec<_> = loc
674 .new_parent_ids
675 .iter()
676 .flat_map(|parent_id| {
677 if let Some(parent_ids) = target_commits_external_parents.get(parent_id) {
678 parent_ids.iter().cloned().collect_vec()
679 } else {
680 vec![parent_id.clone()]
681 }
682 })
683 .collect();
684
685 let new_children: Vec<_> = if loc
689 .new_child_ids
690 .iter()
691 .any(|id| target_commit_ids.contains(id))
692 {
693 let target_commits_descendants: Vec<_> =
694 RevsetExpression::commits(target_commit_ids.iter().cloned().collect_vec())
695 .union(
696 &RevsetExpression::commits(target_commit_ids.iter().cloned().collect_vec())
697 .children(),
698 )
699 .evaluate(repo)
700 .map_err(|err| err.into_backend_error())?
701 .iter()
702 .commits(repo.store())
703 .try_collect()
704 .map_err(|err| err.into_backend_error())?;
705
706 let mut target_commit_external_descendants: HashMap<CommitId, IndexSet<Commit>> =
709 HashMap::new();
710 for commit in &target_commits_descendants {
713 if !target_commit_external_descendants.contains_key(commit.id()) {
714 let children = if target_commit_ids.contains(commit.id()) {
715 IndexSet::new()
716 } else {
717 IndexSet::from([commit.clone()])
718 };
719 target_commit_external_descendants.insert(commit.id().clone(), children);
720 }
721
722 let children = target_commit_external_descendants
723 .get(commit.id())
724 .unwrap()
725 .iter()
726 .cloned()
727 .collect_vec();
728 for parent_id in commit.parent_ids() {
729 if target_commit_ids.contains(parent_id) {
730 if let Some(target_children) =
731 target_commit_external_descendants.get_mut(parent_id)
732 {
733 target_children.extend(children.iter().cloned());
734 } else {
735 target_commit_external_descendants
736 .insert(parent_id.clone(), children.iter().cloned().collect());
737 }
738 }
739 }
740 }
741
742 let mut new_children = Vec::new();
743 for id in &loc.new_child_ids {
744 if let Some(children) = target_commit_external_descendants.get(id) {
745 new_children.extend(children.iter().cloned());
746 } else {
747 new_children.push(repo.store().get_commit(id)?);
748 }
749 }
750 new_children
751 } else {
752 loc.new_child_ids
753 .iter()
754 .map(|id| repo.store().get_commit(id))
755 .try_collect()?
756 };
757
758 let new_children_parents: HashMap<_, _> = if !new_children.is_empty() {
761 let target_heads = compute_commits_heads(&target_commit_ids, &connected_target_commits);
764
765 new_children
766 .iter()
767 .map(|child_commit| {
768 let mut new_child_parent_ids = IndexSet::new();
769 for old_child_parent_id in child_commit.parent_ids() {
770 let old_child_parent_ids = if let Some(parents) =
772 target_commits_external_parents.get(old_child_parent_id)
773 {
774 parents.iter().collect_vec()
775 } else {
776 vec![old_child_parent_id]
777 };
778
779 for id in old_child_parent_ids {
783 if new_parent_ids.contains(id) {
784 new_child_parent_ids.extend(target_heads.clone());
785 } else {
786 new_child_parent_ids.insert(id.clone());
787 }
788 }
789 }
790
791 new_child_parent_ids.extend(target_heads.clone());
794
795 (
796 child_commit.id().clone(),
797 new_child_parent_ids.into_iter().collect_vec(),
798 )
799 })
800 .collect()
801 } else {
802 HashMap::new()
803 };
804
805 let mut roots = target_roots.iter().cloned().collect_vec();
808 roots.extend(new_children.iter().ids().cloned());
809
810 let descendants = repo.find_descendants_for_rebase(roots.clone())?;
811 let commit_new_parents_map = descendants
812 .iter()
813 .map(|commit| -> BackendResult<_> {
814 let commit_id = commit.id();
815 let new_parent_ids =
816 if let Some(new_child_parents) = new_children_parents.get(commit_id) {
817 new_child_parents.clone()
819 } else if target_commit_ids.contains(commit_id) {
820 if target_roots.contains(commit_id) {
822 new_parent_ids.clone()
825 } else {
826 let mut new_parents = vec![];
834 for parent_id in commit.parent_ids() {
835 if target_commit_ids.contains(parent_id) {
836 new_parents.push(parent_id.clone());
837 } else if let Some(parents) =
838 connected_target_commits_internal_parents.get(parent_id)
839 {
840 new_parents.extend(parents.iter().cloned());
841 } else if !fallible_any(&new_children, |child| {
842 repo.index().is_ancestor(child.id(), parent_id)
843 })
844 .map_err(|err| BackendError::Other(err.into()))?
846 {
847 new_parents.push(parent_id.clone());
848 }
849 }
850 new_parents
851 }
852 } else if commit
853 .parent_ids()
854 .iter()
855 .any(|id| target_commits_external_parents.contains_key(id))
856 {
857 let mut new_parents = vec![];
860 for parent in commit.parent_ids() {
861 if let Some(parents) = target_commits_external_parents.get(parent) {
862 new_parents.extend(parents.iter().cloned());
863 } else {
864 new_parents.push(parent.clone());
865 }
866 }
867 new_parents
868 } else {
869 commit.parent_ids().iter().cloned().collect_vec()
870 };
871 Ok((commit.id().clone(), new_parent_ids))
872 })
873 .try_collect()?;
874
875 Ok(ComputedMoveCommits {
876 target_commit_ids,
877 descendants,
878 commit_new_parents_map,
879 to_abandon: HashSet::new(),
880 })
881}
882
883fn apply_move_commits(
884 mut_repo: &mut MutableRepo,
885 commits: ComputedMoveCommits,
886 options: &RebaseOptions,
887) -> BackendResult<MoveCommitsStats> {
888 let mut num_rebased_targets = 0;
889 let mut num_rebased_descendants = 0;
890 let mut num_skipped_rebases = 0;
891 let mut num_abandoned_empty = 0;
892
893 let rebase_descendant_options = &RebaseOptions {
895 empty: EmptyBehavior::Keep,
896 rewrite_refs: options.rewrite_refs.clone(),
897 simplify_ancestor_merge: options.simplify_ancestor_merge,
898 };
899
900 let mut rebased_commits: HashMap<CommitId, RebasedCommit> = HashMap::new();
901 mut_repo.transform_commits(
902 commits.descendants,
903 &commits.commit_new_parents_map,
904 &options.rewrite_refs,
905 async |rewriter| {
906 let old_commit_id = rewriter.old_commit().id().clone();
907 if commits.to_abandon.contains(&old_commit_id) {
908 rewriter.abandon();
909 } else if rewriter.parents_changed() {
910 let is_target_commit = commits.target_commit_ids.contains(&old_commit_id);
911 let rebased_commit = rebase_commit_with_options(
912 rewriter,
913 if is_target_commit {
914 options
915 } else {
916 rebase_descendant_options
917 },
918 )?;
919 if let RebasedCommit::Abandoned { .. } = rebased_commit {
920 num_abandoned_empty += 1;
921 } else if is_target_commit {
922 num_rebased_targets += 1;
923 } else {
924 num_rebased_descendants += 1;
925 }
926 rebased_commits.insert(old_commit_id, rebased_commit);
927 } else {
928 num_skipped_rebases += 1;
929 }
930
931 Ok(())
932 },
933 )?;
934
935 Ok(MoveCommitsStats {
936 num_rebased_targets,
937 num_rebased_descendants,
938 num_skipped_rebases,
939 num_abandoned_empty,
940 rebased_commits,
941 })
942}
943
944#[derive(Debug, Default)]
945pub struct DuplicateCommitsStats {
946 pub duplicated_commits: IndexMap<CommitId, Commit>,
948 pub num_rebased: u32,
951}
952
953pub async fn duplicate_commits(
971 mut_repo: &mut MutableRepo,
972 target_commit_ids: &[CommitId],
973 target_descriptions: &HashMap<CommitId, String>,
974 parent_commit_ids: &[CommitId],
975 children_commit_ids: &[CommitId],
976) -> BackendResult<DuplicateCommitsStats> {
977 if target_commit_ids.is_empty() {
978 return Ok(DuplicateCommitsStats::default());
979 }
980
981 let mut duplicated_old_to_new: IndexMap<CommitId, Commit> = IndexMap::new();
982 let mut num_rebased = 0;
983
984 let target_commit_ids: IndexSet<_> = target_commit_ids.iter().cloned().collect();
985
986 let connected_target_commits: Vec<_> =
987 RevsetExpression::commits(target_commit_ids.iter().cloned().collect_vec())
988 .connected()
989 .evaluate(mut_repo)
990 .map_err(|err| err.into_backend_error())?
991 .iter()
992 .commits(mut_repo.store())
993 .try_collect()
994 .map_err(|err| err.into_backend_error())?;
995
996 let target_commits_internal_parents = {
1003 let mut target_commits_internal_parents =
1004 compute_internal_parents_within(&target_commit_ids, &connected_target_commits);
1005 target_commits_internal_parents.retain(|id, _| target_commit_ids.contains(id));
1006 target_commits_internal_parents
1007 };
1008
1009 let target_root_ids: HashSet<_> = target_commits_internal_parents
1011 .iter()
1012 .filter(|(_, parents)| parents.is_empty())
1013 .map(|(commit_id, _)| commit_id.clone())
1014 .collect();
1015
1016 let target_head_ids = if !children_commit_ids.is_empty() {
1019 compute_commits_heads(&target_commit_ids, &connected_target_commits)
1020 } else {
1021 vec![]
1022 };
1023
1024 for original_commit_id in target_commit_ids.iter().rev() {
1027 let original_commit = mut_repo
1028 .store()
1029 .get_commit_async(original_commit_id)
1030 .await?;
1031 let new_parent_ids = if target_root_ids.contains(original_commit_id) {
1032 parent_commit_ids.to_vec()
1033 } else {
1034 target_commits_internal_parents
1035 .get(original_commit_id)
1036 .unwrap()
1037 .iter()
1038 .map(|id| {
1040 duplicated_old_to_new
1041 .get(id)
1042 .map_or(id, |commit| commit.id())
1043 .clone()
1044 })
1045 .collect()
1046 };
1047 let mut new_commit_builder = CommitRewriter::new(mut_repo, original_commit, new_parent_ids)
1048 .rebase()
1049 .await?
1050 .clear_rewrite_source()
1051 .generate_new_change_id();
1052 if let Some(desc) = target_descriptions.get(original_commit_id) {
1053 new_commit_builder = new_commit_builder.set_description(desc);
1054 }
1055 duplicated_old_to_new.insert(original_commit_id.clone(), new_commit_builder.write()?);
1056 }
1057
1058 let target_head_ids = target_head_ids
1061 .into_iter()
1062 .map(|commit_id| {
1063 duplicated_old_to_new
1064 .get(&commit_id)
1065 .map_or(commit_id, |commit| commit.id().clone())
1066 })
1067 .collect_vec();
1068
1069 let children_commit_ids_set: HashSet<CommitId> = children_commit_ids.iter().cloned().collect();
1071 mut_repo.transform_descendants(children_commit_ids.to_vec(), async |mut rewriter| {
1072 if children_commit_ids_set.contains(rewriter.old_commit().id()) {
1073 let mut child_new_parent_ids = IndexSet::new();
1074 for old_parent_id in rewriter.old_commit().parent_ids() {
1075 if parent_commit_ids.contains(old_parent_id) {
1080 child_new_parent_ids.extend(target_head_ids.clone());
1081 } else {
1082 child_new_parent_ids.insert(old_parent_id.clone());
1083 }
1084 }
1085 child_new_parent_ids.extend(target_head_ids.clone());
1088 rewriter.set_new_parents(child_new_parent_ids.into_iter().collect());
1089 }
1090 num_rebased += 1;
1091 rewriter.rebase().await?.write()?;
1092 Ok(())
1093 })?;
1094
1095 Ok(DuplicateCommitsStats {
1096 duplicated_commits: duplicated_old_to_new,
1097 num_rebased,
1098 })
1099}
1100
1101pub fn duplicate_commits_onto_parents(
1111 mut_repo: &mut MutableRepo,
1112 target_commits: &[CommitId],
1113 target_descriptions: &HashMap<CommitId, String>,
1114) -> BackendResult<DuplicateCommitsStats> {
1115 if target_commits.is_empty() {
1116 return Ok(DuplicateCommitsStats::default());
1117 }
1118
1119 let mut duplicated_old_to_new: IndexMap<CommitId, Commit> = IndexMap::new();
1120
1121 for original_commit_id in target_commits.iter().rev() {
1124 let original_commit = mut_repo.store().get_commit(original_commit_id)?;
1125 let new_parent_ids = original_commit
1126 .parent_ids()
1127 .iter()
1128 .map(|id| {
1129 duplicated_old_to_new
1130 .get(id)
1131 .map_or(id, |commit| commit.id())
1132 .clone()
1133 })
1134 .collect();
1135 let mut new_commit_builder = mut_repo
1136 .rewrite_commit(&original_commit)
1137 .clear_rewrite_source()
1138 .generate_new_change_id()
1139 .set_parents(new_parent_ids);
1140 if let Some(desc) = target_descriptions.get(original_commit_id) {
1141 new_commit_builder = new_commit_builder.set_description(desc);
1142 }
1143 duplicated_old_to_new.insert(original_commit_id.clone(), new_commit_builder.write()?);
1144 }
1145
1146 Ok(DuplicateCommitsStats {
1147 duplicated_commits: duplicated_old_to_new,
1148 num_rebased: 0,
1149 })
1150}
1151
1152fn compute_internal_parents_within(
1160 target_commit_ids: &IndexSet<CommitId>,
1161 graph_commits: &[Commit],
1162) -> HashMap<CommitId, IndexSet<CommitId>> {
1163 let mut internal_parents: HashMap<CommitId, IndexSet<CommitId>> = HashMap::new();
1164 for commit in graph_commits.iter().rev() {
1165 let mut new_parents = IndexSet::new();
1168 for old_parent in commit.parent_ids() {
1169 if target_commit_ids.contains(old_parent) {
1170 new_parents.insert(old_parent.clone());
1171 } else if let Some(parents) = internal_parents.get(old_parent) {
1172 new_parents.extend(parents.iter().cloned());
1173 }
1174 }
1175 internal_parents.insert(commit.id().clone(), new_parents);
1176 }
1177 internal_parents
1178}
1179
1180fn compute_commits_heads(
1186 target_commit_ids: &IndexSet<CommitId>,
1187 connected_target_commits: &[Commit],
1188) -> Vec<CommitId> {
1189 let mut target_head_ids: HashSet<CommitId> = HashSet::new();
1190 for commit in connected_target_commits.iter().rev() {
1191 target_head_ids.insert(commit.id().clone());
1192 for old_parent in commit.parent_ids() {
1193 target_head_ids.remove(old_parent);
1194 }
1195 }
1196 connected_target_commits
1197 .iter()
1198 .rev()
1199 .filter(|commit| {
1200 target_head_ids.contains(commit.id()) && target_commit_ids.contains(commit.id())
1201 })
1202 .map(|commit| commit.id().clone())
1203 .collect_vec()
1204}
1205
1206#[derive(Debug)]
1207pub struct CommitWithSelection {
1208 pub commit: Commit,
1209 pub selected_tree: MergedTree,
1210 pub parent_tree: MergedTree,
1211}
1212
1213impl CommitWithSelection {
1214 pub fn is_full_selection(&self) -> bool {
1216 self.selected_tree.tree_ids() == self.commit.tree_ids()
1217 }
1218
1219 pub fn is_empty_selection(&self) -> bool {
1225 self.selected_tree.tree_ids() == self.parent_tree.tree_ids()
1226 }
1227
1228 pub fn diff_with_labels(
1232 &self,
1233 parent_tree_label: &str,
1234 selected_tree_label: &str,
1235 full_selection_label: &str,
1236 ) -> BackendResult<Diff<(MergedTree, String)>> {
1237 let parent_tree_label = format!(
1238 "{} ({parent_tree_label})",
1239 self.commit.parents_conflict_label()?
1240 );
1241
1242 let commit_label = self.commit.conflict_label();
1243 let selected_tree_label = if self.is_full_selection() {
1244 format!("{commit_label} ({full_selection_label})")
1245 } else {
1246 format!("{selected_tree_label} (from {commit_label})")
1247 };
1248
1249 Ok(Diff::new(
1250 (self.parent_tree.clone(), parent_tree_label),
1251 (self.selected_tree.clone(), selected_tree_label),
1252 ))
1253 }
1254}
1255
1256#[must_use]
1258pub struct SquashedCommit<'repo> {
1259 pub commit_builder: CommitBuilder<'repo>,
1261 pub abandoned_commits: Vec<Commit>,
1263}
1264
1265pub fn squash_commits<'repo>(
1269 repo: &'repo mut MutableRepo,
1270 sources: &[CommitWithSelection],
1271 destination: &Commit,
1272 keep_emptied: bool,
1273) -> BackendResult<Option<SquashedCommit<'repo>>> {
1274 struct SourceCommit<'a> {
1275 commit: &'a CommitWithSelection,
1276 diff: Diff<(MergedTree, String)>,
1277 abandon: bool,
1278 }
1279 let mut source_commits = vec![];
1280 for source in sources {
1281 let abandon = !keep_emptied && source.is_full_selection();
1282 if !abandon && source.is_empty_selection() {
1283 continue;
1287 }
1288
1289 source_commits.push(SourceCommit {
1292 commit: source,
1293 diff: source.diff_with_labels(
1294 "parents of squashed revision",
1295 "selected changes for squash",
1296 "squashed revision",
1297 )?,
1298 abandon,
1299 });
1300 }
1301
1302 if source_commits.is_empty() {
1303 return Ok(None);
1304 }
1305
1306 let mut abandoned_commits = vec![];
1307 for source in &source_commits {
1308 if source.abandon {
1309 repo.record_abandoned_commit(&source.commit.commit);
1310 abandoned_commits.push(source.commit.commit.clone());
1311 } else {
1312 let source_tree = source.commit.commit.tree();
1313 let new_source_tree = MergedTree::merge(Merge::from_diffs(
1315 (source_tree, source.commit.commit.conflict_label()),
1316 [source.diff.clone().invert()],
1317 ))
1318 .block_on()?;
1319 repo.rewrite_commit(&source.commit.commit)
1320 .set_tree(new_source_tree)
1321 .write()?;
1322 }
1323 }
1324
1325 let mut rewritten_destination = destination.clone();
1326 if fallible_any(sources, |source| {
1327 repo.index()
1328 .is_ancestor(source.commit.id(), destination.id())
1329 })
1330 .map_err(|err| BackendError::Other(err.into()))?
1332 {
1333 let options = RebaseOptions::default();
1338 repo.rebase_descendants_with_options(&options, |old_commit, rebased_commit| {
1339 if old_commit.id() != destination.id() {
1340 return;
1341 }
1342 rewritten_destination = match rebased_commit {
1343 RebasedCommit::Rewritten(commit) => commit,
1344 RebasedCommit::Abandoned { .. } => panic!("all commits should be kept"),
1345 };
1346 })?;
1347 }
1348 let mut predecessors = vec![destination.id().clone()];
1349 predecessors.extend(
1350 source_commits
1351 .iter()
1352 .map(|source| source.commit.commit.id().clone()),
1353 );
1354 let destination_tree = MergedTree::merge(Merge::from_diffs(
1356 (
1357 rewritten_destination.tree(),
1358 format!("{} (squash destination)", destination.conflict_label()),
1359 ),
1360 source_commits.into_iter().map(|source| source.diff),
1361 ))
1362 .block_on()?;
1363
1364 let commit_builder = repo
1365 .rewrite_commit(&rewritten_destination)
1366 .set_tree(destination_tree)
1367 .set_predecessors(predecessors);
1368 Ok(Some(SquashedCommit {
1369 commit_builder,
1370 abandoned_commits,
1371 }))
1372}
1373
1374pub fn find_duplicate_divergent_commits(
1378 repo: &dyn Repo,
1379 new_parent_ids: &[CommitId],
1380 target: &MoveCommitsTarget,
1381) -> BackendResult<Vec<Commit>> {
1382 let target_commits: Vec<Commit> = match target {
1383 MoveCommitsTarget::Commits(commit_ids) => commit_ids
1384 .iter()
1385 .map(|commit_id| repo.store().get_commit(commit_id))
1386 .try_collect()?,
1387 MoveCommitsTarget::Roots(root_ids) => RevsetExpression::commits(root_ids.clone())
1388 .descendants()
1389 .evaluate(repo)
1390 .map_err(|err| err.into_backend_error())?
1391 .iter()
1392 .commits(repo.store())
1393 .try_collect()
1394 .map_err(|err| err.into_backend_error())?,
1395 };
1396 let target_commit_ids: HashSet<&CommitId> = target_commits.iter().map(Commit::id).collect();
1397
1398 let divergent_changes: Vec<(&Commit, Vec<CommitId>)> = target_commits
1401 .iter()
1402 .map(|target_commit| -> Result<_, BackendError> {
1403 let mut ancestor_candidates = repo
1404 .resolve_change_id(target_commit.change_id())
1405 .map_err(|err| BackendError::Other(err.into()))?
1407 .and_then(ResolvedChangeTargets::into_visible)
1408 .unwrap_or_default();
1409 ancestor_candidates.retain(|commit_id| !target_commit_ids.contains(commit_id));
1410 Ok((target_commit, ancestor_candidates))
1411 })
1412 .filter_ok(|(_, candidates)| !candidates.is_empty())
1413 .try_collect()?;
1414 if divergent_changes.is_empty() {
1415 return Ok(Vec::new());
1416 }
1417
1418 let target_root_ids = match target {
1419 MoveCommitsTarget::Commits(commit_ids) => commit_ids,
1420 MoveCommitsTarget::Roots(root_ids) => root_ids,
1421 };
1422
1423 let is_new_ancestor = RevsetExpression::commits(target_root_ids.clone())
1426 .range(&RevsetExpression::commits(new_parent_ids.to_owned()))
1427 .evaluate(repo)
1428 .map_err(|err| err.into_backend_error())?
1429 .containing_fn();
1430
1431 let mut duplicate_divergent = Vec::new();
1432 for (target_commit, ancestor_candidates) in divergent_changes {
1437 for ancestor_candidate_id in ancestor_candidates {
1438 if !is_new_ancestor(&ancestor_candidate_id).map_err(|err| err.into_backend_error())? {
1439 continue;
1440 }
1441
1442 let ancestor_candidate = repo.store().get_commit(&ancestor_candidate_id)?;
1443 let new_tree =
1444 rebase_to_dest_parent(repo, slice::from_ref(target_commit), &ancestor_candidate)?;
1445 if new_tree.tree_ids() == ancestor_candidate.tree_ids() {
1448 duplicate_divergent.push(target_commit.clone());
1449 break;
1450 }
1451 }
1452 }
1453 Ok(duplicate_divergent)
1454}