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 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 .into_iter()
295 .collect();
296 self.new_parents.retain(|parent| head_set.contains(parent));
297 Ok(())
298 }
299
300 pub fn abandon(self) {
304 let old_commit_id = self.old_commit.id().clone();
305 let new_parents = self.new_parents;
306 self.mut_repo
307 .record_abandoned_commit_with_parents(old_commit_id, new_parents);
308 }
309
310 pub async fn rebase_with_empty_behavior(
313 self,
314 empty: EmptyBehavior,
315 ) -> BackendResult<Option<CommitBuilder<'repo>>> {
316 let old_parents_fut = self.old_commit.parents();
317 let new_parents_fut = try_join_all(
318 self.new_parents
319 .iter()
320 .map(|new_parent_id| self.mut_repo.store().get_commit_async(new_parent_id)),
321 );
322 let (old_parents, new_parents) = try_join!(old_parents_fut, new_parents_fut)?;
323 let old_parent_trees = old_parents
324 .iter()
325 .map(|parent| parent.tree_ids().clone())
326 .collect_vec();
327 let new_parent_trees = new_parents
328 .iter()
329 .map(|parent| parent.tree_ids().clone())
330 .collect_vec();
331
332 let (was_empty, new_tree) = if new_parent_trees == old_parent_trees {
333 (
334 true,
337 self.old_commit.tree(),
339 )
340 } else {
341 let old_base_tree_fut = merge_commit_trees(self.mut_repo, &old_parents);
345 let new_base_tree_fut = merge_commit_trees(self.mut_repo, &new_parents);
346 let old_tree = self.old_commit.tree();
347 let (old_base_tree, new_base_tree) = try_join!(old_base_tree_fut, new_base_tree_fut)?;
348 (
349 old_base_tree.tree_ids() == self.old_commit.tree_ids(),
350 MergedTree::merge(Merge::from_vec(vec![
351 (
352 new_base_tree,
353 format!(
354 "{} (rebase destination)",
355 conflict_label_for_commits(&new_parents)
356 ),
357 ),
358 (
359 old_base_tree,
360 format!(
361 "{} (parents of rebased revision)",
362 conflict_label_for_commits(&old_parents)
363 ),
364 ),
365 (
366 old_tree,
367 format!("{} (rebased revision)", self.old_commit.conflict_label()),
368 ),
369 ]))
370 .await?,
371 )
372 };
373 if let [parent] = &new_parents[..] {
376 let should_abandon = match empty {
377 EmptyBehavior::Keep => false,
378 EmptyBehavior::AbandonNewlyEmpty => {
379 parent.tree_ids() == new_tree.tree_ids() && !was_empty
380 }
381 EmptyBehavior::AbandonAllEmpty => parent.tree_ids() == new_tree.tree_ids(),
382 };
383 if should_abandon {
384 self.abandon();
385 return Ok(None);
386 }
387 }
388
389 let builder = self
390 .mut_repo
391 .rewrite_commit(&self.old_commit)
392 .set_parents(self.new_parents)
393 .set_tree(new_tree);
394 Ok(Some(builder))
395 }
396
397 pub async fn rebase(self) -> BackendResult<CommitBuilder<'repo>> {
400 let builder = self.rebase_with_empty_behavior(EmptyBehavior::Keep).await?;
401 Ok(builder.unwrap())
402 }
403
404 pub fn reparent(self) -> CommitBuilder<'repo> {
407 self.mut_repo
408 .rewrite_commit(&self.old_commit)
409 .set_parents(self.new_parents)
410 }
411}
412
413#[derive(Debug)]
414pub enum RebasedCommit {
415 Rewritten(Commit),
416 Abandoned { parent_id: CommitId },
417}
418
419pub async fn rebase_commit_with_options(
420 mut rewriter: CommitRewriter<'_>,
421 options: &RebaseOptions,
422) -> BackendResult<RebasedCommit> {
423 if options.simplify_ancestor_merge {
425 rewriter
426 .simplify_ancestor_merge()
427 .map_err(|err| BackendError::Other(err.into()))?;
429 }
430
431 let single_parent = match &rewriter.new_parents[..] {
432 [parent_id] => Some(parent_id.clone()),
433 _ => None,
434 };
435 let new_parents_len = rewriter.new_parents.len();
436 if let Some(builder) = rewriter.rebase_with_empty_behavior(options.empty).await? {
437 let new_commit = builder.write().await?;
438 Ok(RebasedCommit::Rewritten(new_commit))
439 } else {
440 assert_eq!(new_parents_len, 1);
441 Ok(RebasedCommit::Abandoned {
442 parent_id: single_parent.unwrap(),
443 })
444 }
445}
446
447pub async fn rebase_to_dest_parent(
450 repo: &dyn Repo,
451 sources: &[Commit],
452 destination: &Commit,
453) -> BackendResult<MergedTree> {
454 if let [source] = sources
455 && source.parent_ids() == destination.parent_ids()
456 {
457 return Ok(source.tree());
458 }
459
460 let diffs: Vec<_> = try_join_all(sources.iter().map(async |source| -> BackendResult<_> {
461 Ok(Diff::new(
462 (
463 source.parent_tree(repo).await?,
464 format!(
465 "{} (original parents)",
466 source.parents_conflict_label().await?
467 ),
468 ),
469 (
470 source.tree(),
471 format!("{} (original revision)", source.conflict_label()),
472 ),
473 ))
474 }))
475 .await?;
476 MergedTree::merge(Merge::from_diffs(
477 (
478 destination.parent_tree(repo).await?,
479 format!(
480 "{} (new parents)",
481 destination.parents_conflict_label().await?
482 ),
483 ),
484 diffs,
485 ))
486 .await
487}
488
489#[derive(Clone, Copy, Default, PartialEq, Eq, Debug)]
490pub enum EmptyBehavior {
491 #[default]
493 Keep,
494 AbandonNewlyEmpty,
498 AbandonAllEmpty,
502}
503
504#[derive(Clone, Debug, Default)]
511pub struct RebaseOptions {
512 pub empty: EmptyBehavior,
513 pub rewrite_refs: RewriteRefsOptions,
514 pub simplify_ancestor_merge: bool,
517}
518
519#[derive(Clone, Debug, Default)]
521pub struct RewriteRefsOptions {
522 pub delete_abandoned_bookmarks: bool,
527}
528
529#[derive(Debug)]
530pub struct MoveCommitsStats {
531 pub num_rebased_targets: u32,
533 pub num_rebased_descendants: u32,
535 pub num_skipped_rebases: u32,
538 pub num_abandoned_empty: u32,
540 pub rebased_commits: HashMap<CommitId, RebasedCommit>,
542}
543
544#[derive(Clone, Debug)]
546pub struct MoveCommitsLocation {
547 pub new_parent_ids: Vec<CommitId>,
548 pub new_child_ids: Vec<CommitId>,
549 pub target: MoveCommitsTarget,
550}
551
552#[derive(Clone, Debug)]
553pub enum MoveCommitsTarget {
554 Commits(Vec<CommitId>),
557 Roots(Vec<CommitId>),
559}
560
561#[derive(Clone, Debug)]
562pub struct ComputedMoveCommits {
563 target_commit_ids: IndexSet<CommitId>,
564 descendants: Vec<Commit>,
565 commit_new_parents_map: HashMap<CommitId, Vec<CommitId>>,
566 to_abandon: HashSet<CommitId>,
567}
568
569impl ComputedMoveCommits {
570 fn empty() -> Self {
571 Self {
572 target_commit_ids: IndexSet::new(),
573 descendants: vec![],
574 commit_new_parents_map: HashMap::new(),
575 to_abandon: HashSet::new(),
576 }
577 }
578
579 pub fn record_to_abandon(&mut self, commit_ids: impl IntoIterator<Item = CommitId>) {
586 self.to_abandon.extend(commit_ids);
587 }
588
589 pub async fn apply(
590 self,
591 mut_repo: &mut MutableRepo,
592 options: &RebaseOptions,
593 ) -> BackendResult<MoveCommitsStats> {
594 apply_move_commits(mut_repo, self, options).await
595 }
596}
597
598pub async fn move_commits(
607 mut_repo: &mut MutableRepo,
608 loc: &MoveCommitsLocation,
609 options: &RebaseOptions,
610) -> BackendResult<MoveCommitsStats> {
611 compute_move_commits(mut_repo, loc)
612 .await?
613 .apply(mut_repo, options)
614 .await
615}
616
617pub async fn compute_move_commits(
618 repo: &MutableRepo,
619 loc: &MoveCommitsLocation,
620) -> BackendResult<ComputedMoveCommits> {
621 let target_commit_ids: IndexSet<CommitId>;
622 let connected_target_commits: Vec<Commit>;
623 let connected_target_commits_internal_parents: HashMap<CommitId, IndexSet<CommitId>>;
624 let target_roots: HashSet<CommitId>;
625
626 match &loc.target {
627 MoveCommitsTarget::Commits(commit_ids) => {
628 if commit_ids.is_empty() {
629 return Ok(ComputedMoveCommits::empty());
630 }
631
632 target_commit_ids = commit_ids.iter().cloned().collect();
633
634 connected_target_commits = RevsetExpression::commits(commit_ids.clone())
635 .connected()
636 .evaluate(repo)
637 .map_err(|err| err.into_backend_error())?
638 .stream()
639 .commits(repo.store())
640 .try_collect()
641 .await
642 .map_err(|err| err.into_backend_error())?;
643 connected_target_commits_internal_parents =
644 compute_internal_parents_within(&target_commit_ids, &connected_target_commits);
645
646 target_roots = connected_target_commits_internal_parents
647 .iter()
648 .filter(|&(commit_id, parents)| {
649 target_commit_ids.contains(commit_id) && parents.is_empty()
650 })
651 .map(|(commit_id, _)| commit_id.clone())
652 .collect();
653 }
654 MoveCommitsTarget::Roots(root_ids) => {
655 if root_ids.is_empty() {
656 return Ok(ComputedMoveCommits::empty());
657 }
658
659 target_commit_ids = RevsetExpression::commits(root_ids.clone())
660 .descendants()
661 .evaluate(repo)
662 .map_err(|err| err.into_backend_error())?
663 .stream()
664 .try_collect()
665 .await
666 .map_err(|err| err.into_backend_error())?;
667
668 connected_target_commits = try_join_all(
669 target_commit_ids
670 .iter()
671 .map(|id| repo.store().get_commit_async(id)),
672 )
673 .await?;
674 connected_target_commits_internal_parents = HashMap::new();
677 target_roots = root_ids.iter().cloned().collect();
678 }
679 }
680
681 let mut target_commits_external_parents: HashMap<CommitId, IndexSet<CommitId>> = HashMap::new();
685 for id in target_commit_ids.iter().rev() {
686 let commit = repo.store().get_commit_async(id).await?;
687 let mut new_parents = IndexSet::new();
688 for old_parent in commit.parent_ids() {
689 if let Some(parents) = target_commits_external_parents.get(old_parent) {
690 new_parents.extend(parents.iter().cloned());
691 } else {
692 new_parents.insert(old_parent.clone());
693 }
694 }
695 target_commits_external_parents.insert(commit.id().clone(), new_parents);
696 }
697
698 let new_parent_ids: Vec<_> = loc
702 .new_parent_ids
703 .iter()
704 .flat_map(|parent_id| {
705 if let Some(parent_ids) = target_commits_external_parents.get(parent_id) {
706 parent_ids.iter().cloned().collect_vec()
707 } else {
708 vec![parent_id.clone()]
709 }
710 })
711 .collect();
712
713 let new_children: Vec<_> = if loc
717 .new_child_ids
718 .iter()
719 .any(|id| target_commit_ids.contains(id))
720 {
721 let target_commits_descendants: Vec<_> =
722 RevsetExpression::commits(target_commit_ids.iter().cloned().collect_vec())
723 .union(
724 &RevsetExpression::commits(target_commit_ids.iter().cloned().collect_vec())
725 .children(),
726 )
727 .evaluate(repo)
728 .map_err(|err| err.into_backend_error())?
729 .stream()
730 .commits(repo.store())
731 .try_collect()
732 .await
733 .map_err(|err| err.into_backend_error())?;
734
735 let mut target_commit_external_descendants: HashMap<CommitId, IndexSet<Commit>> =
738 HashMap::new();
739 for commit in &target_commits_descendants {
742 if !target_commit_external_descendants.contains_key(commit.id()) {
743 let children = if target_commit_ids.contains(commit.id()) {
744 IndexSet::new()
745 } else {
746 IndexSet::from([commit.clone()])
747 };
748 target_commit_external_descendants.insert(commit.id().clone(), children);
749 }
750
751 let children = target_commit_external_descendants
752 .get(commit.id())
753 .unwrap()
754 .iter()
755 .cloned()
756 .collect_vec();
757 for parent_id in commit.parent_ids() {
758 if target_commit_ids.contains(parent_id) {
759 if let Some(target_children) =
760 target_commit_external_descendants.get_mut(parent_id)
761 {
762 target_children.extend(children.iter().cloned());
763 } else {
764 target_commit_external_descendants
765 .insert(parent_id.clone(), children.iter().cloned().collect());
766 }
767 }
768 }
769 }
770
771 let mut new_children = Vec::new();
772 for id in &loc.new_child_ids {
773 if let Some(children) = target_commit_external_descendants.get(id) {
774 new_children.extend(children.iter().cloned());
775 } else {
776 new_children.push(repo.store().get_commit_async(id).await?);
777 }
778 }
779 new_children
780 } else {
781 try_join_all(
782 loc.new_child_ids
783 .iter()
784 .map(|id| repo.store().get_commit_async(id)),
785 )
786 .await?
787 };
788
789 let new_children_parents: HashMap<_, _> = if !new_children.is_empty() {
792 let target_heads = compute_commits_heads(&target_commit_ids, &connected_target_commits);
795
796 new_children
797 .iter()
798 .map(|child_commit| {
799 let mut new_child_parent_ids = IndexSet::new();
800 for old_child_parent_id in child_commit.parent_ids() {
801 let old_child_parent_ids = if let Some(parents) =
803 target_commits_external_parents.get(old_child_parent_id)
804 {
805 parents.iter().collect_vec()
806 } else {
807 vec![old_child_parent_id]
808 };
809
810 for id in old_child_parent_ids {
814 if new_parent_ids.contains(id) {
815 new_child_parent_ids.extend(target_heads.clone());
816 } else {
817 new_child_parent_ids.insert(id.clone());
818 }
819 }
820 }
821
822 new_child_parent_ids.extend(target_heads.clone());
825
826 (
827 child_commit.id().clone(),
828 new_child_parent_ids.into_iter().collect_vec(),
829 )
830 })
831 .collect()
832 } else {
833 HashMap::new()
834 };
835
836 let mut roots = target_roots.iter().cloned().collect_vec();
839 roots.extend(new_children.iter().ids().cloned());
840
841 let descendants = repo
842 .find_descendants_for_rebase(roots.clone(), &RevsetExpression::none())
843 .await?;
844 let commit_new_parents_map = descendants
845 .iter()
846 .map(|commit| -> BackendResult<_> {
847 let commit_id = commit.id();
848 let new_parent_ids =
849 if let Some(new_child_parents) = new_children_parents.get(commit_id) {
850 new_child_parents.clone()
852 } else if target_commit_ids.contains(commit_id) {
853 if target_roots.contains(commit_id) {
855 new_parent_ids.clone()
858 } else {
859 let mut new_parents = vec![];
867 for parent_id in commit.parent_ids() {
868 if target_commit_ids.contains(parent_id) {
869 new_parents.push(parent_id.clone());
870 } else if let Some(parents) =
871 connected_target_commits_internal_parents.get(parent_id)
872 {
873 new_parents.extend(parents.iter().cloned());
874 } else if !fallible_any(&new_children, |child| {
875 repo.index().is_ancestor(child.id(), parent_id)
876 })
877 .map_err(|err| BackendError::Other(err.into()))?
879 {
880 new_parents.push(parent_id.clone());
881 }
882 }
883 new_parents
884 }
885 } else if commit
886 .parent_ids()
887 .iter()
888 .any(|id| target_commits_external_parents.contains_key(id))
889 {
890 let mut new_parents = vec![];
893 for parent in commit.parent_ids() {
894 if let Some(parents) = target_commits_external_parents.get(parent) {
895 new_parents.extend(parents.iter().cloned());
896 } else {
897 new_parents.push(parent.clone());
898 }
899 }
900 new_parents
901 } else {
902 commit.parent_ids().iter().cloned().collect_vec()
903 };
904 Ok((commit.id().clone(), new_parent_ids))
905 })
906 .try_collect()?;
907
908 Ok(ComputedMoveCommits {
909 target_commit_ids,
910 descendants,
911 commit_new_parents_map,
912 to_abandon: HashSet::new(),
913 })
914}
915
916async fn apply_move_commits(
917 mut_repo: &mut MutableRepo,
918 commits: ComputedMoveCommits,
919 options: &RebaseOptions,
920) -> BackendResult<MoveCommitsStats> {
921 let mut num_rebased_targets = 0;
922 let mut num_rebased_descendants = 0;
923 let mut num_skipped_rebases = 0;
924 let mut num_abandoned_empty = 0;
925
926 let rebase_descendant_options = &RebaseOptions {
929 empty: EmptyBehavior::Keep,
930 rewrite_refs: options.rewrite_refs.clone(),
931 simplify_ancestor_merge: false,
932 };
933
934 let mut rebased_commits: HashMap<CommitId, RebasedCommit> = HashMap::new();
935 mut_repo
936 .transform_commits(
937 commits.descendants,
938 &commits.commit_new_parents_map,
939 &options.rewrite_refs,
940 async |rewriter| {
941 let old_commit_id = rewriter.old_commit().id().clone();
942 if commits.to_abandon.contains(&old_commit_id) {
943 rewriter.abandon();
944 } else if rewriter.parents_changed() {
945 let is_target_commit = commits.target_commit_ids.contains(&old_commit_id);
946 let rebased_commit = rebase_commit_with_options(
947 rewriter,
948 if is_target_commit {
949 options
950 } else {
951 rebase_descendant_options
952 },
953 )
954 .await?;
955 if let RebasedCommit::Abandoned { .. } = rebased_commit {
956 num_abandoned_empty += 1;
957 } else if is_target_commit {
958 num_rebased_targets += 1;
959 } else {
960 num_rebased_descendants += 1;
961 }
962 rebased_commits.insert(old_commit_id, rebased_commit);
963 } else {
964 num_skipped_rebases += 1;
965 }
966
967 Ok(())
968 },
969 )
970 .await?;
971
972 Ok(MoveCommitsStats {
973 num_rebased_targets,
974 num_rebased_descendants,
975 num_skipped_rebases,
976 num_abandoned_empty,
977 rebased_commits,
978 })
979}
980
981#[derive(Debug, Default)]
982pub struct DuplicateCommitsStats {
983 pub duplicated_commits: IndexMap<CommitId, Commit>,
985 pub num_rebased: u32,
988}
989
990pub async fn duplicate_commits(
1008 mut_repo: &mut MutableRepo,
1009 target_commit_ids: &[CommitId],
1010 target_descriptions: &HashMap<CommitId, String>,
1011 parent_commit_ids: &[CommitId],
1012 children_commit_ids: &[CommitId],
1013) -> BackendResult<DuplicateCommitsStats> {
1014 if target_commit_ids.is_empty() {
1015 return Ok(DuplicateCommitsStats::default());
1016 }
1017
1018 let mut duplicated_old_to_new: IndexMap<CommitId, Commit> = IndexMap::new();
1019 let mut num_rebased = 0;
1020
1021 let target_commit_ids: IndexSet<_> = target_commit_ids.iter().cloned().collect();
1022
1023 let connected_target_commits: Vec<_> =
1024 RevsetExpression::commits(target_commit_ids.iter().cloned().collect_vec())
1025 .connected()
1026 .evaluate(mut_repo)
1027 .map_err(|err| err.into_backend_error())?
1028 .stream()
1029 .commits(mut_repo.store())
1030 .try_collect()
1031 .await
1032 .map_err(|err| err.into_backend_error())?;
1033
1034 let target_commits_internal_parents = {
1041 let mut target_commits_internal_parents =
1042 compute_internal_parents_within(&target_commit_ids, &connected_target_commits);
1043 target_commits_internal_parents.retain(|id, _| target_commit_ids.contains(id));
1044 target_commits_internal_parents
1045 };
1046
1047 let target_root_ids: HashSet<_> = target_commits_internal_parents
1049 .iter()
1050 .filter(|(_, parents)| parents.is_empty())
1051 .map(|(commit_id, _)| commit_id.clone())
1052 .collect();
1053
1054 let target_head_ids = if !children_commit_ids.is_empty() {
1057 compute_commits_heads(&target_commit_ids, &connected_target_commits)
1058 } else {
1059 vec![]
1060 };
1061
1062 for original_commit_id in target_commit_ids.iter().rev() {
1065 let original_commit = mut_repo
1066 .store()
1067 .get_commit_async(original_commit_id)
1068 .await?;
1069 let new_parent_ids = if target_root_ids.contains(original_commit_id) {
1070 parent_commit_ids.to_vec()
1071 } else {
1072 target_commits_internal_parents
1073 .get(original_commit_id)
1074 .unwrap()
1075 .iter()
1076 .map(|id| {
1078 duplicated_old_to_new
1079 .get(id)
1080 .map_or(id, |commit| commit.id())
1081 .clone()
1082 })
1083 .collect()
1084 };
1085 let mut new_commit_builder = CommitRewriter::new(mut_repo, original_commit, new_parent_ids)
1086 .rebase()
1087 .await?
1088 .clear_rewrite_source()
1089 .generate_new_change_id();
1090 if let Some(desc) = target_descriptions.get(original_commit_id) {
1091 new_commit_builder = new_commit_builder.set_description(desc);
1092 }
1093 duplicated_old_to_new.insert(
1094 original_commit_id.clone(),
1095 new_commit_builder.write().await?,
1096 );
1097 }
1098
1099 let target_head_ids = target_head_ids
1102 .into_iter()
1103 .map(|commit_id| {
1104 duplicated_old_to_new
1105 .get(&commit_id)
1106 .map_or(commit_id, |commit| commit.id().clone())
1107 })
1108 .collect_vec();
1109
1110 let children_commit_ids_set: HashSet<CommitId> = children_commit_ids.iter().cloned().collect();
1112 mut_repo
1113 .transform_descendants(children_commit_ids.to_vec(), async |mut rewriter| {
1114 if children_commit_ids_set.contains(rewriter.old_commit().id()) {
1115 let mut child_new_parent_ids = IndexSet::new();
1116 for old_parent_id in rewriter.old_commit().parent_ids() {
1117 if parent_commit_ids.contains(old_parent_id) {
1122 child_new_parent_ids.extend(target_head_ids.clone());
1123 } else {
1124 child_new_parent_ids.insert(old_parent_id.clone());
1125 }
1126 }
1127 child_new_parent_ids.extend(target_head_ids.clone());
1130 rewriter.set_new_parents(child_new_parent_ids.into_iter().collect());
1131 }
1132 num_rebased += 1;
1133 rewriter.rebase().await?.write().await?;
1134 Ok(())
1135 })
1136 .await?;
1137
1138 Ok(DuplicateCommitsStats {
1139 duplicated_commits: duplicated_old_to_new,
1140 num_rebased,
1141 })
1142}
1143
1144pub async fn duplicate_commits_onto_parents(
1154 mut_repo: &mut MutableRepo,
1155 target_commits: &[CommitId],
1156 target_descriptions: &HashMap<CommitId, String>,
1157) -> BackendResult<DuplicateCommitsStats> {
1158 if target_commits.is_empty() {
1159 return Ok(DuplicateCommitsStats::default());
1160 }
1161
1162 let mut duplicated_old_to_new: IndexMap<CommitId, Commit> = IndexMap::new();
1163
1164 for original_commit_id in target_commits.iter().rev() {
1167 let original_commit = mut_repo
1168 .store()
1169 .get_commit_async(original_commit_id)
1170 .await?;
1171 let new_parent_ids = original_commit
1172 .parent_ids()
1173 .iter()
1174 .map(|id| {
1175 duplicated_old_to_new
1176 .get(id)
1177 .map_or(id, |commit| commit.id())
1178 .clone()
1179 })
1180 .collect();
1181 let mut new_commit_builder = mut_repo
1182 .rewrite_commit(&original_commit)
1183 .clear_rewrite_source()
1184 .generate_new_change_id()
1185 .set_parents(new_parent_ids);
1186 if let Some(desc) = target_descriptions.get(original_commit_id) {
1187 new_commit_builder = new_commit_builder.set_description(desc);
1188 }
1189 duplicated_old_to_new.insert(
1190 original_commit_id.clone(),
1191 new_commit_builder.write().await?,
1192 );
1193 }
1194
1195 Ok(DuplicateCommitsStats {
1196 duplicated_commits: duplicated_old_to_new,
1197 num_rebased: 0,
1198 })
1199}
1200
1201fn compute_internal_parents_within(
1209 target_commit_ids: &IndexSet<CommitId>,
1210 graph_commits: &[Commit],
1211) -> HashMap<CommitId, IndexSet<CommitId>> {
1212 let mut internal_parents: HashMap<CommitId, IndexSet<CommitId>> = HashMap::new();
1213 for commit in graph_commits.iter().rev() {
1214 let mut new_parents = IndexSet::new();
1217 for old_parent in commit.parent_ids() {
1218 if target_commit_ids.contains(old_parent) {
1219 new_parents.insert(old_parent.clone());
1220 } else if let Some(parents) = internal_parents.get(old_parent) {
1221 new_parents.extend(parents.iter().cloned());
1222 }
1223 }
1224 internal_parents.insert(commit.id().clone(), new_parents);
1225 }
1226 internal_parents
1227}
1228
1229fn compute_commits_heads(
1235 target_commit_ids: &IndexSet<CommitId>,
1236 connected_target_commits: &[Commit],
1237) -> Vec<CommitId> {
1238 let mut target_head_ids: HashSet<CommitId> = HashSet::new();
1239 for commit in connected_target_commits.iter().rev() {
1240 target_head_ids.insert(commit.id().clone());
1241 for old_parent in commit.parent_ids() {
1242 target_head_ids.remove(old_parent);
1243 }
1244 }
1245 connected_target_commits
1246 .iter()
1247 .rev()
1248 .filter(|commit| {
1249 target_head_ids.contains(commit.id()) && target_commit_ids.contains(commit.id())
1250 })
1251 .map(|commit| commit.id().clone())
1252 .collect_vec()
1253}
1254
1255#[derive(Debug)]
1256pub struct CommitWithSelection {
1257 pub commit: Commit,
1258 pub selected_tree: MergedTree,
1259 pub parent_tree: MergedTree,
1260}
1261
1262impl CommitWithSelection {
1263 pub fn is_full_selection(&self) -> bool {
1265 self.selected_tree.tree_ids() == self.commit.tree_ids()
1266 }
1267
1268 pub fn is_empty_selection(&self) -> bool {
1274 self.selected_tree.tree_ids() == self.parent_tree.tree_ids()
1275 }
1276
1277 pub async fn diff_with_labels(
1281 &self,
1282 parent_tree_label: &str,
1283 selected_tree_label: &str,
1284 full_selection_label: &str,
1285 ) -> BackendResult<Diff<(MergedTree, String)>> {
1286 let parent_tree_label = format!(
1287 "{} ({parent_tree_label})",
1288 self.commit.parents_conflict_label().await?
1289 );
1290
1291 let commit_label = self.commit.conflict_label();
1292 let selected_tree_label = if self.is_full_selection() {
1293 format!("{commit_label} ({full_selection_label})")
1294 } else {
1295 format!("{selected_tree_label} (from {commit_label})")
1296 };
1297
1298 Ok(Diff::new(
1299 (self.parent_tree.clone(), parent_tree_label),
1300 (self.selected_tree.clone(), selected_tree_label),
1301 ))
1302 }
1303}
1304
1305#[must_use]
1307pub struct SquashedCommit<'repo> {
1308 pub commit_builder: CommitBuilder<'repo>,
1310 pub abandoned_commits: Vec<Commit>,
1312}
1313
1314pub async fn squash_commits<'repo>(
1318 repo: &'repo mut MutableRepo,
1319 sources: &[CommitWithSelection],
1320 destination: &Commit,
1321 keep_emptied: bool,
1322) -> BackendResult<Option<SquashedCommit<'repo>>> {
1323 struct SourceCommit<'a> {
1324 commit: &'a CommitWithSelection,
1325 diff: Diff<(MergedTree, String)>,
1326 abandon: bool,
1327 }
1328 let mut source_commits = vec![];
1329 for source in sources {
1330 let abandon = !keep_emptied && source.is_full_selection();
1331 if !abandon && source.is_empty_selection() {
1332 continue;
1336 }
1337
1338 source_commits.push(SourceCommit {
1341 commit: source,
1342 diff: source
1343 .diff_with_labels(
1344 "parents of squashed revision",
1345 "selected changes for squash",
1346 "squashed revision",
1347 )
1348 .await?,
1349 abandon,
1350 });
1351 }
1352
1353 if source_commits.is_empty() {
1354 return Ok(None);
1355 }
1356
1357 let mut abandoned_commits = vec![];
1358 for source in &source_commits {
1359 if source.abandon {
1360 repo.record_abandoned_commit(&source.commit.commit);
1361 abandoned_commits.push(source.commit.commit.clone());
1362 } else {
1363 let source_tree = source.commit.commit.tree();
1364 let new_source_tree = MergedTree::merge(Merge::from_diffs(
1366 (source_tree, source.commit.commit.conflict_label()),
1367 [source.diff.clone().invert()],
1368 ))
1369 .await?;
1370 repo.rewrite_commit(&source.commit.commit)
1371 .set_tree(new_source_tree)
1372 .write()
1373 .await?;
1374 }
1375 }
1376
1377 let mut rewritten_destination = destination.clone();
1378 if fallible_any(sources, |source| {
1379 repo.index()
1380 .is_ancestor(source.commit.id(), destination.id())
1381 })
1382 .map_err(|err| BackendError::Other(err.into()))?
1384 {
1385 let immutable = RevsetExpression::none();
1390 let options = RebaseOptions::default();
1391 repo.rebase_descendants_with_options(&immutable, &options, |old_commit, rebased_commit| {
1392 if old_commit.id() != destination.id() {
1393 return;
1394 }
1395 rewritten_destination = match rebased_commit {
1396 RebasedCommit::Rewritten(commit) => commit,
1397 RebasedCommit::Abandoned { .. } => panic!("all commits should be kept"),
1398 };
1399 })
1400 .await?;
1401 }
1402 let mut predecessors = vec![destination.id().clone()];
1403 predecessors.extend(
1404 source_commits
1405 .iter()
1406 .map(|source| source.commit.commit.id().clone()),
1407 );
1408 let destination_tree = MergedTree::merge(Merge::from_diffs(
1410 (
1411 rewritten_destination.tree(),
1412 format!("{} (squash destination)", destination.conflict_label()),
1413 ),
1414 source_commits.into_iter().map(|source| source.diff),
1415 ))
1416 .await?;
1417
1418 let commit_builder = repo
1419 .rewrite_commit(&rewritten_destination)
1420 .set_tree(destination_tree)
1421 .set_predecessors(predecessors);
1422 Ok(Some(SquashedCommit {
1423 commit_builder,
1424 abandoned_commits,
1425 }))
1426}
1427
1428pub async fn find_duplicate_divergent_commits(
1432 repo: &dyn Repo,
1433 new_parent_ids: &[CommitId],
1434 target: &MoveCommitsTarget,
1435) -> BackendResult<Vec<Commit>> {
1436 let target_commits: Vec<Commit> = match target {
1437 MoveCommitsTarget::Commits(commit_ids) => {
1438 try_join_all(
1439 commit_ids
1440 .iter()
1441 .map(|commit_id| repo.store().get_commit_async(commit_id)),
1442 )
1443 .await?
1444 }
1445 MoveCommitsTarget::Roots(root_ids) => RevsetExpression::commits(root_ids.clone())
1446 .descendants()
1447 .evaluate(repo)
1448 .map_err(|err| err.into_backend_error())?
1449 .stream()
1450 .commits(repo.store())
1451 .try_collect()
1452 .await
1453 .map_err(|err| err.into_backend_error())?,
1454 };
1455 let target_commit_ids: HashSet<&CommitId> = target_commits.iter().map(Commit::id).collect();
1456
1457 let divergent_changes: Vec<(&Commit, Vec<CommitId>)> = target_commits
1460 .iter()
1461 .map(|target_commit| -> Result<_, BackendError> {
1462 let mut ancestor_candidates = repo
1463 .resolve_change_id(target_commit.change_id())
1464 .map_err(|err| BackendError::Other(err.into()))?
1466 .and_then(ResolvedChangeTargets::into_visible)
1467 .unwrap_or_default();
1468 ancestor_candidates.retain(|commit_id| !target_commit_ids.contains(commit_id));
1469 Ok((target_commit, ancestor_candidates))
1470 })
1471 .filter_ok(|(_, candidates)| !candidates.is_empty())
1472 .try_collect()?;
1473 if divergent_changes.is_empty() {
1474 return Ok(Vec::new());
1475 }
1476
1477 let target_root_ids = match target {
1478 MoveCommitsTarget::Commits(commit_ids) => commit_ids,
1479 MoveCommitsTarget::Roots(root_ids) => root_ids,
1480 };
1481
1482 let is_new_ancestor = RevsetExpression::commits(target_root_ids.clone())
1485 .range(&RevsetExpression::commits(new_parent_ids.to_owned()))
1486 .evaluate(repo)
1487 .map_err(|err| err.into_backend_error())?
1488 .containing_fn();
1489
1490 let mut duplicate_divergent = Vec::new();
1491 for (target_commit, ancestor_candidates) in divergent_changes {
1496 for ancestor_candidate_id in ancestor_candidates {
1497 if !is_new_ancestor(&ancestor_candidate_id).map_err(|err| err.into_backend_error())? {
1498 continue;
1499 }
1500
1501 let ancestor_candidate = repo
1502 .store()
1503 .get_commit_async(&ancestor_candidate_id)
1504 .await?;
1505 let new_tree =
1506 rebase_to_dest_parent(repo, slice::from_ref(target_commit), &ancestor_candidate)
1507 .await?;
1508 if new_tree.tree_ids() == ancestor_candidate.tree_ids() {
1511 duplicate_divergent.push(target_commit.clone());
1512 break;
1513 }
1514 }
1515 }
1516 Ok(duplicate_divergent)
1517}