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::future::try_join_all;
24use futures::try_join;
25use indexmap::IndexMap;
26use indexmap::IndexSet;
27use itertools::Itertools as _;
28use pollster::FutureExt as _;
29use tracing::instrument;
30
31use crate::backend::BackendError;
32use crate::backend::BackendResult;
33use crate::backend::CommitId;
34use crate::backend::MergedTreeId;
35use crate::commit::Commit;
36use crate::commit::CommitIteratorExt as _;
37use crate::commit_builder::CommitBuilder;
38use crate::index::Index;
39use crate::index::IndexError;
40use crate::matchers::Matcher;
41use crate::matchers::Visit;
42use crate::merge::Merge;
43use crate::merged_tree::MergedTree;
44use crate::merged_tree::MergedTreeBuilder;
45use crate::merged_tree::TreeDiffEntry;
46use crate::repo::MutableRepo;
47use crate::repo::Repo;
48use crate::repo_path::RepoPath;
49use crate::revset::RevsetExpression;
50use crate::revset::RevsetIteratorExt as _;
51use crate::store::Store;
52
53#[instrument(skip(repo))]
55pub async fn merge_commit_trees(repo: &dyn Repo, commits: &[Commit]) -> BackendResult<MergedTree> {
56 if let [commit] = commits {
57 commit.tree_async().await
58 } else {
59 merge_commit_trees_no_resolve_without_repo(repo.store(), repo.index(), commits)
60 .await?
61 .resolve()
62 .await
63 }
64}
65
66#[instrument(skip(index))]
68pub async fn merge_commit_trees_no_resolve_without_repo(
69 store: &Arc<Store>,
70 index: &dyn Index,
71 commits: &[Commit],
72) -> BackendResult<MergedTree> {
73 let commit_ids = commits
74 .iter()
75 .map(|commit| commit.id().clone())
76 .collect_vec();
77 let commit_id_merge = find_recursive_merge_commits(store, index, commit_ids)?;
78 let tree_merge = commit_id_merge
79 .try_map_async(async |commit_id| {
80 let commit = store.get_commit_async(commit_id).await?;
81 let tree = commit.tree_async().await?;
82 Ok::<_, BackendError>(tree.take())
83 })
84 .await?;
85 Ok(MergedTree::new(tree_merge.flatten().simplify()))
86}
87
88pub fn find_recursive_merge_commits(
90 store: &Arc<Store>,
91 index: &dyn Index,
92 mut commit_ids: Vec<CommitId>,
93) -> BackendResult<Merge<CommitId>> {
94 if commit_ids.is_empty() {
95 Ok(Merge::resolved(store.root_commit_id().clone()))
96 } else if commit_ids.len() == 1 {
97 Ok(Merge::resolved(commit_ids.pop().unwrap()))
98 } else {
99 let mut result = Merge::resolved(commit_ids[0].clone());
100 for (i, other_commit_id) in commit_ids.iter().enumerate().skip(1) {
101 let ancestor_ids = index.common_ancestors(&commit_ids[0..i], &commit_ids[i..][..1]);
102 let ancestor_merge = find_recursive_merge_commits(store, index, ancestor_ids)?;
103 result = Merge::from_vec(vec![
104 result,
105 ancestor_merge,
106 Merge::resolved(other_commit_id.clone()),
107 ])
108 .flatten();
109 }
110 Ok(result)
111 }
112}
113
114pub async fn restore_tree(
116 source: &MergedTree,
117 destination: &MergedTree,
118 matcher: &dyn Matcher,
119) -> BackendResult<MergedTreeId> {
120 if matcher.visit(RepoPath::root()) == Visit::AllRecursively {
121 Ok(source.id())
123 } else {
124 let mut tree_builder = MergedTreeBuilder::new(destination.id().clone());
127 let mut diff_stream = source.diff_stream(destination, matcher);
129 while let Some(TreeDiffEntry {
130 path: repo_path,
131 values,
132 }) = diff_stream.next().await
133 {
134 let source_value = values?.before;
135 tree_builder.set_or_remove(repo_path, source_value);
136 }
137 tree_builder.write_tree(destination.store())
138 }
139}
140
141pub async fn rebase_commit(
142 mut_repo: &mut MutableRepo,
143 old_commit: Commit,
144 new_parents: Vec<CommitId>,
145) -> BackendResult<Commit> {
146 let rewriter = CommitRewriter::new(mut_repo, old_commit, new_parents);
147 let builder = rewriter.rebase().await?;
148 builder.write()
149}
150
151pub struct CommitRewriter<'repo> {
153 mut_repo: &'repo mut MutableRepo,
154 old_commit: Commit,
155 new_parents: Vec<CommitId>,
156}
157
158impl<'repo> CommitRewriter<'repo> {
159 pub fn new(
161 mut_repo: &'repo mut MutableRepo,
162 old_commit: Commit,
163 new_parents: Vec<CommitId>,
164 ) -> Self {
165 Self {
166 mut_repo,
167 old_commit,
168 new_parents,
169 }
170 }
171
172 pub fn repo_mut(&mut self) -> &mut MutableRepo {
174 self.mut_repo
175 }
176
177 pub fn old_commit(&self) -> &Commit {
179 &self.old_commit
180 }
181
182 pub fn new_parents(&self) -> &[CommitId] {
184 &self.new_parents
185 }
186
187 pub fn set_new_parents(&mut self, new_parents: Vec<CommitId>) {
189 self.new_parents = new_parents;
190 }
191
192 pub fn set_new_rewritten_parents(&mut self, unrewritten_parents: &[CommitId]) {
195 self.new_parents = self.mut_repo.new_parents(unrewritten_parents);
196 }
197
198 pub fn replace_parent<'a>(
201 &mut self,
202 old_parent: &CommitId,
203 new_parents: impl IntoIterator<Item = &'a CommitId>,
204 ) {
205 if let Some(i) = self.new_parents.iter().position(|p| p == old_parent) {
206 self.new_parents
207 .splice(i..i + 1, new_parents.into_iter().cloned());
208 let mut unique = HashSet::new();
209 self.new_parents.retain(|p| unique.insert(p.clone()));
210 }
211 }
212
213 pub fn parents_changed(&self) -> bool {
216 self.new_parents != self.old_commit.parent_ids()
217 }
218
219 pub fn simplify_ancestor_merge(&mut self) -> Result<(), IndexError> {
222 let head_set: HashSet<_> = self
223 .mut_repo
224 .index()
225 .heads(&mut self.new_parents.iter())?
226 .into_iter()
227 .collect();
228 self.new_parents.retain(|parent| head_set.contains(parent));
229 Ok(())
230 }
231
232 pub fn abandon(self) {
236 let old_commit_id = self.old_commit.id().clone();
237 let new_parents = self.new_parents;
238 self.mut_repo
239 .record_abandoned_commit_with_parents(old_commit_id, new_parents);
240 }
241
242 pub async fn rebase_with_empty_behavior(
245 self,
246 empty: EmptyBehavior,
247 ) -> BackendResult<Option<CommitBuilder<'repo>>> {
248 let old_parents_fut = self.old_commit.parents_async();
249 let new_parents_fut = try_join_all(
250 self.new_parents
251 .iter()
252 .map(|new_parent_id| self.mut_repo.store().get_commit_async(new_parent_id)),
253 );
254 let (old_parents, new_parents) = try_join!(old_parents_fut, new_parents_fut)?;
255 let old_parent_trees = old_parents
256 .iter()
257 .map(|parent| parent.tree_id().clone())
258 .collect_vec();
259 let new_parent_trees = new_parents
260 .iter()
261 .map(|parent| parent.tree_id().clone())
262 .collect_vec();
263
264 let (was_empty, new_tree_id) = if new_parent_trees == old_parent_trees {
265 (
266 true,
269 self.old_commit.tree_id().clone(),
271 )
272 } else {
273 let old_base_tree_fut = merge_commit_trees(self.mut_repo, &old_parents);
277 let new_base_tree_fut = merge_commit_trees(self.mut_repo, &new_parents);
278 let old_tree_fut = self.old_commit.tree_async();
279 let (old_base_tree, new_base_tree, old_tree) =
280 try_join!(old_base_tree_fut, new_base_tree_fut, old_tree_fut)?;
281 (
282 old_base_tree.id() == *self.old_commit.tree_id(),
283 new_base_tree.merge(old_base_tree, old_tree).await?.id(),
284 )
285 };
286 if let [parent] = &new_parents[..] {
289 let should_abandon = match empty {
290 EmptyBehavior::Keep => false,
291 EmptyBehavior::AbandonNewlyEmpty => *parent.tree_id() == new_tree_id && !was_empty,
292 EmptyBehavior::AbandonAllEmpty => *parent.tree_id() == new_tree_id,
293 };
294 if should_abandon {
295 self.abandon();
296 return Ok(None);
297 }
298 }
299
300 let builder = self
301 .mut_repo
302 .rewrite_commit(&self.old_commit)
303 .set_parents(self.new_parents)
304 .set_tree_id(new_tree_id);
305 Ok(Some(builder))
306 }
307
308 pub async fn rebase(self) -> BackendResult<CommitBuilder<'repo>> {
311 let builder = self.rebase_with_empty_behavior(EmptyBehavior::Keep).await?;
312 Ok(builder.unwrap())
313 }
314
315 pub fn reparent(self) -> CommitBuilder<'repo> {
318 self.mut_repo
319 .rewrite_commit(&self.old_commit)
320 .set_parents(self.new_parents)
321 }
322}
323
324pub enum RebasedCommit {
325 Rewritten(Commit),
326 Abandoned { parent_id: CommitId },
327}
328
329pub fn rebase_commit_with_options(
330 mut rewriter: CommitRewriter<'_>,
331 options: &RebaseOptions,
332) -> BackendResult<RebasedCommit> {
333 if options.simplify_ancestor_merge {
335 rewriter
338 .simplify_ancestor_merge()
339 .map_err(|err| BackendError::Other(err.into()))?;
340 }
341
342 let single_parent = match &rewriter.new_parents[..] {
343 [parent_id] => Some(parent_id.clone()),
344 _ => None,
345 };
346 let new_parents_len = rewriter.new_parents.len();
347 if let Some(builder) = rewriter
348 .rebase_with_empty_behavior(options.empty)
349 .block_on()?
350 {
351 let new_commit = builder.write()?;
352 Ok(RebasedCommit::Rewritten(new_commit))
353 } else {
354 assert_eq!(new_parents_len, 1);
355 Ok(RebasedCommit::Abandoned {
356 parent_id: single_parent.unwrap(),
357 })
358 }
359}
360
361pub fn rebase_to_dest_parent(
363 repo: &dyn Repo,
364 sources: &[Commit],
365 destination: &Commit,
366) -> BackendResult<MergedTree> {
367 if let [source] = sources
368 && source.parent_ids() == destination.parent_ids()
369 {
370 return source.tree();
371 }
372 sources.iter().try_fold(
373 destination.parent_tree(repo)?,
374 |destination_tree, source| {
375 let source_parent_tree = source.parent_tree(repo)?;
376 let source_tree = source.tree()?;
377 destination_tree
378 .merge(source_parent_tree, source_tree)
379 .block_on()
380 },
381 )
382}
383
384#[derive(Clone, Copy, Default, PartialEq, Eq, Debug)]
385pub enum EmptyBehavior {
386 #[default]
388 Keep,
389 AbandonNewlyEmpty,
393 AbandonAllEmpty,
397}
398
399#[derive(Clone, Debug, Default)]
406pub struct RebaseOptions {
407 pub empty: EmptyBehavior,
408 pub rewrite_refs: RewriteRefsOptions,
409 pub simplify_ancestor_merge: bool,
412}
413
414#[derive(Clone, Debug, Default)]
416pub struct RewriteRefsOptions {
417 pub delete_abandoned_bookmarks: bool,
422}
423
424pub struct MoveCommitsStats {
425 pub num_rebased_targets: u32,
427 pub num_rebased_descendants: u32,
429 pub num_skipped_rebases: u32,
432 pub num_abandoned_empty: u32,
434 pub rebased_commits: HashMap<CommitId, RebasedCommit>,
436}
437
438#[derive(Clone, Debug)]
440pub struct MoveCommitsLocation {
441 pub new_parent_ids: Vec<CommitId>,
442 pub new_child_ids: Vec<CommitId>,
443 pub target: MoveCommitsTarget,
444}
445
446#[derive(Clone, Debug)]
447pub enum MoveCommitsTarget {
448 Commits(Vec<CommitId>),
451 Roots(Vec<CommitId>),
453}
454
455#[derive(Clone, Debug)]
456pub struct ComputedMoveCommits {
457 target_commit_ids: IndexSet<CommitId>,
458 descendants: Vec<Commit>,
459 commit_new_parents_map: HashMap<CommitId, Vec<CommitId>>,
460 to_abandon: HashSet<CommitId>,
461}
462
463impl ComputedMoveCommits {
464 fn empty() -> Self {
465 Self {
466 target_commit_ids: IndexSet::new(),
467 descendants: vec![],
468 commit_new_parents_map: HashMap::new(),
469 to_abandon: HashSet::new(),
470 }
471 }
472
473 pub fn record_to_abandon(&mut self, commit_ids: impl IntoIterator<Item = CommitId>) {
480 self.to_abandon.extend(commit_ids);
481 }
482
483 pub fn apply(
484 self,
485 mut_repo: &mut MutableRepo,
486 options: &RebaseOptions,
487 ) -> BackendResult<MoveCommitsStats> {
488 apply_move_commits(mut_repo, self, options)
489 }
490}
491
492pub fn move_commits(
501 mut_repo: &mut MutableRepo,
502 loc: &MoveCommitsLocation,
503 options: &RebaseOptions,
504) -> BackendResult<MoveCommitsStats> {
505 compute_move_commits(mut_repo, loc)?.apply(mut_repo, options)
506}
507
508pub fn compute_move_commits(
509 repo: &MutableRepo,
510 loc: &MoveCommitsLocation,
511) -> BackendResult<ComputedMoveCommits> {
512 let target_commit_ids: IndexSet<CommitId>;
513 let connected_target_commits: Vec<Commit>;
514 let connected_target_commits_internal_parents: HashMap<CommitId, IndexSet<CommitId>>;
515 let target_roots: HashSet<CommitId>;
516
517 match &loc.target {
518 MoveCommitsTarget::Commits(commit_ids) => {
519 if commit_ids.is_empty() {
520 return Ok(ComputedMoveCommits::empty());
521 }
522
523 target_commit_ids = commit_ids.iter().cloned().collect();
524
525 connected_target_commits = RevsetExpression::commits(commit_ids.clone())
526 .connected()
527 .evaluate(repo)
528 .map_err(|err| err.into_backend_error())?
529 .iter()
530 .commits(repo.store())
531 .try_collect()
532 .map_err(|err| err.into_backend_error())?;
533 connected_target_commits_internal_parents =
534 compute_internal_parents_within(&target_commit_ids, &connected_target_commits);
535
536 target_roots = connected_target_commits_internal_parents
537 .iter()
538 .filter(|&(commit_id, parents)| {
539 target_commit_ids.contains(commit_id) && parents.is_empty()
540 })
541 .map(|(commit_id, _)| commit_id.clone())
542 .collect();
543 }
544 MoveCommitsTarget::Roots(root_ids) => {
545 if root_ids.is_empty() {
546 return Ok(ComputedMoveCommits::empty());
547 }
548
549 target_commit_ids = RevsetExpression::commits(root_ids.clone())
550 .descendants()
551 .evaluate(repo)
552 .map_err(|err| err.into_backend_error())?
553 .iter()
554 .try_collect()
555 .map_err(|err| err.into_backend_error())?;
556
557 connected_target_commits = target_commit_ids
558 .iter()
559 .map(|id| repo.store().get_commit(id))
560 .try_collect()?;
561 connected_target_commits_internal_parents = HashMap::new();
564 target_roots = root_ids.iter().cloned().collect();
565 }
566 }
567
568 let mut target_commits_external_parents: HashMap<CommitId, IndexSet<CommitId>> = HashMap::new();
572 for id in target_commit_ids.iter().rev() {
573 let commit = repo.store().get_commit(id)?;
574 let mut new_parents = IndexSet::new();
575 for old_parent in commit.parent_ids() {
576 if let Some(parents) = target_commits_external_parents.get(old_parent) {
577 new_parents.extend(parents.iter().cloned());
578 } else {
579 new_parents.insert(old_parent.clone());
580 }
581 }
582 target_commits_external_parents.insert(commit.id().clone(), new_parents);
583 }
584
585 let new_parent_ids: Vec<_> = loc
589 .new_parent_ids
590 .iter()
591 .flat_map(|parent_id| {
592 if let Some(parent_ids) = target_commits_external_parents.get(parent_id) {
593 parent_ids.iter().cloned().collect_vec()
594 } else {
595 vec![parent_id.clone()]
596 }
597 })
598 .collect();
599
600 let new_children: Vec<_> = if loc
604 .new_child_ids
605 .iter()
606 .any(|id| target_commit_ids.contains(id))
607 {
608 let target_commits_descendants: Vec<_> =
609 RevsetExpression::commits(target_commit_ids.iter().cloned().collect_vec())
610 .union(
611 &RevsetExpression::commits(target_commit_ids.iter().cloned().collect_vec())
612 .children(),
613 )
614 .evaluate(repo)
615 .map_err(|err| err.into_backend_error())?
616 .iter()
617 .commits(repo.store())
618 .try_collect()
619 .map_err(|err| err.into_backend_error())?;
620
621 let mut target_commit_external_descendants: HashMap<CommitId, IndexSet<Commit>> =
624 HashMap::new();
625 for commit in &target_commits_descendants {
628 if !target_commit_external_descendants.contains_key(commit.id()) {
629 let children = if target_commit_ids.contains(commit.id()) {
630 IndexSet::new()
631 } else {
632 IndexSet::from([commit.clone()])
633 };
634 target_commit_external_descendants.insert(commit.id().clone(), children);
635 }
636
637 let children = target_commit_external_descendants
638 .get(commit.id())
639 .unwrap()
640 .iter()
641 .cloned()
642 .collect_vec();
643 for parent_id in commit.parent_ids() {
644 if target_commit_ids.contains(parent_id) {
645 if let Some(target_children) =
646 target_commit_external_descendants.get_mut(parent_id)
647 {
648 target_children.extend(children.iter().cloned());
649 } else {
650 target_commit_external_descendants
651 .insert(parent_id.clone(), children.iter().cloned().collect());
652 }
653 };
654 }
655 }
656
657 let mut new_children = Vec::new();
658 for id in &loc.new_child_ids {
659 if let Some(children) = target_commit_external_descendants.get(id) {
660 new_children.extend(children.iter().cloned());
661 } else {
662 new_children.push(repo.store().get_commit(id)?);
663 }
664 }
665 new_children
666 } else {
667 loc.new_child_ids
668 .iter()
669 .map(|id| repo.store().get_commit(id))
670 .try_collect()?
671 };
672
673 let new_children_parents: HashMap<_, _> = if !new_children.is_empty() {
676 let target_heads = compute_commits_heads(&target_commit_ids, &connected_target_commits);
679
680 new_children
681 .iter()
682 .map(|child_commit| {
683 let mut new_child_parent_ids = IndexSet::new();
684 for old_child_parent_id in child_commit.parent_ids() {
685 let old_child_parent_ids = if let Some(parents) =
687 target_commits_external_parents.get(old_child_parent_id)
688 {
689 parents.iter().collect_vec()
690 } else {
691 vec![old_child_parent_id]
692 };
693
694 for id in old_child_parent_ids {
698 if new_parent_ids.contains(id) {
699 new_child_parent_ids.extend(target_heads.clone());
700 } else {
701 new_child_parent_ids.insert(id.clone());
702 };
703 }
704 }
705
706 new_child_parent_ids.extend(target_heads.clone());
709
710 (
711 child_commit.id().clone(),
712 new_child_parent_ids.into_iter().collect_vec(),
713 )
714 })
715 .collect()
716 } else {
717 HashMap::new()
718 };
719
720 let mut roots = target_roots.iter().cloned().collect_vec();
723 roots.extend(new_children.iter().ids().cloned());
724
725 let descendants = repo.find_descendants_for_rebase(roots.clone())?;
726 let commit_new_parents_map = descendants
727 .iter()
728 .map(|commit| {
729 let commit_id = commit.id();
730 let new_parent_ids =
731
732 if let Some(new_child_parents) = new_children_parents.get(commit_id) {
734 new_child_parents.clone()
735 }
736 else if target_commit_ids.contains(commit_id) {
738 if target_roots.contains(commit_id) {
740 new_parent_ids.clone()
741 }
742 else {
750 let mut new_parents = vec![];
751 for parent_id in commit.parent_ids() {
752 if target_commit_ids.contains(parent_id) {
753 new_parents.push(parent_id.clone());
754 } else if let Some(parents) =
755 connected_target_commits_internal_parents.get(parent_id) {
756 new_parents.extend(parents.iter().cloned());
757 } else if !new_children.iter().any(|new_child| {
758 repo.index().is_ancestor(new_child.id(), parent_id) }) {
759 new_parents.push(parent_id.clone());
760 }
761 }
762 new_parents
763 }
764 }
765 else if commit
768 .parent_ids()
769 .iter()
770 .any(|id| target_commits_external_parents.contains_key(id))
771 {
772 let mut new_parents = vec![];
773 for parent in commit.parent_ids() {
774 if let Some(parents) = target_commits_external_parents.get(parent) {
775 new_parents.extend(parents.iter().cloned());
776 } else {
777 new_parents.push(parent.clone());
778 }
779 }
780 new_parents
781 } else {
782 commit.parent_ids().iter().cloned().collect_vec()
783 };
784 (commit.id().clone(), new_parent_ids)
785 })
786 .collect();
787
788 Ok(ComputedMoveCommits {
789 target_commit_ids,
790 descendants,
791 commit_new_parents_map,
792 to_abandon: HashSet::new(),
793 })
794}
795
796fn apply_move_commits(
797 mut_repo: &mut MutableRepo,
798 commits: ComputedMoveCommits,
799 options: &RebaseOptions,
800) -> BackendResult<MoveCommitsStats> {
801 let mut num_rebased_targets = 0;
802 let mut num_rebased_descendants = 0;
803 let mut num_skipped_rebases = 0;
804 let mut num_abandoned_empty = 0;
805
806 let rebase_descendant_options = &RebaseOptions {
808 empty: EmptyBehavior::Keep,
809 rewrite_refs: options.rewrite_refs.clone(),
810 simplify_ancestor_merge: options.simplify_ancestor_merge,
811 };
812
813 let mut rebased_commits: HashMap<CommitId, RebasedCommit> = HashMap::new();
814 mut_repo.transform_commits(
815 commits.descendants,
816 &commits.commit_new_parents_map,
817 &options.rewrite_refs,
818 async |rewriter| {
819 let old_commit_id = rewriter.old_commit().id().clone();
820 if commits.to_abandon.contains(&old_commit_id) {
821 rewriter.abandon();
822 } else if rewriter.parents_changed() {
823 let is_target_commit = commits.target_commit_ids.contains(&old_commit_id);
824 let rebased_commit = rebase_commit_with_options(
825 rewriter,
826 if is_target_commit {
827 options
828 } else {
829 rebase_descendant_options
830 },
831 )?;
832 if let RebasedCommit::Abandoned { .. } = rebased_commit {
833 num_abandoned_empty += 1;
834 } else if is_target_commit {
835 num_rebased_targets += 1;
836 } else {
837 num_rebased_descendants += 1;
838 }
839 rebased_commits.insert(old_commit_id, rebased_commit);
840 } else {
841 num_skipped_rebases += 1;
842 }
843
844 Ok(())
845 },
846 )?;
847
848 Ok(MoveCommitsStats {
849 num_rebased_targets,
850 num_rebased_descendants,
851 num_skipped_rebases,
852 num_abandoned_empty,
853 rebased_commits,
854 })
855}
856
857#[derive(Default)]
858pub struct DuplicateCommitsStats {
859 pub duplicated_commits: IndexMap<CommitId, Commit>,
861 pub num_rebased: u32,
864}
865
866pub async fn duplicate_commits(
884 mut_repo: &mut MutableRepo,
885 target_commit_ids: &[CommitId],
886 target_descriptions: &HashMap<CommitId, String>,
887 parent_commit_ids: &[CommitId],
888 children_commit_ids: &[CommitId],
889) -> BackendResult<DuplicateCommitsStats> {
890 if target_commit_ids.is_empty() {
891 return Ok(DuplicateCommitsStats::default());
892 }
893
894 let mut duplicated_old_to_new: IndexMap<CommitId, Commit> = IndexMap::new();
895 let mut num_rebased = 0;
896
897 let target_commit_ids: IndexSet<_> = target_commit_ids.iter().cloned().collect();
898
899 let connected_target_commits: Vec<_> =
900 RevsetExpression::commits(target_commit_ids.iter().cloned().collect_vec())
901 .connected()
902 .evaluate(mut_repo)
903 .map_err(|err| err.into_backend_error())?
904 .iter()
905 .commits(mut_repo.store())
906 .try_collect()
907 .map_err(|err| err.into_backend_error())?;
908
909 let target_commits_internal_parents = {
916 let mut target_commits_internal_parents =
917 compute_internal_parents_within(&target_commit_ids, &connected_target_commits);
918 target_commits_internal_parents.retain(|id, _| target_commit_ids.contains(id));
919 target_commits_internal_parents
920 };
921
922 let target_root_ids: HashSet<_> = target_commits_internal_parents
924 .iter()
925 .filter(|(_, parents)| parents.is_empty())
926 .map(|(commit_id, _)| commit_id.clone())
927 .collect();
928
929 let target_head_ids = if !children_commit_ids.is_empty() {
932 compute_commits_heads(&target_commit_ids, &connected_target_commits)
933 } else {
934 vec![]
935 };
936
937 for original_commit_id in target_commit_ids.iter().rev() {
940 let original_commit = mut_repo
941 .store()
942 .get_commit_async(original_commit_id)
943 .await?;
944 let new_parent_ids = if target_root_ids.contains(original_commit_id) {
945 parent_commit_ids.to_vec()
946 } else {
947 target_commits_internal_parents
948 .get(original_commit_id)
949 .unwrap()
950 .iter()
951 .map(|id| {
953 duplicated_old_to_new
954 .get(id)
955 .map_or(id, |commit| commit.id())
956 .clone()
957 })
958 .collect()
959 };
960 let mut new_commit_builder = CommitRewriter::new(mut_repo, original_commit, new_parent_ids)
961 .rebase()
962 .await?
963 .clear_rewrite_source()
964 .generate_new_change_id();
965 if let Some(desc) = target_descriptions.get(original_commit_id) {
966 new_commit_builder = new_commit_builder.set_description(desc);
967 }
968 duplicated_old_to_new.insert(original_commit_id.clone(), new_commit_builder.write()?);
969 }
970
971 let target_head_ids = target_head_ids
974 .into_iter()
975 .map(|commit_id| {
976 duplicated_old_to_new
977 .get(&commit_id)
978 .map_or(commit_id, |commit| commit.id().clone())
979 })
980 .collect_vec();
981
982 let children_commit_ids_set: HashSet<CommitId> = children_commit_ids.iter().cloned().collect();
984 mut_repo.transform_descendants(children_commit_ids.to_vec(), async |mut rewriter| {
985 if children_commit_ids_set.contains(rewriter.old_commit().id()) {
986 let mut child_new_parent_ids = IndexSet::new();
987 for old_parent_id in rewriter.old_commit().parent_ids() {
988 if parent_commit_ids.contains(old_parent_id) {
993 child_new_parent_ids.extend(target_head_ids.clone());
994 } else {
995 child_new_parent_ids.insert(old_parent_id.clone());
996 }
997 }
998 child_new_parent_ids.extend(target_head_ids.clone());
1001 rewriter.set_new_parents(child_new_parent_ids.into_iter().collect());
1002 }
1003 num_rebased += 1;
1004 rewriter.rebase().await?.write()?;
1005 Ok(())
1006 })?;
1007
1008 Ok(DuplicateCommitsStats {
1009 duplicated_commits: duplicated_old_to_new,
1010 num_rebased,
1011 })
1012}
1013
1014pub fn duplicate_commits_onto_parents(
1024 mut_repo: &mut MutableRepo,
1025 target_commits: &[CommitId],
1026 target_descriptions: &HashMap<CommitId, String>,
1027) -> BackendResult<DuplicateCommitsStats> {
1028 if target_commits.is_empty() {
1029 return Ok(DuplicateCommitsStats::default());
1030 }
1031
1032 let mut duplicated_old_to_new: IndexMap<CommitId, Commit> = IndexMap::new();
1033
1034 for original_commit_id in target_commits.iter().rev() {
1037 let original_commit = mut_repo.store().get_commit(original_commit_id)?;
1038 let new_parent_ids = original_commit
1039 .parent_ids()
1040 .iter()
1041 .map(|id| {
1042 duplicated_old_to_new
1043 .get(id)
1044 .map_or(id, |commit| commit.id())
1045 .clone()
1046 })
1047 .collect();
1048 let mut new_commit_builder = mut_repo
1049 .rewrite_commit(&original_commit)
1050 .clear_rewrite_source()
1051 .generate_new_change_id()
1052 .set_parents(new_parent_ids);
1053 if let Some(desc) = target_descriptions.get(original_commit_id) {
1054 new_commit_builder = new_commit_builder.set_description(desc);
1055 }
1056 duplicated_old_to_new.insert(original_commit_id.clone(), new_commit_builder.write()?);
1057 }
1058
1059 Ok(DuplicateCommitsStats {
1060 duplicated_commits: duplicated_old_to_new,
1061 num_rebased: 0,
1062 })
1063}
1064
1065fn compute_internal_parents_within(
1073 target_commit_ids: &IndexSet<CommitId>,
1074 graph_commits: &[Commit],
1075) -> HashMap<CommitId, IndexSet<CommitId>> {
1076 let mut internal_parents: HashMap<CommitId, IndexSet<CommitId>> = HashMap::new();
1077 for commit in graph_commits.iter().rev() {
1078 let mut new_parents = IndexSet::new();
1081 for old_parent in commit.parent_ids() {
1082 if target_commit_ids.contains(old_parent) {
1083 new_parents.insert(old_parent.clone());
1084 } else if let Some(parents) = internal_parents.get(old_parent) {
1085 new_parents.extend(parents.iter().cloned());
1086 }
1087 }
1088 internal_parents.insert(commit.id().clone(), new_parents);
1089 }
1090 internal_parents
1091}
1092
1093fn compute_commits_heads(
1099 target_commit_ids: &IndexSet<CommitId>,
1100 connected_target_commits: &[Commit],
1101) -> Vec<CommitId> {
1102 let mut target_head_ids: HashSet<CommitId> = HashSet::new();
1103 for commit in connected_target_commits.iter().rev() {
1104 target_head_ids.insert(commit.id().clone());
1105 for old_parent in commit.parent_ids() {
1106 target_head_ids.remove(old_parent);
1107 }
1108 }
1109 connected_target_commits
1110 .iter()
1111 .rev()
1112 .filter(|commit| {
1113 target_head_ids.contains(commit.id()) && target_commit_ids.contains(commit.id())
1114 })
1115 .map(|commit| commit.id().clone())
1116 .collect_vec()
1117}
1118
1119pub struct CommitWithSelection {
1120 pub commit: Commit,
1121 pub selected_tree: MergedTree,
1122 pub parent_tree: MergedTree,
1123}
1124
1125impl CommitWithSelection {
1126 pub fn is_full_selection(&self) -> bool {
1128 &self.selected_tree.id() == self.commit.tree_id()
1129 }
1130
1131 pub fn is_empty_selection(&self) -> bool {
1137 self.selected_tree.id() == self.parent_tree.id()
1138 }
1139}
1140
1141#[must_use]
1143pub struct SquashedCommit<'repo> {
1144 pub commit_builder: CommitBuilder<'repo>,
1146 pub abandoned_commits: Vec<Commit>,
1148}
1149
1150pub fn squash_commits<'repo>(
1154 repo: &'repo mut MutableRepo,
1155 sources: &[CommitWithSelection],
1156 destination: &Commit,
1157 keep_emptied: bool,
1158) -> BackendResult<Option<SquashedCommit<'repo>>> {
1159 struct SourceCommit<'a> {
1160 commit: &'a CommitWithSelection,
1161 abandon: bool,
1162 }
1163 let mut source_commits = vec![];
1164 for source in sources {
1165 let abandon = !keep_emptied && source.is_full_selection();
1166 if !abandon && source.is_empty_selection() {
1167 continue;
1171 }
1172
1173 source_commits.push(SourceCommit {
1176 commit: source,
1177 abandon,
1178 });
1179 }
1180
1181 if source_commits.is_empty() {
1182 return Ok(None);
1183 }
1184
1185 let mut abandoned_commits = vec![];
1186 for source in &source_commits {
1187 if source.abandon {
1188 repo.record_abandoned_commit(&source.commit.commit);
1189 abandoned_commits.push(source.commit.commit.clone());
1190 } else {
1191 let source_tree = source.commit.commit.tree()?;
1192 let new_source_tree = source_tree
1194 .merge(
1195 source.commit.selected_tree.clone(),
1196 source.commit.parent_tree.clone(),
1197 )
1198 .block_on()?;
1199 repo.rewrite_commit(&source.commit.commit)
1200 .set_tree_id(new_source_tree.id().clone())
1201 .write()?;
1202 }
1203 }
1204
1205 let mut rewritten_destination = destination.clone();
1206 if sources.iter().any(|source| {
1207 repo.index()
1208 .is_ancestor(source.commit.id(), destination.id())
1209 }) {
1210 let options = RebaseOptions::default();
1215 repo.rebase_descendants_with_options(&options, |old_commit, rebased_commit| {
1216 if old_commit.id() != destination.id() {
1217 return;
1218 }
1219 rewritten_destination = match rebased_commit {
1220 RebasedCommit::Rewritten(commit) => commit,
1221 RebasedCommit::Abandoned { .. } => panic!("all commits should be kept"),
1222 };
1223 })?;
1224 }
1225 let mut destination_tree = rewritten_destination.tree()?;
1227 for source in &source_commits {
1228 destination_tree = destination_tree
1229 .merge(
1230 source.commit.parent_tree.clone(),
1231 source.commit.selected_tree.clone(),
1232 )
1233 .block_on()?;
1234 }
1235 let mut predecessors = vec![destination.id().clone()];
1236 predecessors.extend(
1237 source_commits
1238 .iter()
1239 .map(|source| source.commit.commit.id().clone()),
1240 );
1241
1242 let commit_builder = repo
1243 .rewrite_commit(&rewritten_destination)
1244 .set_tree_id(destination_tree.id().clone())
1245 .set_predecessors(predecessors);
1246 Ok(Some(SquashedCommit {
1247 commit_builder,
1248 abandoned_commits,
1249 }))
1250}
1251
1252pub fn find_duplicate_divergent_commits(
1256 repo: &dyn Repo,
1257 new_parent_ids: &[CommitId],
1258 target: &MoveCommitsTarget,
1259) -> BackendResult<Vec<Commit>> {
1260 let target_commits: Vec<Commit> = match target {
1261 MoveCommitsTarget::Commits(commit_ids) => commit_ids
1262 .iter()
1263 .map(|commit_id| repo.store().get_commit(commit_id))
1264 .try_collect()?,
1265 MoveCommitsTarget::Roots(root_ids) => RevsetExpression::commits(root_ids.clone())
1266 .descendants()
1267 .evaluate(repo)
1268 .map_err(|err| err.into_backend_error())?
1269 .iter()
1270 .commits(repo.store())
1271 .try_collect()
1272 .map_err(|err| err.into_backend_error())?,
1273 };
1274 let target_commit_ids: HashSet<&CommitId> = target_commits.iter().map(Commit::id).collect();
1275
1276 let divergent_changes: Vec<(&Commit, Vec<CommitId>)> = target_commits
1279 .iter()
1280 .map(|target_commit| {
1281 let mut ancestor_candidates = repo
1282 .resolve_change_id(target_commit.change_id())
1283 .unwrap_or_default();
1284 ancestor_candidates.retain(|commit_id| !target_commit_ids.contains(commit_id));
1285 (target_commit, ancestor_candidates)
1286 })
1287 .filter(|(_, candidates)| !candidates.is_empty())
1288 .collect();
1289 if divergent_changes.is_empty() {
1290 return Ok(Vec::new());
1291 }
1292
1293 let target_root_ids = match target {
1294 MoveCommitsTarget::Commits(commit_ids) => commit_ids,
1295 MoveCommitsTarget::Roots(root_ids) => root_ids,
1296 };
1297
1298 let is_new_ancestor = RevsetExpression::commits(target_root_ids.clone())
1301 .range(&RevsetExpression::commits(new_parent_ids.to_owned()))
1302 .evaluate(repo)
1303 .map_err(|err| err.into_backend_error())?
1304 .containing_fn();
1305
1306 let mut duplicate_divergent = Vec::new();
1307 for (target_commit, ancestor_candidates) in divergent_changes {
1312 for ancestor_candidate_id in ancestor_candidates {
1313 if !is_new_ancestor(&ancestor_candidate_id).map_err(|err| err.into_backend_error())? {
1314 continue;
1315 }
1316
1317 let ancestor_candidate = repo.store().get_commit(&ancestor_candidate_id)?;
1318 let new_tree =
1319 rebase_to_dest_parent(repo, slice::from_ref(target_commit), &ancestor_candidate)?;
1320 if new_tree.id() == *ancestor_candidate.tree_id() {
1323 duplicate_divergent.push(target_commit.clone());
1324 break;
1325 }
1326 }
1327 }
1328 Ok(duplicate_divergent)
1329}