1#![expect(missing_docs)]
16
17use std::collections::HashMap;
18use std::collections::HashSet;
19use std::mem;
20use std::slice;
21use std::sync::Arc;
22
23use futures::StreamExt as _;
24use futures::TryStreamExt as _;
25use futures::future::ready;
26use futures::future::try_join_all;
27use futures::try_join;
28use indexmap::IndexMap;
29use indexmap::IndexSet;
30use itertools::Itertools as _;
31use tracing::instrument;
32
33use crate::backend::BackendError;
34use crate::backend::BackendResult;
35use crate::backend::CommitId;
36use crate::commit::Commit;
37use crate::commit::CommitIteratorExt as _;
38use crate::commit::conflict_label_for_commits;
39use crate::commit_builder::CommitBuilder;
40use crate::conflict_labels::ConflictLabels;
41use crate::index::Index;
42use crate::index::IndexResult;
43use crate::index::ResolvedChangeTargets;
44use crate::iter_util::fallible_any;
45use crate::matchers::FilesMatcher;
46use crate::matchers::Matcher;
47use crate::matchers::Visit;
48use crate::merge::Diff;
49use crate::merge::Merge;
50use crate::merged_tree::MergedTree;
51use crate::merged_tree_builder::MergedTreeBuilder;
52use crate::repo::MutableRepo;
53use crate::repo::Repo;
54use crate::repo_path::RepoPath;
55use crate::revset::RevsetExpression;
56use crate::revset::RevsetStreamExt as _;
57use crate::store::Store;
58
59#[instrument(skip(repo))]
61pub async fn merge_commit_trees(repo: &dyn Repo, commits: &[Commit]) -> BackendResult<MergedTree> {
62 if let [commit] = commits {
63 Ok(commit.tree())
64 } else {
65 merge_commit_trees_no_resolve(repo, commits)
66 .await?
67 .resolve()
68 .await
69 }
70}
71
72pub async fn merge_commit_trees_no_resolve(
74 repo: &dyn Repo,
75 commits: &[Commit],
76) -> BackendResult<MergedTree> {
77 if let [commit] = commits {
78 Ok(commit.tree())
79 } else {
80 merge_commit_trees_no_resolve_without_repo(repo.store(), repo.index(), commits).await
81 }
82}
83
84#[instrument(skip(index))]
86pub async fn merge_commit_trees_no_resolve_without_repo(
87 store: &Arc<Store>,
88 index: &dyn Index,
89 commits: &[Commit],
90) -> BackendResult<MergedTree> {
91 let commit_ids = commits
92 .iter()
93 .map(|commit| commit.id().clone())
94 .collect_vec();
95 let commit_id_merge = find_recursive_merge_commits(store, index, commit_ids).await?;
96 let tree_merge: Merge<(MergedTree, String)> = commit_id_merge
97 .try_map_async(async |commit_id| {
98 let commit = store.get_commit_async(commit_id).await?;
99 Ok::<_, BackendError>((commit.tree(), commit.conflict_label()))
100 })
101 .await?;
102 Ok(MergedTree::merge_no_resolve(tree_merge))
103}
104
105pub async fn find_recursive_merge_commits(
107 store: &Arc<Store>,
108 index: &dyn Index,
109 commit_ids: Vec<CommitId>,
110) -> BackendResult<Merge<CommitId>> {
111 #[derive(Debug)]
112 struct WorkItem {
113 commit_ids: Vec<CommitId>,
114 result: Merge<CommitId>,
115 pos: usize,
116 }
117
118 impl WorkItem {
119 fn new(commit_ids: Vec<CommitId>) -> Self {
120 let result = Merge::resolved(commit_ids[0].clone());
121 Self {
122 commit_ids,
123 result,
124 pos: 1,
125 }
126 }
127
128 fn merge_next(&mut self, ancestor: Merge<CommitId>) {
129 let dummy = Merge::resolved(CommitId::new(vec![]));
130 let result = mem::replace(&mut self.result, dummy);
131 let other = Merge::resolved(self.commit_ids[self.pos].clone());
132 self.result = Merge::from_vec(vec![result, ancestor, other]).flatten();
133 self.pos += 1;
134 }
135 }
136
137 let maybe_resolved = |commit_ids: Vec<CommitId>| match commit_ids.len() {
138 0 => Ok(Merge::resolved(store.root_commit_id().clone())),
139 1 => Ok(Merge::resolved(commit_ids.into_iter().next().unwrap())),
140 _ => Err(commit_ids),
141 };
142
143 let mut stack = Vec::new();
154 match maybe_resolved(commit_ids) {
155 Ok(result) => return Ok(result),
156 Err(commit_ids) => stack.push(WorkItem::new(commit_ids)),
157 }
158 loop {
159 let top = stack.last_mut().unwrap();
160 if top.pos < top.commit_ids.len() {
161 let ancestor_ids = index
162 .common_ancestors(&top.commit_ids[0..top.pos], &top.commit_ids[top.pos..][..1])
163 .await
164 .map_err(|err| BackendError::Other(err.into()))?;
166 match maybe_resolved(ancestor_ids) {
167 Ok(ancestor) => top.merge_next(ancestor),
168 Err(ancestor_ids) => stack.push(WorkItem::new(ancestor_ids)),
169 }
170 } else {
171 let ancestor = stack.pop().unwrap();
172 let Some(top) = stack.last_mut() else {
173 return Ok(ancestor.result);
174 };
175 top.merge_next(ancestor.result);
176 }
177 }
178}
179
180pub async fn restore_tree(
182 source: &MergedTree,
183 destination: &MergedTree,
184 source_label: String,
185 destination_label: String,
186 matcher: &dyn Matcher,
187) -> BackendResult<MergedTree> {
188 if matcher.visit(RepoPath::root()) == Visit::AllRecursively {
189 return Ok(source.clone());
191 }
192 let mut diff_stream = source.diff_stream(destination, matcher);
193 let mut paths = Vec::new();
194 while let Some(entry) = diff_stream.next().await {
195 paths.push(entry.path);
198 }
199 let matcher = FilesMatcher::new(paths);
200
201 let select_matching =
202 async |tree: &MergedTree, labels: ConflictLabels| -> BackendResult<MergedTree> {
203 let empty_tree_ids = Merge::repeated(
204 tree.store().empty_tree_id().clone(),
205 tree.tree_ids().num_sides(),
206 );
207 let labeled_empty_tree = MergedTree::new(tree.store().clone(), empty_tree_ids, labels);
208 let mut builder = MergedTreeBuilder::new(labeled_empty_tree);
209 for (path, value) in tree.entries_matching(&matcher) {
210 builder.set_or_remove(path, value?);
213 }
214 builder.write_tree().await
215 };
216
217 const RESTORE_BASE_LABEL: &str = "base files for restore";
218
219 let base_labels = ConflictLabels::from_merge(destination.labels().as_merge().map(|label| {
222 if label.is_empty() || label.starts_with(RESTORE_BASE_LABEL) {
223 label.clone()
224 } else {
225 format!("{RESTORE_BASE_LABEL} (from {label})")
226 }
227 }));
228
229 MergedTree::merge(Merge::from_vec(vec![
241 (
242 destination.clone(),
243 format!("{destination_label} (restore destination)"),
244 ),
245 (
246 select_matching(destination, base_labels).await?,
247 format!("{RESTORE_BASE_LABEL} (from {destination_label})"),
248 ),
249 (
250 select_matching(source, source.labels().clone()).await?,
251 format!("restored files (from {source_label})"),
252 ),
253 ]))
254 .await
255}
256
257pub async fn rebase_commit(
258 mut_repo: &mut MutableRepo,
259 old_commit: Commit,
260 new_parents: Vec<CommitId>,
261) -> BackendResult<Commit> {
262 let rewriter = CommitRewriter::new(mut_repo, old_commit, new_parents);
263 let builder = rewriter.rebase().await?;
264 builder.write().await
265}
266
267pub struct CommitRewriter<'repo> {
269 mut_repo: &'repo mut MutableRepo,
270 old_commit: Commit,
271 new_parents: Vec<CommitId>,
272}
273
274impl<'repo> CommitRewriter<'repo> {
275 pub fn new(
277 mut_repo: &'repo mut MutableRepo,
278 old_commit: Commit,
279 new_parents: Vec<CommitId>,
280 ) -> Self {
281 Self {
282 mut_repo,
283 old_commit,
284 new_parents,
285 }
286 }
287
288 pub fn repo_mut(&mut self) -> &mut MutableRepo {
290 self.mut_repo
291 }
292
293 pub fn old_commit(&self) -> &Commit {
295 &self.old_commit
296 }
297
298 pub fn new_parents(&self) -> &[CommitId] {
300 &self.new_parents
301 }
302
303 pub fn set_new_parents(&mut self, new_parents: Vec<CommitId>) {
305 self.new_parents = new_parents;
306 }
307
308 pub fn set_new_rewritten_parents(&mut self, unrewritten_parents: &[CommitId]) {
311 self.new_parents = self.mut_repo.new_parents(unrewritten_parents);
312 }
313
314 pub fn replace_parent<'a>(
317 &mut self,
318 old_parent: &CommitId,
319 new_parents: impl IntoIterator<Item = &'a CommitId>,
320 ) {
321 if let Some(i) = self.new_parents.iter().position(|p| p == old_parent) {
322 self.new_parents
323 .splice(i..i + 1, new_parents.into_iter().cloned());
324 let mut unique = HashSet::new();
325 self.new_parents.retain(|p| unique.insert(p.clone()));
326 }
327 }
328
329 pub fn parents_changed(&self) -> bool {
332 self.new_parents != self.old_commit.parent_ids()
333 }
334
335 pub async fn simplify_ancestor_merge(&mut self) -> IndexResult<()> {
338 let head_set: HashSet<_> = self
339 .mut_repo
340 .index()
341 .heads(&mut self.new_parents.iter())
342 .await?
343 .into_iter()
344 .collect();
345 self.new_parents.retain(|parent| head_set.contains(parent));
346 Ok(())
347 }
348
349 pub fn abandon(self) {
353 let old_commit_id = self.old_commit.id().clone();
354 let new_parents = self.new_parents;
355 self.mut_repo
356 .record_abandoned_commit_with_parents(old_commit_id, new_parents);
357 }
358
359 pub async fn rebase_with_empty_behavior(
362 self,
363 empty: EmptyBehavior,
364 ) -> BackendResult<Option<CommitBuilder<'repo>>> {
365 let old_parents_fut = self.old_commit.parents();
366 let new_parents_fut = try_join_all(
367 self.new_parents
368 .iter()
369 .map(|new_parent_id| self.mut_repo.store().get_commit_async(new_parent_id)),
370 );
371 let (old_parents, new_parents) = try_join!(old_parents_fut, new_parents_fut)?;
372 let old_parent_trees = old_parents
373 .iter()
374 .map(|parent| parent.tree_ids().clone())
375 .collect_vec();
376 let new_parent_trees = new_parents
377 .iter()
378 .map(|parent| parent.tree_ids().clone())
379 .collect_vec();
380
381 let (was_empty, new_tree) = if new_parent_trees == old_parent_trees {
382 (
383 true,
386 self.old_commit.tree(),
388 )
389 } else {
390 let old_base_tree_fut = merge_commit_trees(self.mut_repo, &old_parents);
394 let new_base_tree_fut = merge_commit_trees(self.mut_repo, &new_parents);
395 let old_tree = self.old_commit.tree();
396 let (old_base_tree, new_base_tree) = try_join!(old_base_tree_fut, new_base_tree_fut)?;
397 (
398 old_base_tree.tree_ids() == self.old_commit.tree_ids(),
399 MergedTree::merge(Merge::from_vec(vec![
400 (
401 new_base_tree,
402 format!(
403 "{} (rebase destination)",
404 conflict_label_for_commits(&new_parents)
405 ),
406 ),
407 (
408 old_base_tree,
409 format!(
410 "{} (parents of rebased revision)",
411 conflict_label_for_commits(&old_parents)
412 ),
413 ),
414 (
415 old_tree,
416 format!("{} (rebased revision)", self.old_commit.conflict_label()),
417 ),
418 ]))
419 .await?,
420 )
421 };
422 if let [parent] = &new_parents[..] {
425 let should_abandon = match empty {
426 EmptyBehavior::Keep => false,
427 EmptyBehavior::AbandonNewlyEmpty => {
428 parent.tree_ids() == new_tree.tree_ids() && !was_empty
429 }
430 EmptyBehavior::AbandonAllEmpty => parent.tree_ids() == new_tree.tree_ids(),
431 };
432 if should_abandon {
433 self.abandon();
434 return Ok(None);
435 }
436 }
437
438 let builder = self
439 .mut_repo
440 .rewrite_commit(&self.old_commit)
441 .set_parents(self.new_parents)
442 .set_tree(new_tree);
443 Ok(Some(builder))
444 }
445
446 pub async fn rebase(self) -> BackendResult<CommitBuilder<'repo>> {
449 let builder = self.rebase_with_empty_behavior(EmptyBehavior::Keep).await?;
450 Ok(builder.unwrap())
451 }
452
453 pub fn reparent(self) -> CommitBuilder<'repo> {
456 self.mut_repo
457 .rewrite_commit(&self.old_commit)
458 .set_parents(self.new_parents)
459 }
460}
461
462#[derive(Debug)]
463pub enum RebasedCommit {
464 Rewritten(Commit),
465 Abandoned { parent_id: CommitId },
466}
467
468pub async fn rebase_commit_with_options(
469 mut rewriter: CommitRewriter<'_>,
470 options: &RebaseOptions,
471) -> BackendResult<RebasedCommit> {
472 if options.simplify_ancestor_merge {
474 rewriter
475 .simplify_ancestor_merge()
476 .await
477 .map_err(|err| BackendError::Other(err.into()))?;
479 }
480
481 let single_parent = match &rewriter.new_parents[..] {
482 [parent_id] => Some(parent_id.clone()),
483 _ => None,
484 };
485 let new_parents_len = rewriter.new_parents.len();
486 if let Some(builder) = rewriter.rebase_with_empty_behavior(options.empty).await? {
487 let new_commit = builder.write().await?;
488 Ok(RebasedCommit::Rewritten(new_commit))
489 } else {
490 assert_eq!(new_parents_len, 1);
491 Ok(RebasedCommit::Abandoned {
492 parent_id: single_parent.unwrap(),
493 })
494 }
495}
496
497pub async fn rebase_to_dest_parent(
500 repo: &dyn Repo,
501 sources: &[Commit],
502 destination: &Commit,
503) -> BackendResult<MergedTree> {
504 if let [source] = sources
505 && source.parent_ids() == destination.parent_ids()
506 {
507 return Ok(source.tree());
508 }
509
510 let diffs: Vec<_> = try_join_all(sources.iter().map(async |source| -> BackendResult<_> {
511 Ok(Diff::new(
512 (
513 source.parent_tree(repo).await?,
514 format!(
515 "{} (original parents)",
516 source.parents_conflict_label().await?
517 ),
518 ),
519 (
520 source.tree(),
521 format!("{} (original revision)", source.conflict_label()),
522 ),
523 ))
524 }))
525 .await?;
526 MergedTree::merge(Merge::from_diffs(
527 (
528 destination.parent_tree(repo).await?,
529 format!(
530 "{} (new parents)",
531 destination.parents_conflict_label().await?
532 ),
533 ),
534 diffs,
535 ))
536 .await
537}
538
539#[derive(Clone, Copy, Default, PartialEq, Eq, Debug)]
540pub enum EmptyBehavior {
541 #[default]
543 Keep,
544 AbandonNewlyEmpty,
548 AbandonAllEmpty,
552}
553
554#[derive(Clone, Debug, Default)]
561pub struct RebaseOptions {
562 pub empty: EmptyBehavior,
563 pub rewrite_refs: RewriteRefsOptions,
564 pub simplify_ancestor_merge: bool,
567}
568
569#[derive(Clone, Debug, Default)]
571pub struct RewriteRefsOptions {
572 pub delete_abandoned_bookmarks: bool,
577}
578
579#[derive(Debug)]
580pub struct MoveCommitsStats {
581 pub num_rebased_targets: u32,
583 pub num_rebased_descendants: u32,
585 pub num_skipped_rebases: u32,
588 pub num_abandoned_empty: u32,
590 pub rebased_commits: HashMap<CommitId, RebasedCommit>,
592}
593
594#[derive(Clone, Debug)]
596pub struct MoveCommitsLocation {
597 pub new_parent_ids: Vec<CommitId>,
598 pub new_child_ids: Vec<CommitId>,
599 pub target: MoveCommitsTarget,
600}
601
602#[derive(Clone, Debug)]
603pub enum MoveCommitsTarget {
604 Commits(Vec<CommitId>),
607 Roots(Vec<CommitId>),
609}
610
611#[derive(Clone, Debug)]
612pub struct ComputedMoveCommits {
613 target_commit_ids: IndexSet<CommitId>,
614 descendants: Vec<Commit>,
615 commit_new_parents_map: HashMap<CommitId, Vec<CommitId>>,
616 to_abandon: HashSet<CommitId>,
617}
618
619impl ComputedMoveCommits {
620 fn empty() -> Self {
621 Self {
622 target_commit_ids: IndexSet::new(),
623 descendants: vec![],
624 commit_new_parents_map: HashMap::new(),
625 to_abandon: HashSet::new(),
626 }
627 }
628
629 pub fn record_to_abandon(&mut self, commit_ids: impl IntoIterator<Item = CommitId>) {
636 self.to_abandon.extend(commit_ids);
637 }
638
639 pub async fn apply(
640 self,
641 mut_repo: &mut MutableRepo,
642 options: &RebaseOptions,
643 ) -> BackendResult<MoveCommitsStats> {
644 apply_move_commits(mut_repo, self, options).await
645 }
646}
647
648pub async fn move_commits(
657 mut_repo: &mut MutableRepo,
658 loc: &MoveCommitsLocation,
659 options: &RebaseOptions,
660) -> BackendResult<MoveCommitsStats> {
661 compute_move_commits(mut_repo, loc)
662 .await?
663 .apply(mut_repo, options)
664 .await
665}
666
667pub async fn compute_move_commits(
668 repo: &MutableRepo,
669 loc: &MoveCommitsLocation,
670) -> BackendResult<ComputedMoveCommits> {
671 let target_commit_ids: IndexSet<CommitId>;
672 let connected_target_commits: Vec<Commit>;
673 let connected_target_commits_internal_parents: HashMap<CommitId, IndexSet<CommitId>>;
674 let target_roots: HashSet<CommitId>;
675
676 match &loc.target {
677 MoveCommitsTarget::Commits(commit_ids) => {
678 if commit_ids.is_empty() {
679 return Ok(ComputedMoveCommits::empty());
680 }
681
682 target_commit_ids = commit_ids.iter().cloned().collect();
683
684 connected_target_commits = RevsetExpression::commits(commit_ids.clone())
685 .connected()
686 .evaluate(repo)
687 .map_err(|err| err.into_backend_error())?
688 .stream()
689 .commits(repo.store())
690 .try_collect()
691 .await
692 .map_err(|err| err.into_backend_error())?;
693 connected_target_commits_internal_parents =
694 compute_internal_parents_within(&target_commit_ids, &connected_target_commits);
695
696 target_roots = connected_target_commits_internal_parents
697 .iter()
698 .filter(|&(commit_id, parents)| {
699 target_commit_ids.contains(commit_id) && parents.is_empty()
700 })
701 .map(|(commit_id, _)| commit_id.clone())
702 .collect();
703 }
704 MoveCommitsTarget::Roots(root_ids) => {
705 if root_ids.is_empty() {
706 return Ok(ComputedMoveCommits::empty());
707 }
708
709 target_commit_ids = RevsetExpression::commits(root_ids.clone())
710 .descendants()
711 .evaluate(repo)
712 .map_err(|err| err.into_backend_error())?
713 .stream()
714 .try_collect()
715 .await
716 .map_err(|err| err.into_backend_error())?;
717
718 connected_target_commits = try_join_all(
719 target_commit_ids
720 .iter()
721 .map(|id| repo.store().get_commit_async(id)),
722 )
723 .await?;
724 connected_target_commits_internal_parents = HashMap::new();
727 target_roots = root_ids.iter().cloned().collect();
728 }
729 }
730
731 let mut target_commits_external_parents: HashMap<CommitId, IndexSet<CommitId>> = HashMap::new();
735 for id in target_commit_ids.iter().rev() {
736 let commit = repo.store().get_commit_async(id).await?;
737 let mut new_parents = IndexSet::new();
738 for old_parent in commit.parent_ids() {
739 if let Some(parents) = target_commits_external_parents.get(old_parent) {
740 new_parents.extend(parents.iter().cloned());
741 } else {
742 new_parents.insert(old_parent.clone());
743 }
744 }
745 target_commits_external_parents.insert(commit.id().clone(), new_parents);
746 }
747
748 let new_parent_ids: Vec<_> = loc
752 .new_parent_ids
753 .iter()
754 .flat_map(|parent_id| {
755 if let Some(parent_ids) = target_commits_external_parents.get(parent_id) {
756 parent_ids.iter().cloned().collect_vec()
757 } else {
758 vec![parent_id.clone()]
759 }
760 })
761 .collect();
762
763 let new_children: Vec<_> = if loc
767 .new_child_ids
768 .iter()
769 .any(|id| target_commit_ids.contains(id))
770 {
771 let target_commits_descendants: Vec<_> =
772 RevsetExpression::commits(target_commit_ids.iter().cloned().collect_vec())
773 .union(
774 &RevsetExpression::commits(target_commit_ids.iter().cloned().collect_vec())
775 .children(),
776 )
777 .evaluate(repo)
778 .map_err(|err| err.into_backend_error())?
779 .stream()
780 .commits(repo.store())
781 .try_collect()
782 .await
783 .map_err(|err| err.into_backend_error())?;
784
785 let mut target_commit_external_descendants: HashMap<CommitId, IndexSet<Commit>> =
788 HashMap::new();
789 for commit in &target_commits_descendants {
792 if !target_commit_external_descendants.contains_key(commit.id()) {
793 let children = if target_commit_ids.contains(commit.id()) {
794 IndexSet::new()
795 } else {
796 IndexSet::from([commit.clone()])
797 };
798 target_commit_external_descendants.insert(commit.id().clone(), children);
799 }
800
801 let children = target_commit_external_descendants
802 .get(commit.id())
803 .unwrap()
804 .iter()
805 .cloned()
806 .collect_vec();
807 for parent_id in commit.parent_ids() {
808 if target_commit_ids.contains(parent_id) {
809 if let Some(target_children) =
810 target_commit_external_descendants.get_mut(parent_id)
811 {
812 target_children.extend(children.iter().cloned());
813 } else {
814 target_commit_external_descendants
815 .insert(parent_id.clone(), children.iter().cloned().collect());
816 }
817 }
818 }
819 }
820
821 let mut new_children = Vec::new();
822 for id in &loc.new_child_ids {
823 if let Some(children) = target_commit_external_descendants.get(id) {
824 new_children.extend(children.iter().cloned());
825 } else {
826 new_children.push(repo.store().get_commit_async(id).await?);
827 }
828 }
829 new_children
830 } else {
831 try_join_all(
832 loc.new_child_ids
833 .iter()
834 .map(|id| repo.store().get_commit_async(id)),
835 )
836 .await?
837 };
838
839 let new_children_parents: HashMap<_, _> = if !new_children.is_empty() {
842 let target_heads = compute_commits_heads(&target_commit_ids, &connected_target_commits);
845
846 new_children
847 .iter()
848 .map(|child_commit| {
849 let mut new_child_parent_ids = IndexSet::new();
850 for old_child_parent_id in child_commit.parent_ids() {
851 let old_child_parent_ids = if let Some(parents) =
853 target_commits_external_parents.get(old_child_parent_id)
854 {
855 parents.iter().collect_vec()
856 } else {
857 vec![old_child_parent_id]
858 };
859
860 for id in old_child_parent_ids {
864 if new_parent_ids.contains(id) {
865 new_child_parent_ids.extend(target_heads.clone());
866 } else {
867 new_child_parent_ids.insert(id.clone());
868 }
869 }
870 }
871
872 new_child_parent_ids.extend(target_heads.clone());
875
876 (
877 child_commit.id().clone(),
878 new_child_parent_ids.into_iter().collect_vec(),
879 )
880 })
881 .collect()
882 } else {
883 HashMap::new()
884 };
885
886 let mut roots = target_roots.iter().cloned().collect_vec();
889 roots.extend(new_children.iter().ids().cloned());
890
891 let descendants = repo
892 .find_descendants_for_rebase(roots.clone(), &RevsetExpression::none())
893 .await?;
894 let commit_new_parents_entries =
895 try_join_all(descendants.iter().map(async |commit| -> BackendResult<_> {
896 let commit_id = commit.id();
897 let new_parent_ids =
898 if let Some(new_child_parents) = new_children_parents.get(commit_id) {
899 new_child_parents.clone()
901 } else if target_commit_ids.contains(commit_id) {
902 if target_roots.contains(commit_id) {
904 new_parent_ids.clone()
907 } else {
908 let mut new_parents = vec![];
916 for parent_id in commit.parent_ids() {
917 if target_commit_ids.contains(parent_id) {
918 new_parents.push(parent_id.clone());
919 } else if let Some(parents) =
920 connected_target_commits_internal_parents.get(parent_id)
921 {
922 new_parents.extend(parents.iter().cloned());
923 } else if !fallible_any(&new_children, async |child| {
924 repo.index().is_ancestor(child.id(), parent_id).await
925 })
926 .await
927 .map_err(|err| BackendError::Other(err.into()))?
929 {
930 new_parents.push(parent_id.clone());
931 }
932 }
933 new_parents
934 }
935 } else if commit
936 .parent_ids()
937 .iter()
938 .any(|id| target_commits_external_parents.contains_key(id))
939 {
940 let mut new_parents = vec![];
943 for parent in commit.parent_ids() {
944 if let Some(parents) = target_commits_external_parents.get(parent) {
945 new_parents.extend(parents.iter().cloned());
946 } else {
947 new_parents.push(parent.clone());
948 }
949 }
950 new_parents
951 } else {
952 commit.parent_ids().iter().cloned().collect_vec()
953 };
954 Ok((commit.id().clone(), new_parent_ids))
955 }))
956 .await?;
957 let commit_new_parents_map = commit_new_parents_entries.into_iter().collect();
958
959 Ok(ComputedMoveCommits {
960 target_commit_ids,
961 descendants,
962 commit_new_parents_map,
963 to_abandon: HashSet::new(),
964 })
965}
966
967async fn apply_move_commits(
968 mut_repo: &mut MutableRepo,
969 commits: ComputedMoveCommits,
970 options: &RebaseOptions,
971) -> BackendResult<MoveCommitsStats> {
972 let mut num_rebased_targets = 0;
973 let mut num_rebased_descendants = 0;
974 let mut num_skipped_rebases = 0;
975 let mut num_abandoned_empty = 0;
976
977 let rebase_descendant_options = &RebaseOptions {
980 empty: EmptyBehavior::Keep,
981 rewrite_refs: options.rewrite_refs.clone(),
982 simplify_ancestor_merge: false,
983 };
984
985 let mut rebased_commits: HashMap<CommitId, RebasedCommit> = HashMap::new();
986 mut_repo
987 .transform_commits(
988 commits.descendants,
989 &commits.commit_new_parents_map,
990 &options.rewrite_refs,
991 async |rewriter| {
992 let old_commit_id = rewriter.old_commit().id().clone();
993 if commits.to_abandon.contains(&old_commit_id) {
994 rewriter.abandon();
995 } else if rewriter.parents_changed() {
996 let is_target_commit = commits.target_commit_ids.contains(&old_commit_id);
997 let rebased_commit = rebase_commit_with_options(
998 rewriter,
999 if is_target_commit {
1000 options
1001 } else {
1002 rebase_descendant_options
1003 },
1004 )
1005 .await?;
1006 if let RebasedCommit::Abandoned { .. } = rebased_commit {
1007 num_abandoned_empty += 1;
1008 } else if is_target_commit {
1009 num_rebased_targets += 1;
1010 } else {
1011 num_rebased_descendants += 1;
1012 }
1013 rebased_commits.insert(old_commit_id, rebased_commit);
1014 } else {
1015 num_skipped_rebases += 1;
1016 }
1017
1018 Ok(())
1019 },
1020 )
1021 .await?;
1022
1023 Ok(MoveCommitsStats {
1024 num_rebased_targets,
1025 num_rebased_descendants,
1026 num_skipped_rebases,
1027 num_abandoned_empty,
1028 rebased_commits,
1029 })
1030}
1031
1032#[derive(Debug, Default)]
1033pub struct DuplicateCommitsStats {
1034 pub duplicated_commits: IndexMap<CommitId, Commit>,
1036 pub num_rebased: u32,
1039}
1040
1041pub async fn duplicate_commits(
1059 mut_repo: &mut MutableRepo,
1060 target_commit_ids: &[CommitId],
1061 target_descriptions: &HashMap<CommitId, String>,
1062 parent_commit_ids: &[CommitId],
1063 children_commit_ids: &[CommitId],
1064) -> BackendResult<DuplicateCommitsStats> {
1065 if target_commit_ids.is_empty() {
1066 return Ok(DuplicateCommitsStats::default());
1067 }
1068
1069 let mut duplicated_old_to_new: IndexMap<CommitId, Commit> = IndexMap::new();
1070 let mut num_rebased = 0;
1071
1072 let target_commit_ids: IndexSet<_> = target_commit_ids.iter().cloned().collect();
1073
1074 let connected_target_commits: Vec<_> =
1075 RevsetExpression::commits(target_commit_ids.iter().cloned().collect_vec())
1076 .connected()
1077 .evaluate(mut_repo)
1078 .map_err(|err| err.into_backend_error())?
1079 .stream()
1080 .commits(mut_repo.store())
1081 .try_collect()
1082 .await
1083 .map_err(|err| err.into_backend_error())?;
1084
1085 let target_commits_internal_parents = {
1092 let mut target_commits_internal_parents =
1093 compute_internal_parents_within(&target_commit_ids, &connected_target_commits);
1094 target_commits_internal_parents.retain(|id, _| target_commit_ids.contains(id));
1095 target_commits_internal_parents
1096 };
1097
1098 let target_root_ids: HashSet<_> = target_commits_internal_parents
1100 .iter()
1101 .filter(|(_, parents)| parents.is_empty())
1102 .map(|(commit_id, _)| commit_id.clone())
1103 .collect();
1104
1105 let target_head_ids = if !children_commit_ids.is_empty() {
1108 compute_commits_heads(&target_commit_ids, &connected_target_commits)
1109 } else {
1110 vec![]
1111 };
1112
1113 for original_commit_id in target_commit_ids.iter().rev() {
1116 let original_commit = mut_repo
1117 .store()
1118 .get_commit_async(original_commit_id)
1119 .await?;
1120 let new_parent_ids = if target_root_ids.contains(original_commit_id) {
1121 parent_commit_ids.to_vec()
1122 } else {
1123 target_commits_internal_parents
1124 .get(original_commit_id)
1125 .unwrap()
1126 .iter()
1127 .map(|id| {
1129 duplicated_old_to_new
1130 .get(id)
1131 .map_or(id, |commit| commit.id())
1132 .clone()
1133 })
1134 .collect()
1135 };
1136 let mut new_commit_builder = CommitRewriter::new(mut_repo, original_commit, new_parent_ids)
1137 .rebase()
1138 .await?
1139 .clear_rewrite_source()
1140 .generate_new_change_id();
1141 if let Some(desc) = target_descriptions.get(original_commit_id) {
1142 new_commit_builder = new_commit_builder.set_description(desc);
1143 }
1144 duplicated_old_to_new.insert(
1145 original_commit_id.clone(),
1146 new_commit_builder.write().await?,
1147 );
1148 }
1149
1150 let target_head_ids = target_head_ids
1153 .into_iter()
1154 .map(|commit_id| {
1155 duplicated_old_to_new
1156 .get(&commit_id)
1157 .map_or(commit_id, |commit| commit.id().clone())
1158 })
1159 .collect_vec();
1160
1161 let children_commit_ids_set: HashSet<CommitId> = children_commit_ids.iter().cloned().collect();
1163 mut_repo
1164 .transform_descendants(children_commit_ids.to_vec(), async |mut rewriter| {
1165 if children_commit_ids_set.contains(rewriter.old_commit().id()) {
1166 let mut child_new_parent_ids = IndexSet::new();
1167 for old_parent_id in rewriter.old_commit().parent_ids() {
1168 if parent_commit_ids.contains(old_parent_id) {
1173 child_new_parent_ids.extend(target_head_ids.clone());
1174 } else {
1175 child_new_parent_ids.insert(old_parent_id.clone());
1176 }
1177 }
1178 child_new_parent_ids.extend(target_head_ids.clone());
1181 rewriter.set_new_parents(child_new_parent_ids.into_iter().collect());
1182 }
1183 num_rebased += 1;
1184 rewriter.rebase().await?.write().await?;
1185 Ok(())
1186 })
1187 .await?;
1188
1189 Ok(DuplicateCommitsStats {
1190 duplicated_commits: duplicated_old_to_new,
1191 num_rebased,
1192 })
1193}
1194
1195pub async fn duplicate_commits_onto_parents(
1205 mut_repo: &mut MutableRepo,
1206 target_commits: &[CommitId],
1207 target_descriptions: &HashMap<CommitId, String>,
1208) -> BackendResult<DuplicateCommitsStats> {
1209 if target_commits.is_empty() {
1210 return Ok(DuplicateCommitsStats::default());
1211 }
1212
1213 let mut duplicated_old_to_new: IndexMap<CommitId, Commit> = IndexMap::new();
1214
1215 for original_commit_id in target_commits.iter().rev() {
1218 let original_commit = mut_repo
1219 .store()
1220 .get_commit_async(original_commit_id)
1221 .await?;
1222 let new_parent_ids = original_commit
1223 .parent_ids()
1224 .iter()
1225 .map(|id| {
1226 duplicated_old_to_new
1227 .get(id)
1228 .map_or(id, |commit| commit.id())
1229 .clone()
1230 })
1231 .collect();
1232 let mut new_commit_builder = mut_repo
1233 .rewrite_commit(&original_commit)
1234 .clear_rewrite_source()
1235 .generate_new_change_id()
1236 .set_parents(new_parent_ids);
1237 if let Some(desc) = target_descriptions.get(original_commit_id) {
1238 new_commit_builder = new_commit_builder.set_description(desc);
1239 }
1240 duplicated_old_to_new.insert(
1241 original_commit_id.clone(),
1242 new_commit_builder.write().await?,
1243 );
1244 }
1245
1246 Ok(DuplicateCommitsStats {
1247 duplicated_commits: duplicated_old_to_new,
1248 num_rebased: 0,
1249 })
1250}
1251
1252fn compute_internal_parents_within(
1260 target_commit_ids: &IndexSet<CommitId>,
1261 graph_commits: &[Commit],
1262) -> HashMap<CommitId, IndexSet<CommitId>> {
1263 let mut internal_parents: HashMap<CommitId, IndexSet<CommitId>> = HashMap::new();
1264 for commit in graph_commits.iter().rev() {
1265 let mut new_parents = IndexSet::new();
1268 for old_parent in commit.parent_ids() {
1269 if target_commit_ids.contains(old_parent) {
1270 new_parents.insert(old_parent.clone());
1271 } else if let Some(parents) = internal_parents.get(old_parent) {
1272 new_parents.extend(parents.iter().cloned());
1273 }
1274 }
1275 internal_parents.insert(commit.id().clone(), new_parents);
1276 }
1277 internal_parents
1278}
1279
1280fn compute_commits_heads(
1286 target_commit_ids: &IndexSet<CommitId>,
1287 connected_target_commits: &[Commit],
1288) -> Vec<CommitId> {
1289 let mut target_head_ids: HashSet<CommitId> = HashSet::new();
1290 for commit in connected_target_commits.iter().rev() {
1291 target_head_ids.insert(commit.id().clone());
1292 for old_parent in commit.parent_ids() {
1293 target_head_ids.remove(old_parent);
1294 }
1295 }
1296 connected_target_commits
1297 .iter()
1298 .rev()
1299 .filter(|commit| {
1300 target_head_ids.contains(commit.id()) && target_commit_ids.contains(commit.id())
1301 })
1302 .map(|commit| commit.id().clone())
1303 .collect_vec()
1304}
1305
1306#[derive(Debug)]
1307pub struct CommitWithSelection {
1308 pub commit: Commit,
1309 pub selected_tree: MergedTree,
1310 pub parent_tree: MergedTree,
1311}
1312
1313impl CommitWithSelection {
1314 pub fn is_full_selection(&self) -> bool {
1316 self.selected_tree.tree_ids() == self.commit.tree_ids()
1317 }
1318
1319 pub fn is_empty_selection(&self) -> bool {
1325 self.selected_tree.tree_ids() == self.parent_tree.tree_ids()
1326 }
1327
1328 pub async fn diff_with_labels(
1332 &self,
1333 parent_tree_label: &str,
1334 selected_tree_label: &str,
1335 full_selection_label: &str,
1336 ) -> BackendResult<Diff<(MergedTree, String)>> {
1337 let parent_tree_label = format!(
1338 "{} ({parent_tree_label})",
1339 self.commit.parents_conflict_label().await?
1340 );
1341
1342 let commit_label = self.commit.conflict_label();
1343 let selected_tree_label = if self.is_full_selection() {
1344 format!("{commit_label} ({full_selection_label})")
1345 } else {
1346 format!("{selected_tree_label} (from {commit_label})")
1347 };
1348
1349 Ok(Diff::new(
1350 (self.parent_tree.clone(), parent_tree_label),
1351 (self.selected_tree.clone(), selected_tree_label),
1352 ))
1353 }
1354}
1355
1356#[must_use]
1358pub struct SquashedCommit<'repo> {
1359 pub commit_builder: CommitBuilder<'repo>,
1361 pub abandoned_commits: Vec<Commit>,
1363}
1364
1365pub async fn squash_commits<'repo>(
1369 repo: &'repo mut MutableRepo,
1370 sources: &[CommitWithSelection],
1371 destination: &Commit,
1372 keep_emptied: bool,
1373) -> BackendResult<Option<SquashedCommit<'repo>>> {
1374 struct SourceCommit<'a> {
1375 commit: &'a CommitWithSelection,
1376 diff: Diff<(MergedTree, String)>,
1377 abandon: bool,
1378 }
1379 let mut source_commits = vec![];
1380 for source in sources {
1381 let abandon = !keep_emptied && source.is_full_selection();
1382 if !abandon && source.is_empty_selection() {
1383 continue;
1387 }
1388
1389 source_commits.push(SourceCommit {
1392 commit: source,
1393 diff: source
1394 .diff_with_labels(
1395 "parents of squashed revision",
1396 "selected changes for squash",
1397 "squashed revision",
1398 )
1399 .await?,
1400 abandon,
1401 });
1402 }
1403
1404 if source_commits.is_empty() {
1405 return Ok(None);
1406 }
1407
1408 let mut abandoned_commits = vec![];
1409 for source in &source_commits {
1410 if source.abandon {
1411 repo.record_abandoned_commit(&source.commit.commit);
1412 abandoned_commits.push(source.commit.commit.clone());
1413 } else {
1414 let source_tree = source.commit.commit.tree();
1415 let new_source_tree = MergedTree::merge(Merge::from_diffs(
1417 (source_tree, source.commit.commit.conflict_label()),
1418 [source.diff.clone().invert()],
1419 ))
1420 .await?;
1421 repo.rewrite_commit(&source.commit.commit)
1422 .set_tree(new_source_tree)
1423 .write()
1424 .await?;
1425 }
1426 }
1427
1428 let mut rewritten_destination = destination.clone();
1429 if fallible_any(sources, async |source| {
1430 repo.index()
1431 .is_ancestor(source.commit.id(), destination.id())
1432 .await
1433 })
1434 .await
1435 .map_err(|err| BackendError::Other(err.into()))?
1437 {
1438 let immutable = RevsetExpression::none();
1443 let options = RebaseOptions::default();
1444 repo.rebase_descendants_with_options(&immutable, &options, |old_commit, rebased_commit| {
1445 if old_commit.id() != destination.id() {
1446 return;
1447 }
1448 rewritten_destination = match rebased_commit {
1449 RebasedCommit::Rewritten(commit) => commit,
1450 RebasedCommit::Abandoned { .. } => panic!("all commits should be kept"),
1451 };
1452 })
1453 .await?;
1454 }
1455 let mut predecessors = vec![destination.id().clone()];
1456 predecessors.extend(
1457 source_commits
1458 .iter()
1459 .map(|source| source.commit.commit.id().clone()),
1460 );
1461 let destination_tree = MergedTree::merge(Merge::from_diffs(
1463 (
1464 rewritten_destination.tree(),
1465 format!("{} (squash destination)", destination.conflict_label()),
1466 ),
1467 source_commits.into_iter().map(|source| source.diff),
1468 ))
1469 .await?;
1470
1471 let commit_builder = repo
1472 .rewrite_commit(&rewritten_destination)
1473 .set_tree(destination_tree)
1474 .set_predecessors(predecessors);
1475 Ok(Some(SquashedCommit {
1476 commit_builder,
1477 abandoned_commits,
1478 }))
1479}
1480
1481pub async fn find_duplicate_divergent_commits(
1485 repo: &dyn Repo,
1486 new_parent_ids: &[CommitId],
1487 target: &MoveCommitsTarget,
1488) -> BackendResult<Vec<Commit>> {
1489 let target_commits: Vec<Commit> = match target {
1490 MoveCommitsTarget::Commits(commit_ids) => {
1491 try_join_all(
1492 commit_ids
1493 .iter()
1494 .map(|commit_id| repo.store().get_commit_async(commit_id)),
1495 )
1496 .await?
1497 }
1498 MoveCommitsTarget::Roots(root_ids) => RevsetExpression::commits(root_ids.clone())
1499 .descendants()
1500 .evaluate(repo)
1501 .map_err(|err| err.into_backend_error())?
1502 .stream()
1503 .commits(repo.store())
1504 .try_collect()
1505 .await
1506 .map_err(|err| err.into_backend_error())?,
1507 };
1508 let target_commit_ids: HashSet<&CommitId> = target_commits.iter().map(Commit::id).collect();
1509
1510 let divergent_changes: Vec<_> = futures::stream::iter(&target_commits)
1513 .map(async |target_commit| -> BackendResult<_> {
1514 let mut ancestor_candidates = repo
1515 .resolve_change_id(target_commit.change_id())
1516 .await
1517 .map_err(|err| BackendError::Other(err.into()))?
1519 .and_then(ResolvedChangeTargets::into_visible)
1520 .unwrap_or_default();
1521 ancestor_candidates.retain(|commit_id| !target_commit_ids.contains(commit_id));
1522 Ok((target_commit, ancestor_candidates))
1523 })
1524 .buffered(repo.store().concurrency())
1525 .try_filter(|(_, candidates)| ready(!candidates.is_empty()))
1526 .try_collect()
1527 .await?;
1528 if divergent_changes.is_empty() {
1529 return Ok(Vec::new());
1530 }
1531
1532 let target_root_ids = match target {
1533 MoveCommitsTarget::Commits(commit_ids) => commit_ids,
1534 MoveCommitsTarget::Roots(root_ids) => root_ids,
1535 };
1536
1537 let is_new_ancestor = RevsetExpression::commits(target_root_ids.clone())
1540 .range(&RevsetExpression::commits(new_parent_ids.to_owned()))
1541 .evaluate(repo)
1542 .map_err(|err| err.into_backend_error())?
1543 .containing_fn();
1544
1545 let mut duplicate_divergent = Vec::new();
1546 for (target_commit, ancestor_candidates) in divergent_changes {
1551 for ancestor_candidate_id in ancestor_candidates {
1552 if !is_new_ancestor(&ancestor_candidate_id)
1553 .await
1554 .map_err(|err| err.into_backend_error())?
1555 {
1556 continue;
1557 }
1558
1559 let ancestor_candidate = repo
1560 .store()
1561 .get_commit_async(&ancestor_candidate_id)
1562 .await?;
1563 let new_tree =
1564 rebase_to_dest_parent(repo, slice::from_ref(target_commit), &ancestor_candidate)
1565 .await?;
1566 if new_tree.tree_ids() == ancestor_candidate.tree_ids() {
1569 duplicate_divergent.push(target_commit.clone());
1570 break;
1571 }
1572 }
1573 }
1574 Ok(duplicate_divergent)
1575}