1#![allow(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, _destination_value) = values?;
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);
274 let new_base_tree_fut = merge_commit_trees(self.mut_repo, &new_parents);
275 let old_tree_fut = self.old_commit.tree_async();
276 let (old_base_tree, new_base_tree, old_tree) =
277 try_join!(old_base_tree_fut, new_base_tree_fut, old_tree_fut)?;
278 (
279 old_base_tree.id() == *self.old_commit.tree_id(),
280 new_base_tree.merge(old_base_tree, old_tree).await?.id(),
281 )
282 };
283 if let [parent] = &new_parents[..] {
286 let should_abandon = match empty {
287 EmptyBehavior::Keep => false,
288 EmptyBehavior::AbandonNewlyEmpty => *parent.tree_id() == new_tree_id && !was_empty,
289 EmptyBehavior::AbandonAllEmpty => *parent.tree_id() == new_tree_id,
290 };
291 if should_abandon {
292 self.abandon();
293 return Ok(None);
294 }
295 }
296
297 let builder = self
298 .mut_repo
299 .rewrite_commit(&self.old_commit)
300 .set_parents(self.new_parents)
301 .set_tree_id(new_tree_id);
302 Ok(Some(builder))
303 }
304
305 pub async fn rebase(self) -> BackendResult<CommitBuilder<'repo>> {
308 let builder = self.rebase_with_empty_behavior(EmptyBehavior::Keep).await?;
309 Ok(builder.unwrap())
310 }
311
312 pub fn reparent(self) -> CommitBuilder<'repo> {
315 self.mut_repo
316 .rewrite_commit(&self.old_commit)
317 .set_parents(self.new_parents)
318 }
319}
320
321pub enum RebasedCommit {
322 Rewritten(Commit),
323 Abandoned { parent_id: CommitId },
324}
325
326pub fn rebase_commit_with_options(
327 mut rewriter: CommitRewriter<'_>,
328 options: &RebaseOptions,
329) -> BackendResult<RebasedCommit> {
330 if options.simplify_ancestor_merge {
332 rewriter
335 .simplify_ancestor_merge()
336 .map_err(|err| BackendError::Other(err.into()))?;
337 }
338
339 let single_parent = match &rewriter.new_parents[..] {
340 [parent_id] => Some(parent_id.clone()),
341 _ => None,
342 };
343 let new_parents_len = rewriter.new_parents.len();
344 if let Some(builder) = rewriter
345 .rebase_with_empty_behavior(options.empty)
346 .block_on()?
347 {
348 let new_commit = builder.write()?;
349 Ok(RebasedCommit::Rewritten(new_commit))
350 } else {
351 assert_eq!(new_parents_len, 1);
352 Ok(RebasedCommit::Abandoned {
353 parent_id: single_parent.unwrap(),
354 })
355 }
356}
357
358pub fn rebase_to_dest_parent(
360 repo: &dyn Repo,
361 sources: &[Commit],
362 destination: &Commit,
363) -> BackendResult<MergedTree> {
364 if let [source] = sources {
365 if source.parent_ids() == destination.parent_ids() {
366 return source.tree();
367 }
368 }
369 sources.iter().try_fold(
370 destination.parent_tree(repo)?,
371 |destination_tree, source| {
372 let source_parent_tree = source.parent_tree(repo)?;
373 let source_tree = source.tree()?;
374 destination_tree
375 .merge(source_parent_tree, source_tree)
376 .block_on()
377 },
378 )
379}
380
381#[derive(Clone, Copy, Default, PartialEq, Eq, Debug)]
382pub enum EmptyBehavior {
383 #[default]
385 Keep,
386 AbandonNewlyEmpty,
390 AbandonAllEmpty,
394}
395
396#[derive(Clone, Debug, Default)]
403pub struct RebaseOptions {
404 pub empty: EmptyBehavior,
405 pub rewrite_refs: RewriteRefsOptions,
406 pub simplify_ancestor_merge: bool,
409}
410
411#[derive(Clone, Debug, Default)]
413pub struct RewriteRefsOptions {
414 pub delete_abandoned_bookmarks: bool,
419}
420
421pub struct MoveCommitsStats {
422 pub num_rebased_targets: u32,
424 pub num_rebased_descendants: u32,
426 pub num_skipped_rebases: u32,
429 pub num_abandoned_empty: u32,
431 pub rebased_commits: HashMap<CommitId, RebasedCommit>,
433}
434
435#[derive(Clone, Debug)]
437pub struct MoveCommitsLocation {
438 pub new_parent_ids: Vec<CommitId>,
439 pub new_child_ids: Vec<CommitId>,
440 pub target: MoveCommitsTarget,
441}
442
443#[derive(Clone, Debug)]
444pub enum MoveCommitsTarget {
445 Commits(Vec<CommitId>),
448 Roots(Vec<CommitId>),
450}
451
452#[derive(Clone, Debug)]
453pub struct ComputedMoveCommits {
454 target_commit_ids: IndexSet<CommitId>,
455 descendants: Vec<Commit>,
456 commit_new_parents_map: HashMap<CommitId, Vec<CommitId>>,
457 to_abandon: HashSet<CommitId>,
458}
459
460impl ComputedMoveCommits {
461 fn empty() -> Self {
462 Self {
463 target_commit_ids: IndexSet::new(),
464 descendants: vec![],
465 commit_new_parents_map: HashMap::new(),
466 to_abandon: HashSet::new(),
467 }
468 }
469
470 pub fn record_to_abandon(&mut self, commit_ids: impl IntoIterator<Item = CommitId>) {
477 self.to_abandon.extend(commit_ids);
478 }
479
480 pub fn apply(
481 self,
482 mut_repo: &mut MutableRepo,
483 options: &RebaseOptions,
484 ) -> BackendResult<MoveCommitsStats> {
485 apply_move_commits(mut_repo, self, options)
486 }
487}
488
489pub fn move_commits(
498 mut_repo: &mut MutableRepo,
499 loc: &MoveCommitsLocation,
500 options: &RebaseOptions,
501) -> BackendResult<MoveCommitsStats> {
502 compute_move_commits(mut_repo, loc)?.apply(mut_repo, options)
503}
504
505pub fn compute_move_commits(
506 repo: &MutableRepo,
507 loc: &MoveCommitsLocation,
508) -> BackendResult<ComputedMoveCommits> {
509 let target_commit_ids: IndexSet<CommitId>;
510 let connected_target_commits: Vec<Commit>;
511 let connected_target_commits_internal_parents: HashMap<CommitId, IndexSet<CommitId>>;
512 let target_roots: HashSet<CommitId>;
513
514 match &loc.target {
515 MoveCommitsTarget::Commits(commit_ids) => {
516 if commit_ids.is_empty() {
517 return Ok(ComputedMoveCommits::empty());
518 }
519
520 target_commit_ids = commit_ids.iter().cloned().collect();
521
522 connected_target_commits = RevsetExpression::commits(commit_ids.clone())
523 .connected()
524 .evaluate(repo)
525 .map_err(|err| err.into_backend_error())?
526 .iter()
527 .commits(repo.store())
528 .try_collect()
529 .map_err(|err| err.into_backend_error())?;
530 connected_target_commits_internal_parents =
531 compute_internal_parents_within(&target_commit_ids, &connected_target_commits);
532
533 target_roots = connected_target_commits_internal_parents
534 .iter()
535 .filter(|&(commit_id, parents)| {
536 target_commit_ids.contains(commit_id) && parents.is_empty()
537 })
538 .map(|(commit_id, _)| commit_id.clone())
539 .collect();
540 }
541 MoveCommitsTarget::Roots(root_ids) => {
542 if root_ids.is_empty() {
543 return Ok(ComputedMoveCommits::empty());
544 }
545
546 target_commit_ids = RevsetExpression::commits(root_ids.clone())
547 .descendants()
548 .evaluate(repo)
549 .map_err(|err| err.into_backend_error())?
550 .iter()
551 .try_collect()
552 .map_err(|err| err.into_backend_error())?;
553
554 connected_target_commits = target_commit_ids
555 .iter()
556 .map(|id| repo.store().get_commit(id))
557 .try_collect()?;
558 connected_target_commits_internal_parents = HashMap::new();
561 target_roots = root_ids.iter().cloned().collect();
562 }
563 }
564
565 let mut target_commits_external_parents: HashMap<CommitId, IndexSet<CommitId>> = HashMap::new();
569 for id in target_commit_ids.iter().rev() {
570 let commit = repo.store().get_commit(id)?;
571 let mut new_parents = IndexSet::new();
572 for old_parent in commit.parent_ids() {
573 if let Some(parents) = target_commits_external_parents.get(old_parent) {
574 new_parents.extend(parents.iter().cloned());
575 } else {
576 new_parents.insert(old_parent.clone());
577 }
578 }
579 target_commits_external_parents.insert(commit.id().clone(), new_parents);
580 }
581
582 let new_parent_ids: Vec<_> = loc
586 .new_parent_ids
587 .iter()
588 .flat_map(|parent_id| {
589 if let Some(parent_ids) = target_commits_external_parents.get(parent_id) {
590 parent_ids.iter().cloned().collect_vec()
591 } else {
592 vec![parent_id.clone()]
593 }
594 })
595 .collect();
596
597 let new_children: Vec<_> = if loc
601 .new_child_ids
602 .iter()
603 .any(|id| target_commit_ids.contains(id))
604 {
605 let target_commits_descendants: Vec<_> =
606 RevsetExpression::commits(target_commit_ids.iter().cloned().collect_vec())
607 .union(
608 &RevsetExpression::commits(target_commit_ids.iter().cloned().collect_vec())
609 .children(),
610 )
611 .evaluate(repo)
612 .map_err(|err| err.into_backend_error())?
613 .iter()
614 .commits(repo.store())
615 .try_collect()
616 .map_err(|err| err.into_backend_error())?;
617
618 let mut target_commit_external_descendants: HashMap<CommitId, IndexSet<Commit>> =
621 HashMap::new();
622 for commit in &target_commits_descendants {
625 if !target_commit_external_descendants.contains_key(commit.id()) {
626 let children = if target_commit_ids.contains(commit.id()) {
627 IndexSet::new()
628 } else {
629 IndexSet::from([commit.clone()])
630 };
631 target_commit_external_descendants.insert(commit.id().clone(), children);
632 }
633
634 let children = target_commit_external_descendants
635 .get(commit.id())
636 .unwrap()
637 .iter()
638 .cloned()
639 .collect_vec();
640 for parent_id in commit.parent_ids() {
641 if target_commit_ids.contains(parent_id) {
642 if let Some(target_children) =
643 target_commit_external_descendants.get_mut(parent_id)
644 {
645 target_children.extend(children.iter().cloned());
646 } else {
647 target_commit_external_descendants
648 .insert(parent_id.clone(), children.iter().cloned().collect());
649 }
650 };
651 }
652 }
653
654 let mut new_children = Vec::new();
655 for id in &loc.new_child_ids {
656 if let Some(children) = target_commit_external_descendants.get(id) {
657 new_children.extend(children.iter().cloned());
658 } else {
659 new_children.push(repo.store().get_commit(id)?);
660 }
661 }
662 new_children
663 } else {
664 loc.new_child_ids
665 .iter()
666 .map(|id| repo.store().get_commit(id))
667 .try_collect()?
668 };
669
670 let new_children_parents: HashMap<_, _> = if !new_children.is_empty() {
673 let target_heads = compute_commits_heads(&target_commit_ids, &connected_target_commits);
676
677 new_children
678 .iter()
679 .map(|child_commit| {
680 let mut new_child_parent_ids = IndexSet::new();
681 for old_child_parent_id in child_commit.parent_ids() {
682 let old_child_parent_ids = if let Some(parents) =
684 target_commits_external_parents.get(old_child_parent_id)
685 {
686 parents.iter().collect_vec()
687 } else {
688 vec![old_child_parent_id]
689 };
690
691 for id in old_child_parent_ids {
695 if new_parent_ids.contains(id) {
696 new_child_parent_ids.extend(target_heads.clone());
697 } else {
698 new_child_parent_ids.insert(id.clone());
699 };
700 }
701 }
702
703 new_child_parent_ids.extend(target_heads.clone());
706
707 (
708 child_commit.id().clone(),
709 new_child_parent_ids.into_iter().collect_vec(),
710 )
711 })
712 .collect()
713 } else {
714 HashMap::new()
715 };
716
717 let mut roots = target_roots.iter().cloned().collect_vec();
720 roots.extend(new_children.iter().ids().cloned());
721
722 let descendants = repo.find_descendants_for_rebase(roots.clone())?;
723 let commit_new_parents_map = descendants
724 .iter()
725 .map(|commit| {
726 let commit_id = commit.id();
727 let new_parent_ids =
728
729 if let Some(new_child_parents) = new_children_parents.get(commit_id) {
731 new_child_parents.clone()
732 }
733 else if target_commit_ids.contains(commit_id) {
735 if target_roots.contains(commit_id) {
737 new_parent_ids.clone()
738 }
739 else {
747 let mut new_parents = vec![];
748 for parent_id in commit.parent_ids() {
749 if target_commit_ids.contains(parent_id) {
750 new_parents.push(parent_id.clone());
751 } else if let Some(parents) =
752 connected_target_commits_internal_parents.get(parent_id) {
753 new_parents.extend(parents.iter().cloned());
754 } else if !new_children.iter().any(|new_child| {
755 repo.index().is_ancestor(new_child.id(), parent_id) }) {
756 new_parents.push(parent_id.clone());
757 }
758 }
759 new_parents
760 }
761 }
762 else if commit
765 .parent_ids()
766 .iter()
767 .any(|id| target_commits_external_parents.contains_key(id))
768 {
769 let mut new_parents = vec![];
770 for parent in commit.parent_ids() {
771 if let Some(parents) = target_commits_external_parents.get(parent) {
772 new_parents.extend(parents.iter().cloned());
773 } else {
774 new_parents.push(parent.clone());
775 }
776 }
777 new_parents
778 } else {
779 commit.parent_ids().iter().cloned().collect_vec()
780 };
781 (commit.id().clone(), new_parent_ids)
782 })
783 .collect();
784
785 Ok(ComputedMoveCommits {
786 target_commit_ids,
787 descendants,
788 commit_new_parents_map,
789 to_abandon: HashSet::new(),
790 })
791}
792
793fn apply_move_commits(
794 mut_repo: &mut MutableRepo,
795 commits: ComputedMoveCommits,
796 options: &RebaseOptions,
797) -> BackendResult<MoveCommitsStats> {
798 let mut num_rebased_targets = 0;
799 let mut num_rebased_descendants = 0;
800 let mut num_skipped_rebases = 0;
801 let mut num_abandoned_empty = 0;
802
803 let rebase_descendant_options = &RebaseOptions {
805 empty: EmptyBehavior::Keep,
806 rewrite_refs: options.rewrite_refs.clone(),
807 simplify_ancestor_merge: options.simplify_ancestor_merge,
808 };
809
810 let mut rebased_commits: HashMap<CommitId, RebasedCommit> = HashMap::new();
811 mut_repo.transform_commits(
812 commits.descendants,
813 &commits.commit_new_parents_map,
814 &options.rewrite_refs,
815 async |rewriter| {
816 let old_commit_id = rewriter.old_commit().id().clone();
817 if commits.to_abandon.contains(&old_commit_id) {
818 rewriter.abandon();
819 } else if rewriter.parents_changed() {
820 let is_target_commit = commits.target_commit_ids.contains(&old_commit_id);
821 let rebased_commit = rebase_commit_with_options(
822 rewriter,
823 if is_target_commit {
824 options
825 } else {
826 rebase_descendant_options
827 },
828 )?;
829 if let RebasedCommit::Abandoned { .. } = rebased_commit {
830 num_abandoned_empty += 1;
831 } else if is_target_commit {
832 num_rebased_targets += 1;
833 } else {
834 num_rebased_descendants += 1;
835 }
836 rebased_commits.insert(old_commit_id, rebased_commit);
837 } else {
838 num_skipped_rebases += 1;
839 }
840
841 Ok(())
842 },
843 )?;
844
845 Ok(MoveCommitsStats {
846 num_rebased_targets,
847 num_rebased_descendants,
848 num_skipped_rebases,
849 num_abandoned_empty,
850 rebased_commits,
851 })
852}
853
854#[derive(Default)]
855pub struct DuplicateCommitsStats {
856 pub duplicated_commits: IndexMap<CommitId, Commit>,
858 pub num_rebased: u32,
861}
862
863pub async fn duplicate_commits(
881 mut_repo: &mut MutableRepo,
882 target_commit_ids: &[CommitId],
883 target_descriptions: &HashMap<CommitId, String>,
884 parent_commit_ids: &[CommitId],
885 children_commit_ids: &[CommitId],
886) -> BackendResult<DuplicateCommitsStats> {
887 if target_commit_ids.is_empty() {
888 return Ok(DuplicateCommitsStats::default());
889 }
890
891 let mut duplicated_old_to_new: IndexMap<CommitId, Commit> = IndexMap::new();
892 let mut num_rebased = 0;
893
894 let target_commit_ids: IndexSet<_> = target_commit_ids.iter().cloned().collect();
895
896 let connected_target_commits: Vec<_> =
897 RevsetExpression::commits(target_commit_ids.iter().cloned().collect_vec())
898 .connected()
899 .evaluate(mut_repo)
900 .map_err(|err| err.into_backend_error())?
901 .iter()
902 .commits(mut_repo.store())
903 .try_collect()
904 .map_err(|err| err.into_backend_error())?;
905
906 let target_commits_internal_parents = {
913 let mut target_commits_internal_parents =
914 compute_internal_parents_within(&target_commit_ids, &connected_target_commits);
915 target_commits_internal_parents.retain(|id, _| target_commit_ids.contains(id));
916 target_commits_internal_parents
917 };
918
919 let target_root_ids: HashSet<_> = target_commits_internal_parents
921 .iter()
922 .filter(|(_, parents)| parents.is_empty())
923 .map(|(commit_id, _)| commit_id.clone())
924 .collect();
925
926 let target_head_ids = if !children_commit_ids.is_empty() {
929 compute_commits_heads(&target_commit_ids, &connected_target_commits)
930 } else {
931 vec![]
932 };
933
934 for original_commit_id in target_commit_ids.iter().rev() {
937 let original_commit = mut_repo
938 .store()
939 .get_commit_async(original_commit_id)
940 .await?;
941 let new_parent_ids = if target_root_ids.contains(original_commit_id) {
942 parent_commit_ids.to_vec()
943 } else {
944 target_commits_internal_parents
945 .get(original_commit_id)
946 .unwrap()
947 .iter()
948 .map(|id| {
950 duplicated_old_to_new
951 .get(id)
952 .map_or(id, |commit| commit.id())
953 .clone()
954 })
955 .collect()
956 };
957 let mut new_commit_builder = CommitRewriter::new(mut_repo, original_commit, new_parent_ids)
958 .rebase()
959 .await?
960 .clear_rewrite_source()
961 .generate_new_change_id();
962 if let Some(desc) = target_descriptions.get(original_commit_id) {
963 new_commit_builder = new_commit_builder.set_description(desc);
964 }
965 duplicated_old_to_new.insert(original_commit_id.clone(), new_commit_builder.write()?);
966 }
967
968 let target_head_ids = target_head_ids
971 .into_iter()
972 .map(|commit_id| {
973 duplicated_old_to_new
974 .get(&commit_id)
975 .map_or(commit_id, |commit| commit.id().clone())
976 })
977 .collect_vec();
978
979 let children_commit_ids_set: HashSet<CommitId> = children_commit_ids.iter().cloned().collect();
981 mut_repo.transform_descendants(children_commit_ids.to_vec(), async |mut rewriter| {
982 if children_commit_ids_set.contains(rewriter.old_commit().id()) {
983 let mut child_new_parent_ids = IndexSet::new();
984 for old_parent_id in rewriter.old_commit().parent_ids() {
985 if parent_commit_ids.contains(old_parent_id) {
990 child_new_parent_ids.extend(target_head_ids.clone());
991 } else {
992 child_new_parent_ids.insert(old_parent_id.clone());
993 }
994 }
995 child_new_parent_ids.extend(target_head_ids.clone());
998 rewriter.set_new_parents(child_new_parent_ids.into_iter().collect());
999 }
1000 num_rebased += 1;
1001 rewriter.rebase().await?.write()?;
1002 Ok(())
1003 })?;
1004
1005 Ok(DuplicateCommitsStats {
1006 duplicated_commits: duplicated_old_to_new,
1007 num_rebased,
1008 })
1009}
1010
1011pub fn duplicate_commits_onto_parents(
1021 mut_repo: &mut MutableRepo,
1022 target_commits: &[CommitId],
1023 target_descriptions: &HashMap<CommitId, String>,
1024) -> BackendResult<DuplicateCommitsStats> {
1025 if target_commits.is_empty() {
1026 return Ok(DuplicateCommitsStats::default());
1027 }
1028
1029 let mut duplicated_old_to_new: IndexMap<CommitId, Commit> = IndexMap::new();
1030
1031 for original_commit_id in target_commits.iter().rev() {
1034 let original_commit = mut_repo.store().get_commit(original_commit_id)?;
1035 let new_parent_ids = original_commit
1036 .parent_ids()
1037 .iter()
1038 .map(|id| {
1039 duplicated_old_to_new
1040 .get(id)
1041 .map_or(id, |commit| commit.id())
1042 .clone()
1043 })
1044 .collect();
1045 let mut new_commit_builder = mut_repo
1046 .rewrite_commit(&original_commit)
1047 .clear_rewrite_source()
1048 .generate_new_change_id()
1049 .set_parents(new_parent_ids);
1050 if let Some(desc) = target_descriptions.get(original_commit_id) {
1051 new_commit_builder = new_commit_builder.set_description(desc);
1052 }
1053 duplicated_old_to_new.insert(original_commit_id.clone(), new_commit_builder.write()?);
1054 }
1055
1056 Ok(DuplicateCommitsStats {
1057 duplicated_commits: duplicated_old_to_new,
1058 num_rebased: 0,
1059 })
1060}
1061
1062fn compute_internal_parents_within(
1070 target_commit_ids: &IndexSet<CommitId>,
1071 graph_commits: &[Commit],
1072) -> HashMap<CommitId, IndexSet<CommitId>> {
1073 let mut internal_parents: HashMap<CommitId, IndexSet<CommitId>> = HashMap::new();
1074 for commit in graph_commits.iter().rev() {
1075 let mut new_parents = IndexSet::new();
1078 for old_parent in commit.parent_ids() {
1079 if target_commit_ids.contains(old_parent) {
1080 new_parents.insert(old_parent.clone());
1081 } else if let Some(parents) = internal_parents.get(old_parent) {
1082 new_parents.extend(parents.iter().cloned());
1083 }
1084 }
1085 internal_parents.insert(commit.id().clone(), new_parents);
1086 }
1087 internal_parents
1088}
1089
1090fn compute_commits_heads(
1096 target_commit_ids: &IndexSet<CommitId>,
1097 connected_target_commits: &[Commit],
1098) -> Vec<CommitId> {
1099 let mut target_head_ids: HashSet<CommitId> = HashSet::new();
1100 for commit in connected_target_commits.iter().rev() {
1101 target_head_ids.insert(commit.id().clone());
1102 for old_parent in commit.parent_ids() {
1103 target_head_ids.remove(old_parent);
1104 }
1105 }
1106 connected_target_commits
1107 .iter()
1108 .rev()
1109 .filter(|commit| {
1110 target_head_ids.contains(commit.id()) && target_commit_ids.contains(commit.id())
1111 })
1112 .map(|commit| commit.id().clone())
1113 .collect_vec()
1114}
1115
1116pub struct CommitWithSelection {
1117 pub commit: Commit,
1118 pub selected_tree: MergedTree,
1119 pub parent_tree: MergedTree,
1120}
1121
1122impl CommitWithSelection {
1123 pub fn is_full_selection(&self) -> bool {
1125 &self.selected_tree.id() == self.commit.tree_id()
1126 }
1127
1128 pub fn is_empty_selection(&self) -> bool {
1134 self.selected_tree.id() == self.parent_tree.id()
1135 }
1136}
1137
1138#[must_use]
1140pub struct SquashedCommit<'repo> {
1141 pub commit_builder: CommitBuilder<'repo>,
1143 pub abandoned_commits: Vec<Commit>,
1145}
1146
1147pub fn squash_commits<'repo>(
1151 repo: &'repo mut MutableRepo,
1152 sources: &[CommitWithSelection],
1153 destination: &Commit,
1154 keep_emptied: bool,
1155) -> BackendResult<Option<SquashedCommit<'repo>>> {
1156 struct SourceCommit<'a> {
1157 commit: &'a CommitWithSelection,
1158 abandon: bool,
1159 }
1160 let mut source_commits = vec![];
1161 for source in sources {
1162 let abandon = !keep_emptied && source.is_full_selection();
1163 if !abandon && source.is_empty_selection() {
1164 continue;
1168 }
1169
1170 source_commits.push(SourceCommit {
1173 commit: source,
1174 abandon,
1175 });
1176 }
1177
1178 if source_commits.is_empty() {
1179 return Ok(None);
1180 }
1181
1182 let mut abandoned_commits = vec![];
1183 for source in &source_commits {
1184 if source.abandon {
1185 repo.record_abandoned_commit(&source.commit.commit);
1186 abandoned_commits.push(source.commit.commit.clone());
1187 } else {
1188 let source_tree = source.commit.commit.tree()?;
1189 let new_source_tree = source_tree
1191 .merge(
1192 source.commit.selected_tree.clone(),
1193 source.commit.parent_tree.clone(),
1194 )
1195 .block_on()?;
1196 repo.rewrite_commit(&source.commit.commit)
1197 .set_tree_id(new_source_tree.id().clone())
1198 .write()?;
1199 }
1200 }
1201
1202 let mut rewritten_destination = destination.clone();
1203 if sources.iter().any(|source| {
1204 repo.index()
1205 .is_ancestor(source.commit.id(), destination.id())
1206 }) {
1207 let options = RebaseOptions::default();
1212 repo.rebase_descendants_with_options(&options, |old_commit, rebased_commit| {
1213 if old_commit.id() != destination.id() {
1214 return;
1215 }
1216 rewritten_destination = match rebased_commit {
1217 RebasedCommit::Rewritten(commit) => commit,
1218 RebasedCommit::Abandoned { .. } => panic!("all commits should be kept"),
1219 };
1220 })?;
1221 }
1222 let mut destination_tree = rewritten_destination.tree()?;
1224 for source in &source_commits {
1225 destination_tree = destination_tree
1226 .merge(
1227 source.commit.parent_tree.clone(),
1228 source.commit.selected_tree.clone(),
1229 )
1230 .block_on()?;
1231 }
1232 let mut predecessors = vec![destination.id().clone()];
1233 predecessors.extend(
1234 source_commits
1235 .iter()
1236 .map(|source| source.commit.commit.id().clone()),
1237 );
1238
1239 let commit_builder = repo
1240 .rewrite_commit(&rewritten_destination)
1241 .set_tree_id(destination_tree.id().clone())
1242 .set_predecessors(predecessors);
1243 Ok(Some(SquashedCommit {
1244 commit_builder,
1245 abandoned_commits,
1246 }))
1247}
1248
1249pub fn find_duplicate_divergent_commits(
1253 repo: &dyn Repo,
1254 new_parent_ids: &[CommitId],
1255 target: &MoveCommitsTarget,
1256) -> BackendResult<Vec<Commit>> {
1257 let target_commits: Vec<Commit> = match target {
1258 MoveCommitsTarget::Commits(commit_ids) => commit_ids
1259 .iter()
1260 .map(|commit_id| repo.store().get_commit(commit_id))
1261 .try_collect()?,
1262 MoveCommitsTarget::Roots(root_ids) => RevsetExpression::commits(root_ids.clone())
1263 .descendants()
1264 .evaluate(repo)
1265 .map_err(|err| err.into_backend_error())?
1266 .iter()
1267 .commits(repo.store())
1268 .try_collect()
1269 .map_err(|err| err.into_backend_error())?,
1270 };
1271 let target_commit_ids: HashSet<&CommitId> = target_commits.iter().map(Commit::id).collect();
1272
1273 let divergent_changes: Vec<(&Commit, Vec<CommitId>)> = target_commits
1276 .iter()
1277 .map(|target_commit| {
1278 let mut ancestor_candidates = repo
1279 .resolve_change_id(target_commit.change_id())
1280 .unwrap_or_default();
1281 ancestor_candidates.retain(|commit_id| !target_commit_ids.contains(commit_id));
1282 (target_commit, ancestor_candidates)
1283 })
1284 .filter(|(_, candidates)| !candidates.is_empty())
1285 .collect();
1286 if divergent_changes.is_empty() {
1287 return Ok(Vec::new());
1288 }
1289
1290 let target_root_ids = match target {
1291 MoveCommitsTarget::Commits(commit_ids) => commit_ids,
1292 MoveCommitsTarget::Roots(root_ids) => root_ids,
1293 };
1294
1295 let is_new_ancestor = RevsetExpression::commits(target_root_ids.clone())
1298 .range(&RevsetExpression::commits(new_parent_ids.to_owned()))
1299 .evaluate(repo)
1300 .map_err(|err| err.into_backend_error())?
1301 .containing_fn();
1302
1303 let mut duplicate_divergent = Vec::new();
1304 for (target_commit, ancestor_candidates) in divergent_changes {
1309 for ancestor_candidate_id in ancestor_candidates {
1310 if !is_new_ancestor(&ancestor_candidate_id).map_err(|err| err.into_backend_error())? {
1311 continue;
1312 }
1313
1314 let ancestor_candidate = repo.store().get_commit(&ancestor_candidate_id)?;
1315 let new_tree =
1316 rebase_to_dest_parent(repo, slice::from_ref(target_commit), &ancestor_candidate)?;
1317 if new_tree.id() == *ancestor_candidate.tree_id() {
1320 duplicate_divergent.push(target_commit.clone());
1321 break;
1322 }
1323 }
1324 }
1325 Ok(duplicate_divergent)
1326}