1#![allow(missing_docs)]
16
17use std::collections::HashMap;
18use std::collections::HashSet;
19use std::sync::Arc;
20
21use futures::StreamExt as _;
22use indexmap::IndexMap;
23use indexmap::IndexSet;
24use itertools::Itertools as _;
25use pollster::FutureExt as _;
26use tracing::instrument;
27
28use crate::backend::BackendError;
29use crate::backend::BackendResult;
30use crate::backend::CommitId;
31use crate::backend::MergedTreeId;
32use crate::commit::Commit;
33use crate::commit::CommitIteratorExt as _;
34use crate::commit_builder::CommitBuilder;
35use crate::dag_walk;
36use crate::index::Index;
37use crate::index::IndexError;
38use crate::matchers::Matcher;
39use crate::matchers::Visit;
40use crate::merged_tree::MergedTree;
41use crate::merged_tree::MergedTreeBuilder;
42use crate::merged_tree::TreeDiffEntry;
43use crate::repo::MutableRepo;
44use crate::repo::Repo;
45use crate::repo_path::RepoPath;
46use crate::revset::RevsetExpression;
47use crate::revset::RevsetIteratorExt as _;
48use crate::store::Store;
49
50#[instrument(skip(repo))]
52pub fn merge_commit_trees(repo: &dyn Repo, commits: &[Commit]) -> BackendResult<MergedTree> {
53 if let [commit] = commits {
54 commit.tree()
55 } else {
56 merge_commit_trees_no_resolve_without_repo(repo.store(), repo.index(), commits)?.resolve()
57 }
58}
59
60#[instrument(skip(index))]
62pub fn merge_commit_trees_no_resolve_without_repo(
63 store: &Arc<Store>,
64 index: &dyn Index,
65 commits: &[Commit],
66) -> BackendResult<MergedTree> {
67 if commits.is_empty() {
68 Ok(store.get_root_tree(&store.empty_merged_tree_id())?)
69 } else {
70 let mut new_tree = commits[0].tree()?;
71 let commit_ids = commits
72 .iter()
73 .map(|commit| commit.id().clone())
74 .collect_vec();
75 for (i, other_commit) in commits.iter().enumerate().skip(1) {
76 let ancestor_ids = index.common_ancestors(&commit_ids[0..i], &commit_ids[i..][..1]);
77 let ancestors: Vec<_> = ancestor_ids
78 .iter()
79 .map(|id| store.get_commit(id))
80 .try_collect()?;
81 let ancestor_tree =
82 merge_commit_trees_no_resolve_without_repo(store, index, &ancestors)?;
83 let other_tree = other_commit.tree()?;
84 new_tree = new_tree.merge_no_resolve(&ancestor_tree, &other_tree);
85 }
86 Ok(new_tree)
87 }
88}
89
90pub fn restore_tree(
92 source: &MergedTree,
93 destination: &MergedTree,
94 matcher: &dyn Matcher,
95) -> BackendResult<MergedTreeId> {
96 if matcher.visit(RepoPath::root()) == Visit::AllRecursively {
97 Ok(source.id())
99 } else {
100 let mut tree_builder = MergedTreeBuilder::new(destination.id().clone());
103 async {
104 let mut diff_stream = source.diff_stream(destination, matcher);
106 while let Some(TreeDiffEntry {
107 path: repo_path,
108 values,
109 }) = diff_stream.next().await
110 {
111 let (source_value, _destination_value) = values?;
112 tree_builder.set_or_remove(repo_path, source_value);
113 }
114 Ok::<(), BackendError>(())
115 }
116 .block_on()?;
117 tree_builder.write_tree(destination.store())
118 }
119}
120
121pub fn rebase_commit(
122 mut_repo: &mut MutableRepo,
123 old_commit: Commit,
124 new_parents: Vec<CommitId>,
125) -> BackendResult<Commit> {
126 let rewriter = CommitRewriter::new(mut_repo, old_commit, new_parents);
127 let builder = rewriter.rebase()?;
128 builder.write()
129}
130
131pub struct CommitRewriter<'repo> {
133 mut_repo: &'repo mut MutableRepo,
134 old_commit: Commit,
135 new_parents: Vec<CommitId>,
136}
137
138impl<'repo> CommitRewriter<'repo> {
139 pub fn new(
141 mut_repo: &'repo mut MutableRepo,
142 old_commit: Commit,
143 new_parents: Vec<CommitId>,
144 ) -> Self {
145 Self {
146 mut_repo,
147 old_commit,
148 new_parents,
149 }
150 }
151
152 pub fn mut_repo(&mut self) -> &mut MutableRepo {
154 self.mut_repo
155 }
156
157 pub fn old_commit(&self) -> &Commit {
159 &self.old_commit
160 }
161
162 pub fn new_parents(&self) -> &[CommitId] {
164 &self.new_parents
165 }
166
167 pub fn set_new_parents(&mut self, new_parents: Vec<CommitId>) {
169 self.new_parents = new_parents;
170 }
171
172 pub fn set_new_rewritten_parents(&mut self, unrewritten_parents: &[CommitId]) {
175 self.new_parents = self.mut_repo.new_parents(unrewritten_parents);
176 }
177
178 pub fn replace_parent<'a>(
181 &mut self,
182 old_parent: &CommitId,
183 new_parents: impl IntoIterator<Item = &'a CommitId>,
184 ) {
185 if let Some(i) = self.new_parents.iter().position(|p| p == old_parent) {
186 self.new_parents
187 .splice(i..i + 1, new_parents.into_iter().cloned());
188 let mut unique = HashSet::new();
189 self.new_parents.retain(|p| unique.insert(p.clone()));
190 }
191 }
192
193 pub fn parents_changed(&self) -> bool {
196 self.new_parents != self.old_commit.parent_ids()
197 }
198
199 pub fn simplify_ancestor_merge(&mut self) -> Result<(), IndexError> {
202 let head_set: HashSet<_> = self
203 .mut_repo
204 .index()
205 .heads(&mut self.new_parents.iter())?
206 .into_iter()
207 .collect();
208 self.new_parents.retain(|parent| head_set.contains(parent));
209 Ok(())
210 }
211
212 pub fn abandon(self) {
216 let old_commit_id = self.old_commit.id().clone();
217 let new_parents = self.new_parents;
218 self.mut_repo
219 .record_abandoned_commit_with_parents(old_commit_id, new_parents);
220 }
221
222 pub fn rebase_with_empty_behavior(
225 self,
226 empty: EmptyBehaviour,
227 ) -> BackendResult<Option<CommitBuilder<'repo>>> {
228 let old_parents: Vec<_> = self.old_commit.parents().try_collect()?;
229 let old_parent_trees = old_parents
230 .iter()
231 .map(|parent| parent.tree_id().clone())
232 .collect_vec();
233 let new_parents: Vec<_> = self
234 .new_parents
235 .iter()
236 .map(|new_parent_id| self.mut_repo.store().get_commit(new_parent_id))
237 .try_collect()?;
238 let new_parent_trees = new_parents
239 .iter()
240 .map(|parent| parent.tree_id().clone())
241 .collect_vec();
242
243 let (was_empty, new_tree_id) = if new_parent_trees == old_parent_trees {
244 (
245 true,
248 self.old_commit.tree_id().clone(),
250 )
251 } else {
252 let old_base_tree = merge_commit_trees(self.mut_repo, &old_parents)?;
253 let new_base_tree = merge_commit_trees(self.mut_repo, &new_parents)?;
254 let old_tree = self.old_commit.tree()?;
255 (
256 old_base_tree.id() == *self.old_commit.tree_id(),
257 new_base_tree.merge(&old_base_tree, &old_tree)?.id(),
258 )
259 };
260 if let [parent] = &new_parents[..] {
263 let should_abandon = match empty {
264 EmptyBehaviour::Keep => false,
265 EmptyBehaviour::AbandonNewlyEmpty => *parent.tree_id() == new_tree_id && !was_empty,
266 EmptyBehaviour::AbandonAllEmpty => *parent.tree_id() == new_tree_id,
267 };
268 if should_abandon {
269 self.abandon();
270 return Ok(None);
271 }
272 }
273
274 let builder = self
275 .mut_repo
276 .rewrite_commit(&self.old_commit)
277 .set_parents(self.new_parents)
278 .set_tree_id(new_tree_id);
279 Ok(Some(builder))
280 }
281
282 pub fn rebase(self) -> BackendResult<CommitBuilder<'repo>> {
285 let builder = self.rebase_with_empty_behavior(EmptyBehaviour::Keep)?;
286 Ok(builder.unwrap())
287 }
288
289 pub fn reparent(self) -> CommitBuilder<'repo> {
292 self.mut_repo
293 .rewrite_commit(&self.old_commit)
294 .set_parents(self.new_parents)
295 }
296}
297
298pub enum RebasedCommit {
299 Rewritten(Commit),
300 Abandoned { parent_id: CommitId },
301}
302
303pub fn rebase_commit_with_options(
304 mut rewriter: CommitRewriter<'_>,
305 options: &RebaseOptions,
306) -> BackendResult<RebasedCommit> {
307 if options.simplify_ancestor_merge {
309 rewriter
312 .simplify_ancestor_merge()
313 .map_err(|err| BackendError::Other(err.into()))?;
314 }
315
316 let single_parent = match &rewriter.new_parents[..] {
317 [parent_id] => Some(parent_id.clone()),
318 _ => None,
319 };
320 let new_parents_len = rewriter.new_parents.len();
321 if let Some(builder) = rewriter.rebase_with_empty_behavior(options.empty)? {
322 let new_commit = builder.write()?;
323 Ok(RebasedCommit::Rewritten(new_commit))
324 } else {
325 assert_eq!(new_parents_len, 1);
326 Ok(RebasedCommit::Abandoned {
327 parent_id: single_parent.unwrap(),
328 })
329 }
330}
331
332pub fn rebase_to_dest_parent(
334 repo: &dyn Repo,
335 sources: &[Commit],
336 destination: &Commit,
337) -> BackendResult<MergedTree> {
338 if let [source] = sources {
339 if source.parent_ids() == destination.parent_ids() {
340 return source.tree();
341 }
342 }
343 sources.iter().try_fold(
344 destination.parent_tree(repo)?,
345 |destination_tree, source| {
346 let source_parent_tree = source.parent_tree(repo)?;
347 let source_tree = source.tree()?;
348 destination_tree.merge(&source_parent_tree, &source_tree)
349 },
350 )
351}
352
353#[derive(Clone, Copy, Default, PartialEq, Eq, Debug)]
354pub enum EmptyBehaviour {
355 #[default]
357 Keep,
358 AbandonNewlyEmpty,
362 AbandonAllEmpty,
366}
367
368#[derive(Clone, Debug, Default)]
375pub struct RebaseOptions {
376 pub empty: EmptyBehaviour,
377 pub rewrite_refs: RewriteRefsOptions,
378 pub simplify_ancestor_merge: bool,
381}
382
383#[derive(Clone, Debug, Default)]
385pub struct RewriteRefsOptions {
386 pub delete_abandoned_bookmarks: bool,
391}
392
393#[derive(Default)]
394pub struct MoveCommitsStats {
395 pub num_rebased_targets: u32,
397 pub num_rebased_descendants: u32,
399 pub num_skipped_rebases: u32,
402 pub num_abandoned: u32,
404}
405
406pub enum MoveCommitsTarget {
407 Commits(Vec<Commit>),
410 Roots(Vec<Commit>),
412}
413
414pub fn move_commits(
423 mut_repo: &mut MutableRepo,
424 new_parent_ids: &[CommitId],
425 new_children: &[Commit],
426 target: &MoveCommitsTarget,
427 options: &RebaseOptions,
428) -> BackendResult<MoveCommitsStats> {
429 let target_commits: Vec<Commit>;
430 let target_commit_ids: HashSet<_>;
431 let connected_target_commits: Vec<Commit>;
432 let connected_target_commits_internal_parents: HashMap<CommitId, Vec<CommitId>>;
433 let target_roots: HashSet<CommitId>;
434
435 match target {
436 MoveCommitsTarget::Commits(commits) => {
437 if commits.is_empty() {
438 return Ok(MoveCommitsStats::default());
439 }
440
441 target_commits = commits.clone();
442 target_commit_ids = target_commits.iter().ids().cloned().collect();
443
444 connected_target_commits =
445 RevsetExpression::commits(target_commits.iter().ids().cloned().collect_vec())
446 .connected()
447 .evaluate(mut_repo)
448 .map_err(|err| err.expect_backend_error())?
449 .iter()
450 .commits(mut_repo.store())
451 .try_collect()
452 .map_err(|err| err.expect_backend_error())?;
454 connected_target_commits_internal_parents =
455 compute_internal_parents_within(&target_commit_ids, &connected_target_commits);
456
457 target_roots = connected_target_commits_internal_parents
458 .iter()
459 .filter(|(commit_id, parents)| {
460 target_commit_ids.contains(commit_id) && parents.is_empty()
461 })
462 .map(|(commit_id, _)| commit_id.clone())
463 .collect();
464 }
465 MoveCommitsTarget::Roots(roots) => {
466 if roots.is_empty() {
467 return Ok(MoveCommitsStats::default());
468 }
469
470 target_commits = RevsetExpression::commits(roots.iter().ids().cloned().collect_vec())
471 .descendants()
472 .evaluate(mut_repo)
473 .map_err(|err| err.expect_backend_error())?
474 .iter()
475 .commits(mut_repo.store())
476 .try_collect()
477 .map_err(|err| err.expect_backend_error())?;
479 target_commit_ids = target_commits.iter().ids().cloned().collect();
480
481 connected_target_commits = target_commits.iter().cloned().collect_vec();
482 connected_target_commits_internal_parents = HashMap::new();
485 target_roots = roots.iter().ids().cloned().collect();
486 }
487 }
488
489 let mut target_commits_external_parents: HashMap<CommitId, IndexSet<CommitId>> = HashMap::new();
493 for commit in target_commits.iter().rev() {
494 let mut new_parents = IndexSet::new();
495 for old_parent in commit.parent_ids() {
496 if let Some(parents) = target_commits_external_parents.get(old_parent) {
497 new_parents.extend(parents.iter().cloned());
498 } else {
499 new_parents.insert(old_parent.clone());
500 }
501 }
502 target_commits_external_parents.insert(commit.id().clone(), new_parents);
503 }
504
505 let new_parent_ids: Vec<_> = new_parent_ids
509 .iter()
510 .flat_map(|parent_id| {
511 if let Some(parent_ids) = target_commits_external_parents.get(parent_id) {
512 parent_ids.iter().cloned().collect_vec()
513 } else {
514 vec![parent_id.clone()]
515 }
516 })
517 .collect();
518
519 let new_children: Vec<_> = if new_children
523 .iter()
524 .any(|child| target_commit_ids.contains(child.id()))
525 {
526 let target_commits_descendants: Vec<_> =
527 RevsetExpression::commits(target_commit_ids.iter().cloned().collect_vec())
528 .union(
529 &RevsetExpression::commits(target_commit_ids.iter().cloned().collect_vec())
530 .children(),
531 )
532 .evaluate(mut_repo)
533 .map_err(|err| err.expect_backend_error())?
534 .iter()
535 .commits(mut_repo.store())
536 .try_collect()
537 .map_err(|err| err.expect_backend_error())?;
539
540 let mut target_commit_external_descendants: HashMap<CommitId, IndexSet<Commit>> =
543 HashMap::new();
544 for commit in &target_commits_descendants {
547 if !target_commit_external_descendants.contains_key(commit.id()) {
548 let children = if target_commit_ids.contains(commit.id()) {
549 IndexSet::new()
550 } else {
551 IndexSet::from([commit.clone()])
552 };
553 target_commit_external_descendants.insert(commit.id().clone(), children);
554 }
555
556 let children = target_commit_external_descendants
557 .get(commit.id())
558 .unwrap()
559 .iter()
560 .cloned()
561 .collect_vec();
562 for parent_id in commit.parent_ids() {
563 if target_commit_ids.contains(parent_id) {
564 if let Some(target_children) =
565 target_commit_external_descendants.get_mut(parent_id)
566 {
567 target_children.extend(children.iter().cloned());
568 } else {
569 target_commit_external_descendants
570 .insert(parent_id.clone(), children.iter().cloned().collect());
571 }
572 };
573 }
574 }
575
576 new_children
577 .iter()
578 .flat_map(|child| {
579 if let Some(children) = target_commit_external_descendants.get(child.id()) {
580 children.iter().cloned().collect_vec()
581 } else {
582 vec![child.clone()]
583 }
584 })
585 .collect()
586 } else {
587 new_children.to_vec()
588 };
589
590 let new_children_parents: HashMap<_, _> = if !new_children.is_empty() {
593 let target_heads = compute_commits_heads(&target_commit_ids, &connected_target_commits);
596
597 new_children
598 .iter()
599 .map(|child_commit| {
600 let mut new_child_parent_ids = IndexSet::new();
601 for old_child_parent_id in child_commit.parent_ids() {
602 let old_child_parent_ids = if let Some(parents) =
604 target_commits_external_parents.get(old_child_parent_id)
605 {
606 parents.iter().collect_vec()
607 } else {
608 vec![old_child_parent_id]
609 };
610
611 for id in old_child_parent_ids {
615 if new_parent_ids.contains(id) {
616 new_child_parent_ids.extend(target_heads.clone());
617 } else {
618 new_child_parent_ids.insert(id.clone());
619 };
620 }
621 }
622
623 new_child_parent_ids.extend(target_heads.clone());
626
627 (
628 child_commit.id().clone(),
629 new_child_parent_ids.into_iter().collect_vec(),
630 )
631 })
632 .collect()
633 } else {
634 HashMap::new()
635 };
636
637 let mut roots = target_roots.iter().cloned().collect_vec();
640 roots.extend(new_children.iter().ids().cloned());
641 let to_visit_expression = RevsetExpression::commits(roots).descendants();
642 let to_visit: Vec<_> = to_visit_expression
643 .evaluate(mut_repo)
644 .map_err(|err| err.expect_backend_error())?
645 .iter()
646 .commits(mut_repo.store())
647 .try_collect()
648 .map_err(|err| err.expect_backend_error())?;
650 let to_visit_commits: IndexMap<_, _> = to_visit
651 .into_iter()
652 .map(|commit| (commit.id().clone(), commit))
653 .collect();
654
655 let to_visit_commits_new_parents: HashMap<_, _> = to_visit_commits
656 .iter()
657 .map(|(commit_id, commit)| {
658 let new_parents =
659 if let Some(new_child_parents) = new_children_parents.get(commit_id) {
661 new_child_parents.clone()
662 }
663 else if target_commit_ids.contains(commit_id) {
665 if target_roots.contains(commit_id) {
667 new_parent_ids.clone()
668 }
669 else {
677 let mut new_parents = vec![];
678 for parent_id in commit.parent_ids() {
679 if target_commit_ids.contains(parent_id) {
680 new_parents.push(parent_id.clone());
681 } else if let Some(parents) =
682 connected_target_commits_internal_parents.get(parent_id) {
683 new_parents.extend(parents.iter().cloned());
684 } else if !new_children.iter().any(|new_child| {
685 mut_repo.index().is_ancestor(new_child.id(), parent_id) }) {
686 new_parents.push(parent_id.clone());
687 }
688 }
689 new_parents
690 }
691 }
692 else if commit
695 .parent_ids()
696 .iter()
697 .any(|id| target_commits_external_parents.contains_key(id))
698 {
699 let mut new_parents = vec![];
700 for parent in commit.parent_ids() {
701 if let Some(parents) = target_commits_external_parents.get(parent) {
702 new_parents.extend(parents.iter().cloned());
703 } else {
704 new_parents.push(parent.clone());
705 }
706 }
707 new_parents
708 } else {
709 commit.parent_ids().iter().cloned().collect_vec()
710 };
711
712 (commit_id.clone(), new_parents)
713 })
714 .collect();
715
716 let mut visited: HashSet<CommitId> = HashSet::new();
719 let mut to_visit = dag_walk::topo_order_reverse(
720 to_visit_commits.keys().cloned().collect_vec(),
721 |commit_id| commit_id.clone(),
722 |commit_id| -> Vec<CommitId> {
723 visited.insert(commit_id.clone());
724 to_visit_commits_new_parents
725 .get(commit_id)
726 .cloned()
727 .unwrap()
728 .iter()
729 .filter(|&id| to_visit_commits.contains_key(id) && !visited.contains(id))
732 .cloned()
733 .collect()
734 },
735 );
736
737 let mut num_rebased_targets = 0;
738 let mut num_rebased_descendants = 0;
739 let mut num_skipped_rebases = 0;
740 let mut num_abandoned = 0;
741
742 let rebase_descendant_options = &RebaseOptions {
744 empty: EmptyBehaviour::Keep,
745 rewrite_refs: options.rewrite_refs.clone(),
746 simplify_ancestor_merge: options.simplify_ancestor_merge,
747 };
748
749 while let Some(old_commit_id) = to_visit.pop() {
752 let old_commit = to_visit_commits.get(&old_commit_id).unwrap();
753 let parent_ids = to_visit_commits_new_parents.get(&old_commit_id).unwrap();
754 let new_parent_ids = mut_repo.new_parents(parent_ids);
755 let rewriter = CommitRewriter::new(mut_repo, old_commit.clone(), new_parent_ids);
756 if rewriter.parents_changed() {
757 let is_target_commit = target_commit_ids.contains(&old_commit_id);
758 let rebased_commit = rebase_commit_with_options(
759 rewriter,
760 if is_target_commit {
761 options
762 } else {
763 rebase_descendant_options
764 },
765 )?;
766 if let RebasedCommit::Abandoned { .. } = rebased_commit {
767 num_abandoned += 1;
768 } else if is_target_commit {
769 num_rebased_targets += 1;
770 } else {
771 num_rebased_descendants += 1;
772 }
773 } else {
774 num_skipped_rebases += 1;
775 }
776 }
777 mut_repo.update_rewritten_references(&options.rewrite_refs)?;
778
779 Ok(MoveCommitsStats {
780 num_rebased_targets,
781 num_rebased_descendants,
782 num_skipped_rebases,
783 num_abandoned,
784 })
785}
786
787#[derive(Default)]
788pub struct DuplicateCommitsStats {
789 pub duplicated_commits: IndexMap<CommitId, Commit>,
791 pub num_rebased: u32,
794}
795
796pub fn duplicate_commits(
809 mut_repo: &mut MutableRepo,
810 target_commits: &[CommitId],
811 parent_commit_ids: &[CommitId],
812 children_commit_ids: &[CommitId],
813) -> BackendResult<DuplicateCommitsStats> {
814 if target_commits.is_empty() {
815 return Ok(DuplicateCommitsStats::default());
816 }
817
818 let mut duplicated_old_to_new: IndexMap<CommitId, Commit> = IndexMap::new();
819 let mut num_rebased = 0;
820
821 let target_commit_ids: HashSet<_> = target_commits.iter().cloned().collect();
822
823 let connected_target_commits: Vec<_> =
824 RevsetExpression::commits(target_commit_ids.iter().cloned().collect_vec())
825 .connected()
826 .evaluate(mut_repo)
827 .map_err(|err| err.expect_backend_error())?
828 .iter()
829 .commits(mut_repo.store())
830 .try_collect()
831 .map_err(|err| err.expect_backend_error())?;
833
834 let target_commits_internal_parents = {
841 let mut target_commits_internal_parents =
842 compute_internal_parents_within(&target_commit_ids, &connected_target_commits);
843 target_commits_internal_parents.retain(|id, _| target_commit_ids.contains(id));
844 target_commits_internal_parents
845 };
846
847 let target_root_ids: HashSet<_> = target_commits_internal_parents
849 .iter()
850 .filter(|(_, parents)| parents.is_empty())
851 .map(|(commit_id, _)| commit_id.clone())
852 .collect();
853
854 let target_head_ids = if !children_commit_ids.is_empty() {
857 compute_commits_heads(&target_commit_ids, &connected_target_commits)
858 } else {
859 vec![]
860 };
861
862 for original_commit_id in target_commits.iter().rev() {
865 let original_commit = mut_repo.store().get_commit(original_commit_id)?;
866 let new_parent_ids = if target_root_ids.contains(original_commit_id) {
867 parent_commit_ids.to_vec()
868 } else {
869 target_commits_internal_parents
870 .get(original_commit_id)
871 .unwrap()
872 .iter()
873 .map(|id| {
875 duplicated_old_to_new
876 .get(id)
877 .map_or(id, |commit| commit.id())
878 .clone()
879 })
880 .collect()
881 };
882 let new_commit = CommitRewriter::new(mut_repo, original_commit, new_parent_ids)
883 .rebase()?
884 .generate_new_change_id()
885 .write()?;
886 duplicated_old_to_new.insert(original_commit_id.clone(), new_commit);
887 }
888
889 let target_head_ids = target_head_ids
892 .into_iter()
893 .map(|commit_id| {
894 duplicated_old_to_new
895 .get(&commit_id)
896 .map_or(commit_id, |commit| commit.id().clone())
897 })
898 .collect_vec();
899
900 let children_commit_ids_set: HashSet<CommitId> = children_commit_ids.iter().cloned().collect();
902 mut_repo.transform_descendants(children_commit_ids.to_vec(), |mut rewriter| {
903 if children_commit_ids_set.contains(rewriter.old_commit().id()) {
904 let mut child_new_parent_ids = IndexSet::new();
905 for old_parent_id in rewriter.old_commit().parent_ids() {
906 if parent_commit_ids.contains(old_parent_id) {
911 child_new_parent_ids.extend(target_head_ids.clone());
912 } else {
913 child_new_parent_ids.insert(old_parent_id.clone());
914 }
915 }
916 child_new_parent_ids.extend(target_head_ids.clone());
919 rewriter.set_new_parents(child_new_parent_ids.into_iter().collect());
920 }
921 num_rebased += 1;
922 rewriter.rebase()?.write()?;
923 Ok(())
924 })?;
925
926 Ok(DuplicateCommitsStats {
927 duplicated_commits: duplicated_old_to_new,
928 num_rebased,
929 })
930}
931
932pub fn duplicate_commits_onto_parents(
938 mut_repo: &mut MutableRepo,
939 target_commits: &[CommitId],
940) -> BackendResult<DuplicateCommitsStats> {
941 if target_commits.is_empty() {
942 return Ok(DuplicateCommitsStats::default());
943 }
944
945 let mut duplicated_old_to_new: IndexMap<CommitId, Commit> = IndexMap::new();
946
947 for original_commit_id in target_commits.iter().rev() {
950 let original_commit = mut_repo.store().get_commit(original_commit_id)?;
951 let new_parent_ids = original_commit
952 .parent_ids()
953 .iter()
954 .map(|id| {
955 duplicated_old_to_new
956 .get(id)
957 .map_or(id, |commit| commit.id())
958 .clone()
959 })
960 .collect();
961 let new_commit = mut_repo
962 .rewrite_commit(&original_commit)
963 .generate_new_change_id()
964 .set_parents(new_parent_ids)
965 .write()?;
966 duplicated_old_to_new.insert(original_commit_id.clone(), new_commit);
967 }
968
969 Ok(DuplicateCommitsStats {
970 duplicated_commits: duplicated_old_to_new,
971 num_rebased: 0,
972 })
973}
974
975fn compute_internal_parents_within(
983 target_commit_ids: &HashSet<CommitId>,
984 graph_commits: &[Commit],
985) -> HashMap<CommitId, Vec<CommitId>> {
986 let mut internal_parents: HashMap<CommitId, Vec<CommitId>> = HashMap::new();
987 for commit in graph_commits.iter().rev() {
988 let mut new_parents = vec![];
991 for old_parent in commit.parent_ids() {
992 if target_commit_ids.contains(old_parent) {
993 new_parents.push(old_parent.clone());
994 } else if let Some(parents) = internal_parents.get(old_parent) {
995 new_parents.extend(parents.iter().cloned());
996 }
997 }
998 internal_parents.insert(commit.id().clone(), new_parents);
999 }
1000 internal_parents
1001}
1002
1003fn compute_commits_heads(
1009 target_commit_ids: &HashSet<CommitId>,
1010 connected_target_commits: &[Commit],
1011) -> Vec<CommitId> {
1012 let mut target_head_ids: HashSet<CommitId> = HashSet::new();
1013 for commit in connected_target_commits.iter().rev() {
1014 target_head_ids.insert(commit.id().clone());
1015 for old_parent in commit.parent_ids() {
1016 target_head_ids.remove(old_parent);
1017 }
1018 }
1019 connected_target_commits
1020 .iter()
1021 .rev()
1022 .filter(|commit| {
1023 target_head_ids.contains(commit.id()) && target_commit_ids.contains(commit.id())
1024 })
1025 .map(|commit| commit.id().clone())
1026 .collect_vec()
1027}
1028
1029pub struct CommitWithSelection {
1030 pub commit: Commit,
1031 pub selected_tree: MergedTree,
1032 pub parent_tree: MergedTree,
1033}
1034
1035impl CommitWithSelection {
1036 pub fn is_full_selection(&self) -> bool {
1038 &self.selected_tree.id() == self.commit.tree_id()
1039 }
1040
1041 pub fn is_empty_selection(&self) -> bool {
1047 self.selected_tree.id() == self.parent_tree.id()
1048 }
1049}
1050
1051#[must_use]
1053pub struct SquashedCommit<'repo> {
1054 pub commit_builder: CommitBuilder<'repo>,
1056 pub abandoned_commits: Vec<Commit>,
1058}
1059
1060pub fn squash_commits<'repo>(
1064 repo: &'repo mut MutableRepo,
1065 sources: &[CommitWithSelection],
1066 destination: &Commit,
1067 keep_emptied: bool,
1068) -> BackendResult<Option<SquashedCommit<'repo>>> {
1069 struct SourceCommit<'a> {
1070 commit: &'a CommitWithSelection,
1071 abandon: bool,
1072 }
1073 let mut source_commits = vec![];
1074 for source in sources {
1075 let abandon = !keep_emptied && source.is_full_selection();
1076 if !abandon && source.is_empty_selection() {
1077 continue;
1081 }
1082
1083 source_commits.push(SourceCommit {
1086 commit: source,
1087 abandon,
1088 });
1089 }
1090
1091 if source_commits.is_empty() {
1092 return Ok(None);
1093 }
1094
1095 let mut abandoned_commits = vec![];
1096 for source in &source_commits {
1097 if source.abandon {
1098 repo.record_abandoned_commit(&source.commit.commit);
1099 abandoned_commits.push(source.commit.commit.clone());
1100 } else {
1101 let source_tree = source.commit.commit.tree()?;
1102 let new_source_tree =
1104 source_tree.merge(&source.commit.selected_tree, &source.commit.parent_tree)?;
1105 repo.rewrite_commit(&source.commit.commit)
1106 .set_tree_id(new_source_tree.id().clone())
1107 .write()?;
1108 }
1109 }
1110
1111 let mut rewritten_destination = destination.clone();
1112 if sources.iter().any(|source| {
1113 repo.index()
1114 .is_ancestor(source.commit.id(), destination.id())
1115 }) {
1116 let options = RebaseOptions::default();
1121 repo.rebase_descendants_with_options(&options, |old_commit, rebased_commit| {
1122 if old_commit.id() != destination.id() {
1123 return;
1124 }
1125 rewritten_destination = match rebased_commit {
1126 RebasedCommit::Rewritten(commit) => commit,
1127 RebasedCommit::Abandoned { .. } => panic!("all commits should be kept"),
1128 };
1129 })?;
1130 }
1131 let mut destination_tree = rewritten_destination.tree()?;
1133 for source in &source_commits {
1134 destination_tree =
1135 destination_tree.merge(&source.commit.parent_tree, &source.commit.selected_tree)?;
1136 }
1137 let mut predecessors = vec![destination.id().clone()];
1138 predecessors.extend(
1139 source_commits
1140 .iter()
1141 .map(|source| source.commit.commit.id().clone()),
1142 );
1143
1144 let commit_builder = repo
1145 .rewrite_commit(&rewritten_destination)
1146 .set_tree_id(destination_tree.id().clone())
1147 .set_predecessors(predecessors);
1148 Ok(Some(SquashedCommit {
1149 commit_builder,
1150 abandoned_commits,
1151 }))
1152}