jj_lib/
rewrite.rs

1// Copyright 2020 The Jujutsu Authors
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// https://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15#![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 indexmap::IndexMap;
24use indexmap::IndexSet;
25use itertools::Itertools 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::index::Index;
36use crate::index::IndexError;
37use crate::matchers::Matcher;
38use crate::matchers::Visit;
39use crate::merged_tree::MergedTree;
40use crate::merged_tree::MergedTreeBuilder;
41use crate::merged_tree::TreeDiffEntry;
42use crate::repo::MutableRepo;
43use crate::repo::Repo;
44use crate::repo_path::RepoPath;
45use crate::revset::RevsetExpression;
46use crate::revset::RevsetIteratorExt as _;
47use crate::store::Store;
48
49/// Merges `commits` and tries to resolve any conflicts recursively.
50#[instrument(skip(repo))]
51pub fn merge_commit_trees(repo: &dyn Repo, commits: &[Commit]) -> BackendResult<MergedTree> {
52    if let [commit] = commits {
53        commit.tree()
54    } else {
55        merge_commit_trees_no_resolve_without_repo(repo.store(), repo.index(), commits)?.resolve()
56    }
57}
58
59/// Merges `commits` without attempting to resolve file conflicts.
60#[instrument(skip(index))]
61pub fn merge_commit_trees_no_resolve_without_repo(
62    store: &Arc<Store>,
63    index: &dyn Index,
64    commits: &[Commit],
65) -> BackendResult<MergedTree> {
66    if commits.is_empty() {
67        Ok(store.get_root_tree(&store.empty_merged_tree_id())?)
68    } else {
69        let mut new_tree = commits[0].tree()?;
70        let commit_ids = commits
71            .iter()
72            .map(|commit| commit.id().clone())
73            .collect_vec();
74        for (i, other_commit) in commits.iter().enumerate().skip(1) {
75            let ancestor_ids = index.common_ancestors(&commit_ids[0..i], &commit_ids[i..][..1]);
76            let ancestors: Vec<_> = ancestor_ids
77                .iter()
78                .map(|id| store.get_commit(id))
79                .try_collect()?;
80            let ancestor_tree =
81                merge_commit_trees_no_resolve_without_repo(store, index, &ancestors)?;
82            let other_tree = other_commit.tree()?;
83            new_tree = new_tree.merge_no_resolve(&ancestor_tree, &other_tree);
84        }
85        Ok(new_tree)
86    }
87}
88
89/// Restore matching paths from the source into the destination.
90pub async fn restore_tree(
91    source: &MergedTree,
92    destination: &MergedTree,
93    matcher: &dyn Matcher,
94) -> BackendResult<MergedTreeId> {
95    if matcher.visit(RepoPath::root()) == Visit::AllRecursively {
96        // Optimization for a common case
97        Ok(source.id())
98    } else {
99        // TODO: We should be able to not traverse deeper in the diff if the matcher
100        // matches an entire subtree.
101        let mut tree_builder = MergedTreeBuilder::new(destination.id().clone());
102        // TODO: handle copy tracking
103        let mut diff_stream = source.diff_stream(destination, matcher);
104        while let Some(TreeDiffEntry {
105            path: repo_path,
106            values,
107        }) = diff_stream.next().await
108        {
109            let (source_value, _destination_value) = values?;
110            tree_builder.set_or_remove(repo_path, source_value);
111        }
112        tree_builder.write_tree(destination.store())
113    }
114}
115
116pub fn rebase_commit(
117    mut_repo: &mut MutableRepo,
118    old_commit: Commit,
119    new_parents: Vec<CommitId>,
120) -> BackendResult<Commit> {
121    let rewriter = CommitRewriter::new(mut_repo, old_commit, new_parents);
122    let builder = rewriter.rebase()?;
123    builder.write()
124}
125
126/// Helps rewrite a commit.
127pub struct CommitRewriter<'repo> {
128    mut_repo: &'repo mut MutableRepo,
129    old_commit: Commit,
130    new_parents: Vec<CommitId>,
131}
132
133impl<'repo> CommitRewriter<'repo> {
134    /// Create a new instance.
135    pub fn new(
136        mut_repo: &'repo mut MutableRepo,
137        old_commit: Commit,
138        new_parents: Vec<CommitId>,
139    ) -> Self {
140        Self {
141            mut_repo,
142            old_commit,
143            new_parents,
144        }
145    }
146
147    /// Returns the `MutableRepo`.
148    pub fn mut_repo(&mut self) -> &mut MutableRepo {
149        self.mut_repo
150    }
151
152    /// The commit we're rewriting.
153    pub fn old_commit(&self) -> &Commit {
154        &self.old_commit
155    }
156
157    /// Get the old commit's intended new parents.
158    pub fn new_parents(&self) -> &[CommitId] {
159        &self.new_parents
160    }
161
162    /// Set the old commit's intended new parents.
163    pub fn set_new_parents(&mut self, new_parents: Vec<CommitId>) {
164        self.new_parents = new_parents;
165    }
166
167    /// Set the old commit's intended new parents to be the rewritten versions
168    /// of the given parents.
169    pub fn set_new_rewritten_parents(&mut self, unrewritten_parents: &[CommitId]) {
170        self.new_parents = self.mut_repo.new_parents(unrewritten_parents);
171    }
172
173    /// Update the intended new parents by replacing any occurrence of
174    /// `old_parent` by `new_parents`.
175    pub fn replace_parent<'a>(
176        &mut self,
177        old_parent: &CommitId,
178        new_parents: impl IntoIterator<Item = &'a CommitId>,
179    ) {
180        if let Some(i) = self.new_parents.iter().position(|p| p == old_parent) {
181            self.new_parents
182                .splice(i..i + 1, new_parents.into_iter().cloned());
183            let mut unique = HashSet::new();
184            self.new_parents.retain(|p| unique.insert(p.clone()));
185        }
186    }
187
188    /// Checks if the intended new parents are different from the old commit's
189    /// parents.
190    pub fn parents_changed(&self) -> bool {
191        self.new_parents != self.old_commit.parent_ids()
192    }
193
194    /// If a merge commit would end up with one parent being an ancestor of the
195    /// other, then filter out the ancestor.
196    pub fn simplify_ancestor_merge(&mut self) -> Result<(), IndexError> {
197        let head_set: HashSet<_> = self
198            .mut_repo
199            .index()
200            .heads(&mut self.new_parents.iter())?
201            .into_iter()
202            .collect();
203        self.new_parents.retain(|parent| head_set.contains(parent));
204        Ok(())
205    }
206
207    /// Records the old commit as abandoned with the new parents.
208    ///
209    /// This is equivalent to `reparent(settings).abandon()`, but is cheaper.
210    pub fn abandon(self) {
211        let old_commit_id = self.old_commit.id().clone();
212        let new_parents = self.new_parents;
213        self.mut_repo
214            .record_abandoned_commit_with_parents(old_commit_id, new_parents);
215    }
216
217    /// Rebase the old commit onto the new parents. Returns a `CommitBuilder`
218    /// for the new commit. Returns `None` if the commit was abandoned.
219    pub fn rebase_with_empty_behavior(
220        self,
221        empty: EmptyBehaviour,
222    ) -> BackendResult<Option<CommitBuilder<'repo>>> {
223        let old_parents: Vec<_> = self.old_commit.parents().try_collect()?;
224        let old_parent_trees = old_parents
225            .iter()
226            .map(|parent| parent.tree_id().clone())
227            .collect_vec();
228        let new_parents: Vec<_> = self
229            .new_parents
230            .iter()
231            .map(|new_parent_id| self.mut_repo.store().get_commit(new_parent_id))
232            .try_collect()?;
233        let new_parent_trees = new_parents
234            .iter()
235            .map(|parent| parent.tree_id().clone())
236            .collect_vec();
237
238        let (was_empty, new_tree_id) = if new_parent_trees == old_parent_trees {
239            (
240                // Optimization: was_empty is only used for newly empty, but when the
241                // parents haven't changed it can't be newly empty.
242                true,
243                // Optimization: Skip merging.
244                self.old_commit.tree_id().clone(),
245            )
246        } else {
247            let old_base_tree = merge_commit_trees(self.mut_repo, &old_parents)?;
248            let new_base_tree = merge_commit_trees(self.mut_repo, &new_parents)?;
249            let old_tree = self.old_commit.tree()?;
250            (
251                old_base_tree.id() == *self.old_commit.tree_id(),
252                new_base_tree.merge(&old_base_tree, &old_tree)?.id(),
253            )
254        };
255        // Ensure we don't abandon commits with multiple parents (merge commits), even
256        // if they're empty.
257        if let [parent] = &new_parents[..] {
258            let should_abandon = match empty {
259                EmptyBehaviour::Keep => false,
260                EmptyBehaviour::AbandonNewlyEmpty => *parent.tree_id() == new_tree_id && !was_empty,
261                EmptyBehaviour::AbandonAllEmpty => *parent.tree_id() == new_tree_id,
262            };
263            if should_abandon {
264                self.abandon();
265                return Ok(None);
266            }
267        }
268
269        let builder = self
270            .mut_repo
271            .rewrite_commit(&self.old_commit)
272            .set_parents(self.new_parents)
273            .set_tree_id(new_tree_id);
274        Ok(Some(builder))
275    }
276
277    /// Rebase the old commit onto the new parents. Returns a `CommitBuilder`
278    /// for the new commit.
279    pub fn rebase(self) -> BackendResult<CommitBuilder<'repo>> {
280        let builder = self.rebase_with_empty_behavior(EmptyBehaviour::Keep)?;
281        Ok(builder.unwrap())
282    }
283
284    /// Rewrite the old commit onto the new parents without changing its
285    /// contents. Returns a `CommitBuilder` for the new commit.
286    pub fn reparent(self) -> CommitBuilder<'repo> {
287        self.mut_repo
288            .rewrite_commit(&self.old_commit)
289            .set_parents(self.new_parents)
290    }
291}
292
293pub enum RebasedCommit {
294    Rewritten(Commit),
295    Abandoned { parent_id: CommitId },
296}
297
298pub fn rebase_commit_with_options(
299    mut rewriter: CommitRewriter<'_>,
300    options: &RebaseOptions,
301) -> BackendResult<RebasedCommit> {
302    // If specified, don't create commit where one parent is an ancestor of another.
303    if options.simplify_ancestor_merge {
304        // TODO: BackendError is not the right error here because
305        // the error does not come from `Backend`, but `Index`.
306        rewriter
307            .simplify_ancestor_merge()
308            .map_err(|err| BackendError::Other(err.into()))?;
309    }
310
311    let single_parent = match &rewriter.new_parents[..] {
312        [parent_id] => Some(parent_id.clone()),
313        _ => None,
314    };
315    let new_parents_len = rewriter.new_parents.len();
316    if let Some(builder) = rewriter.rebase_with_empty_behavior(options.empty)? {
317        let new_commit = builder.write()?;
318        Ok(RebasedCommit::Rewritten(new_commit))
319    } else {
320        assert_eq!(new_parents_len, 1);
321        Ok(RebasedCommit::Abandoned {
322            parent_id: single_parent.unwrap(),
323        })
324    }
325}
326
327/// Moves changes from `sources` to the `destination` parent, returns new tree.
328pub fn rebase_to_dest_parent(
329    repo: &dyn Repo,
330    sources: &[Commit],
331    destination: &Commit,
332) -> BackendResult<MergedTree> {
333    if let [source] = sources {
334        if source.parent_ids() == destination.parent_ids() {
335            return source.tree();
336        }
337    }
338    sources.iter().try_fold(
339        destination.parent_tree(repo)?,
340        |destination_tree, source| {
341            let source_parent_tree = source.parent_tree(repo)?;
342            let source_tree = source.tree()?;
343            destination_tree.merge(&source_parent_tree, &source_tree)
344        },
345    )
346}
347
348#[derive(Clone, Copy, Default, PartialEq, Eq, Debug)]
349pub enum EmptyBehaviour {
350    /// Always keep empty commits
351    #[default]
352    Keep,
353    /// Skips commits that would be empty after the rebase, but that were not
354    /// originally empty.
355    /// Will never skip merge commits with multiple non-empty parents.
356    AbandonNewlyEmpty,
357    /// Skips all empty commits, including ones that were empty before the
358    /// rebase.
359    /// Will never skip merge commits with multiple non-empty parents.
360    AbandonAllEmpty,
361}
362
363/// Controls the configuration of a rebase.
364// If we wanted to add a flag similar to `git rebase --ignore-date`, then this
365// makes it much easier by ensuring that the only changes required are to
366// change the RebaseOptions construction in the CLI, and changing the
367// rebase_commit function to actually use the flag, and ensure we don't need to
368// plumb it in.
369#[derive(Clone, Debug, Default)]
370pub struct RebaseOptions {
371    pub empty: EmptyBehaviour,
372    pub rewrite_refs: RewriteRefsOptions,
373    /// If a merge commit would end up with one parent being an ancestor of the
374    /// other, then filter out the ancestor.
375    pub simplify_ancestor_merge: bool,
376}
377
378/// Configuration for [`MutableRepo::update_rewritten_references()`].
379#[derive(Clone, Debug, Default)]
380pub struct RewriteRefsOptions {
381    /// Whether or not delete bookmarks pointing to the abandoned commits.
382    ///
383    /// If false, bookmarks will be moved to the parents of the abandoned
384    /// commit.
385    pub delete_abandoned_bookmarks: bool,
386}
387
388pub struct MoveCommitsStats {
389    /// The number of commits in the target set which were rebased.
390    pub num_rebased_targets: u32,
391    /// The number of descendant commits which were rebased.
392    pub num_rebased_descendants: u32,
393    /// The number of commits for which rebase was skipped, due to the commit
394    /// already being in place.
395    pub num_skipped_rebases: u32,
396    /// The number of commits which were abandoned due to being empty.
397    pub num_abandoned_empty: u32,
398    /// The rebased commits
399    pub rebased_commits: HashMap<CommitId, RebasedCommit>,
400}
401
402/// Target and destination commits to be rebased by [`move_commits()`].
403#[derive(Clone, Debug)]
404pub struct MoveCommitsLocation {
405    pub new_parent_ids: Vec<CommitId>,
406    pub new_child_ids: Vec<CommitId>,
407    pub target: MoveCommitsTarget,
408}
409
410#[derive(Clone, Debug)]
411pub enum MoveCommitsTarget {
412    /// The commits to be moved. Commits should be mutable and in reverse
413    /// topological order.
414    Commits(Vec<CommitId>),
415    /// The root commits to be moved, along with all their descendants.
416    Roots(Vec<CommitId>),
417}
418
419#[derive(Clone, Debug)]
420pub struct ComputedMoveCommits {
421    target_commit_ids: IndexSet<CommitId>,
422    descendants: Vec<Commit>,
423    commit_new_parents_map: HashMap<CommitId, Vec<CommitId>>,
424    to_abandon: HashSet<CommitId>,
425}
426
427impl ComputedMoveCommits {
428    fn empty() -> Self {
429        ComputedMoveCommits {
430            target_commit_ids: IndexSet::new(),
431            descendants: vec![],
432            commit_new_parents_map: HashMap::new(),
433            to_abandon: HashSet::new(),
434        }
435    }
436
437    /// Records a set of commits to abandon while rebasing.
438    ///
439    /// Abandoning these commits while rebasing ensures that their descendants
440    /// are still rebased properly. [`MutableRepo::record_abandoned_commit`] is
441    /// similar, but it can lead to issues when abandoning a target commit
442    /// before the rebase.
443    pub fn record_to_abandon(&mut self, commit_ids: impl IntoIterator<Item = CommitId>) {
444        self.to_abandon.extend(commit_ids);
445    }
446
447    pub fn apply(
448        self,
449        mut_repo: &mut MutableRepo,
450        options: &RebaseOptions,
451    ) -> BackendResult<MoveCommitsStats> {
452        apply_move_commits(mut_repo, self, options)
453    }
454}
455
456/// Moves `loc.target` commits from their current location to a new location in
457/// the graph.
458///
459/// Commits in `target` are rebased onto the new parents given by
460/// `new_parent_ids`, while the `new_child_ids` commits are rebased onto the
461/// heads of the commits in `targets`. This assumes that commits in `target` and
462/// `new_child_ids` can be rewritten, and there will be no cycles in the
463/// resulting graph. Commits in `target` should be in reverse topological order.
464pub fn move_commits(
465    mut_repo: &mut MutableRepo,
466    loc: &MoveCommitsLocation,
467    options: &RebaseOptions,
468) -> BackendResult<MoveCommitsStats> {
469    compute_move_commits(mut_repo, loc)?.apply(mut_repo, options)
470}
471
472pub fn compute_move_commits(
473    repo: &MutableRepo,
474    loc: &MoveCommitsLocation,
475) -> BackendResult<ComputedMoveCommits> {
476    let target_commit_ids: IndexSet<CommitId>;
477    let connected_target_commits: Vec<Commit>;
478    let connected_target_commits_internal_parents: HashMap<CommitId, IndexSet<CommitId>>;
479    let target_roots: HashSet<CommitId>;
480
481    match &loc.target {
482        MoveCommitsTarget::Commits(commit_ids) => {
483            if commit_ids.is_empty() {
484                return Ok(ComputedMoveCommits::empty());
485            }
486
487            target_commit_ids = commit_ids.iter().cloned().collect();
488
489            connected_target_commits = RevsetExpression::commits(commit_ids.clone())
490                .connected()
491                .evaluate(repo)
492                .map_err(|err| err.into_backend_error())?
493                .iter()
494                .commits(repo.store())
495                .try_collect()
496                .map_err(|err| err.into_backend_error())?;
497            connected_target_commits_internal_parents =
498                compute_internal_parents_within(&target_commit_ids, &connected_target_commits);
499
500            target_roots = connected_target_commits_internal_parents
501                .iter()
502                .filter(|&(commit_id, parents)| {
503                    target_commit_ids.contains(commit_id) && parents.is_empty()
504                })
505                .map(|(commit_id, _)| commit_id.clone())
506                .collect();
507        }
508        MoveCommitsTarget::Roots(root_ids) => {
509            if root_ids.is_empty() {
510                return Ok(ComputedMoveCommits::empty());
511            }
512
513            target_commit_ids = RevsetExpression::commits(root_ids.clone())
514                .descendants()
515                .evaluate(repo)
516                .map_err(|err| err.into_backend_error())?
517                .iter()
518                .try_collect()
519                .map_err(|err| err.into_backend_error())?;
520
521            connected_target_commits = target_commit_ids
522                .iter()
523                .map(|id| repo.store().get_commit(id))
524                .try_collect()?;
525            // We don't have to compute the internal parents for the connected target set,
526            // since the connected target set is the same as the target set.
527            connected_target_commits_internal_parents = HashMap::new();
528            target_roots = root_ids.iter().cloned().collect();
529        }
530    }
531
532    // If a commit outside the target set has a commit in the target set as a
533    // parent, then - after the transformation - it should have that commit's
534    // ancestors which are not in the target set as parents.
535    let mut target_commits_external_parents: HashMap<CommitId, IndexSet<CommitId>> = HashMap::new();
536    for id in target_commit_ids.iter().rev() {
537        let commit = repo.store().get_commit(id)?;
538        let mut new_parents = IndexSet::new();
539        for old_parent in commit.parent_ids() {
540            if let Some(parents) = target_commits_external_parents.get(old_parent) {
541                new_parents.extend(parents.iter().cloned());
542            } else {
543                new_parents.insert(old_parent.clone());
544            }
545        }
546        target_commits_external_parents.insert(commit.id().clone(), new_parents);
547    }
548
549    // If the new parents include a commit in the target set, replace it with the
550    // commit's ancestors which are outside the set.
551    // e.g. `jj rebase -r A --before A`
552    let new_parent_ids: Vec<_> = loc
553        .new_parent_ids
554        .iter()
555        .flat_map(|parent_id| {
556            if let Some(parent_ids) = target_commits_external_parents.get(parent_id) {
557                parent_ids.iter().cloned().collect_vec()
558            } else {
559                vec![parent_id.clone()]
560            }
561        })
562        .collect();
563
564    // If the new children include a commit in the target set, replace it with the
565    // commit's descendants which are outside the set.
566    // e.g. `jj rebase -r A --after A`
567    let new_children: Vec<_> = if loc
568        .new_child_ids
569        .iter()
570        .any(|id| target_commit_ids.contains(id))
571    {
572        let target_commits_descendants: Vec<_> =
573            RevsetExpression::commits(target_commit_ids.iter().cloned().collect_vec())
574                .union(
575                    &RevsetExpression::commits(target_commit_ids.iter().cloned().collect_vec())
576                        .children(),
577                )
578                .evaluate(repo)
579                .map_err(|err| err.into_backend_error())?
580                .iter()
581                .commits(repo.store())
582                .try_collect()
583                .map_err(|err| err.into_backend_error())?;
584
585        // For all commits in the target set, compute its transitive descendant commits
586        // which are outside of the target set by up to 1 generation.
587        let mut target_commit_external_descendants: HashMap<CommitId, IndexSet<Commit>> =
588            HashMap::new();
589        // Iterate through all descendants of the target set, going through children
590        // before parents.
591        for commit in &target_commits_descendants {
592            if !target_commit_external_descendants.contains_key(commit.id()) {
593                let children = if target_commit_ids.contains(commit.id()) {
594                    IndexSet::new()
595                } else {
596                    IndexSet::from([commit.clone()])
597                };
598                target_commit_external_descendants.insert(commit.id().clone(), children);
599            }
600
601            let children = target_commit_external_descendants
602                .get(commit.id())
603                .unwrap()
604                .iter()
605                .cloned()
606                .collect_vec();
607            for parent_id in commit.parent_ids() {
608                if target_commit_ids.contains(parent_id) {
609                    if let Some(target_children) =
610                        target_commit_external_descendants.get_mut(parent_id)
611                    {
612                        target_children.extend(children.iter().cloned());
613                    } else {
614                        target_commit_external_descendants
615                            .insert(parent_id.clone(), children.iter().cloned().collect());
616                    }
617                };
618            }
619        }
620
621        let mut new_children = Vec::new();
622        for id in &loc.new_child_ids {
623            if let Some(children) = target_commit_external_descendants.get(id) {
624                new_children.extend(children.iter().cloned());
625            } else {
626                new_children.push(repo.store().get_commit(id)?);
627            }
628        }
629        new_children
630    } else {
631        loc.new_child_ids
632            .iter()
633            .map(|id| repo.store().get_commit(id))
634            .try_collect()?
635    };
636
637    // Compute the parents of the new children, which will include the heads of the
638    // target set.
639    let new_children_parents: HashMap<_, _> = if !new_children.is_empty() {
640        // Compute the heads of the target set, which will be used as the parents of
641        // `new_children`.
642        let target_heads = compute_commits_heads(&target_commit_ids, &connected_target_commits);
643
644        new_children
645            .iter()
646            .map(|child_commit| {
647                let mut new_child_parent_ids = IndexSet::new();
648                for old_child_parent_id in child_commit.parent_ids() {
649                    // Replace target commits with their parents outside the target set.
650                    let old_child_parent_ids = if let Some(parents) =
651                        target_commits_external_parents.get(old_child_parent_id)
652                    {
653                        parents.iter().collect_vec()
654                    } else {
655                        vec![old_child_parent_id]
656                    };
657
658                    // If the original parents of the new children are the new parents of the
659                    // `target_heads`, replace them with the target heads since we are "inserting"
660                    // the target commits in between the new parents and the new children.
661                    for id in old_child_parent_ids {
662                        if new_parent_ids.contains(id) {
663                            new_child_parent_ids.extend(target_heads.clone());
664                        } else {
665                            new_child_parent_ids.insert(id.clone());
666                        };
667                    }
668                }
669
670                // If not already present, add `target_heads` as parents of the new child
671                // commit.
672                new_child_parent_ids.extend(target_heads.clone());
673
674                (
675                    child_commit.id().clone(),
676                    new_child_parent_ids.into_iter().collect_vec(),
677                )
678            })
679            .collect()
680    } else {
681        HashMap::new()
682    };
683
684    // Compute the set of commits to visit, which includes the target commits, the
685    // new children commits (if any), and their descendants.
686    let mut roots = target_roots.iter().cloned().collect_vec();
687    roots.extend(new_children.iter().ids().cloned());
688
689    let descendants = repo.find_descendants_for_rebase(roots.clone())?;
690    let commit_new_parents_map = descendants
691        .iter()
692        .map(|commit| {
693            let commit_id = commit.id();
694            let new_parent_ids =
695
696            // New child of the rebased target commits.
697            if let Some(new_child_parents) = new_children_parents.get(commit_id) {
698                new_child_parents.clone()
699            }
700            // Commit is in the target set.
701            else if target_commit_ids.contains(commit_id) {
702                // If the commit is a root of the target set, it should be rebased onto the new destination.
703                if target_roots.contains(commit_id) {
704                    new_parent_ids.clone()
705                }
706                // Otherwise:
707                // 1. Keep parents which are within the target set.
708                // 2. Replace parents which are outside the target set but are part of the
709                //    connected target set with their ancestor commits which are in the target
710                //    set.
711                // 3. Keep other parents outside the target set if they are not descendants of the
712                //    new children of the target set.
713                else {
714                    let mut new_parents = vec![];
715                    for parent_id in commit.parent_ids() {
716                        if target_commit_ids.contains(parent_id) {
717                            new_parents.push(parent_id.clone());
718                        } else if let Some(parents) =
719                                connected_target_commits_internal_parents.get(parent_id) {
720                            new_parents.extend(parents.iter().cloned());
721                        } else if !new_children.iter().any(|new_child| {
722                                repo.index().is_ancestor(new_child.id(), parent_id) }) {
723                            new_parents.push(parent_id.clone());
724                        }
725                    }
726                   new_parents
727                }
728            }
729            // Commits outside the target set should have references to commits inside the set
730            // replaced.
731            else if commit
732                .parent_ids()
733                .iter()
734                .any(|id| target_commits_external_parents.contains_key(id))
735            {
736                let mut new_parents = vec![];
737                for parent in commit.parent_ids() {
738                    if let Some(parents) = target_commits_external_parents.get(parent) {
739                        new_parents.extend(parents.iter().cloned());
740                    } else {
741                        new_parents.push(parent.clone());
742                    }
743                }
744                new_parents
745            } else {
746                commit.parent_ids().iter().cloned().collect_vec()
747            };
748            (commit.id().clone(), new_parent_ids)
749        })
750        .collect();
751
752    Ok(ComputedMoveCommits {
753        target_commit_ids,
754        descendants,
755        commit_new_parents_map,
756        to_abandon: HashSet::new(),
757    })
758}
759
760fn apply_move_commits(
761    mut_repo: &mut MutableRepo,
762    commits: ComputedMoveCommits,
763    options: &RebaseOptions,
764) -> BackendResult<MoveCommitsStats> {
765    let mut num_rebased_targets = 0;
766    let mut num_rebased_descendants = 0;
767    let mut num_skipped_rebases = 0;
768    let mut num_abandoned_empty = 0;
769
770    // Always keep empty commits when rebasing descendants.
771    let rebase_descendant_options = &RebaseOptions {
772        empty: EmptyBehaviour::Keep,
773        rewrite_refs: options.rewrite_refs.clone(),
774        simplify_ancestor_merge: options.simplify_ancestor_merge,
775    };
776
777    let mut rebased_commits: HashMap<CommitId, RebasedCommit> = HashMap::new();
778    mut_repo.transform_commits(
779        commits.descendants,
780        &commits.commit_new_parents_map,
781        &options.rewrite_refs,
782        |rewriter| {
783            let old_commit_id = rewriter.old_commit().id().clone();
784            if commits.to_abandon.contains(&old_commit_id) {
785                rewriter.abandon();
786            } else if rewriter.parents_changed() {
787                let is_target_commit = commits.target_commit_ids.contains(&old_commit_id);
788                let rebased_commit = rebase_commit_with_options(
789                    rewriter,
790                    if is_target_commit {
791                        options
792                    } else {
793                        rebase_descendant_options
794                    },
795                )?;
796                if let RebasedCommit::Abandoned { .. } = rebased_commit {
797                    num_abandoned_empty += 1;
798                } else if is_target_commit {
799                    num_rebased_targets += 1;
800                } else {
801                    num_rebased_descendants += 1;
802                }
803                rebased_commits.insert(old_commit_id, rebased_commit);
804            } else {
805                num_skipped_rebases += 1;
806            }
807
808            Ok(())
809        },
810    )?;
811
812    Ok(MoveCommitsStats {
813        num_rebased_targets,
814        num_rebased_descendants,
815        num_skipped_rebases,
816        num_abandoned_empty,
817        rebased_commits,
818    })
819}
820
821#[derive(Default)]
822pub struct DuplicateCommitsStats {
823    /// Map of original commit ID to newly duplicated commit.
824    pub duplicated_commits: IndexMap<CommitId, Commit>,
825    /// The number of descendant commits which were rebased onto the duplicated
826    /// commits.
827    pub num_rebased: u32,
828}
829
830/// Duplicates the given `target_commit_ids` onto a new location in the graph.
831///
832/// The roots of `target_commit_ids` are duplicated on top of the new
833/// `parent_commit_ids`, whilst other commits in `target_commit_ids` are
834/// duplicated on top of the newly duplicated commits in the target set. If
835/// `children_commit_ids` is not empty, the `children_commit_ids` will be
836/// rebased onto the heads of the duplicated target commits.
837///
838/// If `target_descriptions` is not empty, it will be consulted to retrieve the
839/// new descriptions of the target commits, falling back to the original if the
840/// map does not contain an entry for a given commit.
841///
842/// This assumes that commits in `children_commit_ids` can be rewritten. There
843/// should also be no cycles in the resulting graph, i.e. `children_commit_ids`
844/// should not be ancestors of `parent_commit_ids`. Commits in
845/// `target_commit_ids` should be in reverse topological order (children before
846/// parents).
847pub fn duplicate_commits(
848    mut_repo: &mut MutableRepo,
849    target_commit_ids: &[CommitId],
850    target_descriptions: &HashMap<CommitId, String>,
851    parent_commit_ids: &[CommitId],
852    children_commit_ids: &[CommitId],
853) -> BackendResult<DuplicateCommitsStats> {
854    if target_commit_ids.is_empty() {
855        return Ok(DuplicateCommitsStats::default());
856    }
857
858    let mut duplicated_old_to_new: IndexMap<CommitId, Commit> = IndexMap::new();
859    let mut num_rebased = 0;
860
861    let target_commit_ids: IndexSet<_> = target_commit_ids.iter().cloned().collect();
862
863    let connected_target_commits: Vec<_> =
864        RevsetExpression::commits(target_commit_ids.iter().cloned().collect_vec())
865            .connected()
866            .evaluate(mut_repo)
867            .map_err(|err| err.into_backend_error())?
868            .iter()
869            .commits(mut_repo.store())
870            .try_collect()
871            .map_err(|err| err.into_backend_error())?;
872
873    // Commits in the target set should only have other commits in the set as
874    // parents, except the roots of the set, which persist their original
875    // parents.
876    // If a commit in the target set has a parent which is not in the set, but has
877    // an ancestor which is in the set, then the commit will have that ancestor
878    // as a parent instead.
879    let target_commits_internal_parents = {
880        let mut target_commits_internal_parents =
881            compute_internal_parents_within(&target_commit_ids, &connected_target_commits);
882        target_commits_internal_parents.retain(|id, _| target_commit_ids.contains(id));
883        target_commits_internal_parents
884    };
885
886    // Compute the roots of `target_commits`.
887    let target_root_ids: HashSet<_> = target_commits_internal_parents
888        .iter()
889        .filter(|(_, parents)| parents.is_empty())
890        .map(|(commit_id, _)| commit_id.clone())
891        .collect();
892
893    // Compute the heads of the target set, which will be used as the parents of
894    // the children commits.
895    let target_head_ids = if !children_commit_ids.is_empty() {
896        compute_commits_heads(&target_commit_ids, &connected_target_commits)
897    } else {
898        vec![]
899    };
900
901    // Topological order ensures that any parents of the original commit are
902    // either not in `target_commits` or were already duplicated.
903    for original_commit_id in target_commit_ids.iter().rev() {
904        let original_commit = mut_repo.store().get_commit(original_commit_id)?;
905        let new_parent_ids = if target_root_ids.contains(original_commit_id) {
906            parent_commit_ids.to_vec()
907        } else {
908            target_commits_internal_parents
909                .get(original_commit_id)
910                .unwrap()
911                .iter()
912                // Replace parent IDs with their new IDs if they were duplicated.
913                .map(|id| {
914                    duplicated_old_to_new
915                        .get(id)
916                        .map_or(id, |commit| commit.id())
917                        .clone()
918                })
919                .collect()
920        };
921        let mut new_commit_builder = CommitRewriter::new(mut_repo, original_commit, new_parent_ids)
922            .rebase()?
923            .generate_new_change_id();
924        if let Some(desc) = target_descriptions.get(original_commit_id) {
925            new_commit_builder = new_commit_builder.set_description(desc);
926        }
927        duplicated_old_to_new.insert(original_commit_id.clone(), new_commit_builder.write()?);
928    }
929
930    // Replace the original commit IDs in `target_head_ids` with the duplicated
931    // commit IDs.
932    let target_head_ids = target_head_ids
933        .into_iter()
934        .map(|commit_id| {
935            duplicated_old_to_new
936                .get(&commit_id)
937                .map_or(commit_id, |commit| commit.id().clone())
938        })
939        .collect_vec();
940
941    // Rebase new children onto the target heads.
942    let children_commit_ids_set: HashSet<CommitId> = children_commit_ids.iter().cloned().collect();
943    mut_repo.transform_descendants(children_commit_ids.to_vec(), |mut rewriter| {
944        if children_commit_ids_set.contains(rewriter.old_commit().id()) {
945            let mut child_new_parent_ids = IndexSet::new();
946            for old_parent_id in rewriter.old_commit().parent_ids() {
947                // If the original parents of the new children are the new parents of
948                // `target_head_ids`, replace them with `target_head_ids` since we are
949                // "inserting" the target commits in between the new parents and the new
950                // children.
951                if parent_commit_ids.contains(old_parent_id) {
952                    child_new_parent_ids.extend(target_head_ids.clone());
953                } else {
954                    child_new_parent_ids.insert(old_parent_id.clone());
955                }
956            }
957            // If not already present, add `target_head_ids` as parents of the new child
958            // commit.
959            child_new_parent_ids.extend(target_head_ids.clone());
960            rewriter.set_new_parents(child_new_parent_ids.into_iter().collect());
961        }
962        num_rebased += 1;
963        rewriter.rebase()?.write()?;
964        Ok(())
965    })?;
966
967    Ok(DuplicateCommitsStats {
968        duplicated_commits: duplicated_old_to_new,
969        num_rebased,
970    })
971}
972
973/// Duplicates the given `target_commits` onto their original parents or other
974/// duplicated commits.
975///
976/// Commits in `target_commits` should be in reverse topological order (children
977/// before parents).
978///
979/// If `target_descriptions` is not empty, it will be consulted to retrieve the
980/// new descriptions of the target commits, falling back to the original if
981/// the map does not contain an entry for a given commit.
982pub fn duplicate_commits_onto_parents(
983    mut_repo: &mut MutableRepo,
984    target_commits: &[CommitId],
985    target_descriptions: &HashMap<CommitId, String>,
986) -> BackendResult<DuplicateCommitsStats> {
987    if target_commits.is_empty() {
988        return Ok(DuplicateCommitsStats::default());
989    }
990
991    let mut duplicated_old_to_new: IndexMap<CommitId, Commit> = IndexMap::new();
992
993    // Topological order ensures that any parents of the original commit are
994    // either not in `target_commits` or were already duplicated.
995    for original_commit_id in target_commits.iter().rev() {
996        let original_commit = mut_repo.store().get_commit(original_commit_id)?;
997        let new_parent_ids = original_commit
998            .parent_ids()
999            .iter()
1000            .map(|id| {
1001                duplicated_old_to_new
1002                    .get(id)
1003                    .map_or(id, |commit| commit.id())
1004                    .clone()
1005            })
1006            .collect();
1007        let mut new_commit_builder = mut_repo
1008            .rewrite_commit(&original_commit)
1009            .generate_new_change_id()
1010            .set_parents(new_parent_ids);
1011        if let Some(desc) = target_descriptions.get(original_commit_id) {
1012            new_commit_builder = new_commit_builder.set_description(desc);
1013        }
1014        duplicated_old_to_new.insert(original_commit_id.clone(), new_commit_builder.write()?);
1015    }
1016
1017    Ok(DuplicateCommitsStats {
1018        duplicated_commits: duplicated_old_to_new,
1019        num_rebased: 0,
1020    })
1021}
1022
1023/// Computes the internal parents of all commits in a connected commit graph,
1024/// allowing only commits in the target set as parents.
1025///
1026/// The parents of each commit are identical to the ones found using a preorder
1027/// DFS of the node's ancestors, starting from the node itself, and avoiding
1028/// traversing an edge if the parent is in the target set. `graph_commits`
1029/// should be in reverse topological order.
1030fn compute_internal_parents_within(
1031    target_commit_ids: &IndexSet<CommitId>,
1032    graph_commits: &[Commit],
1033) -> HashMap<CommitId, IndexSet<CommitId>> {
1034    let mut internal_parents: HashMap<CommitId, IndexSet<CommitId>> = HashMap::new();
1035    for commit in graph_commits.iter().rev() {
1036        // The roots of the set will not have any parents found in `internal_parents`,
1037        // and will be stored as an empty vector.
1038        let mut new_parents = IndexSet::new();
1039        for old_parent in commit.parent_ids() {
1040            if target_commit_ids.contains(old_parent) {
1041                new_parents.insert(old_parent.clone());
1042            } else if let Some(parents) = internal_parents.get(old_parent) {
1043                new_parents.extend(parents.iter().cloned());
1044            }
1045        }
1046        internal_parents.insert(commit.id().clone(), new_parents);
1047    }
1048    internal_parents
1049}
1050
1051/// Computes the heads of commits in the target set, given the list of
1052/// `target_commit_ids` and a connected graph of commits.
1053///
1054/// `connected_target_commits` should be in reverse topological order (children
1055/// before parents).
1056fn compute_commits_heads(
1057    target_commit_ids: &IndexSet<CommitId>,
1058    connected_target_commits: &[Commit],
1059) -> Vec<CommitId> {
1060    let mut target_head_ids: HashSet<CommitId> = HashSet::new();
1061    for commit in connected_target_commits.iter().rev() {
1062        target_head_ids.insert(commit.id().clone());
1063        for old_parent in commit.parent_ids() {
1064            target_head_ids.remove(old_parent);
1065        }
1066    }
1067    connected_target_commits
1068        .iter()
1069        .rev()
1070        .filter(|commit| {
1071            target_head_ids.contains(commit.id()) && target_commit_ids.contains(commit.id())
1072        })
1073        .map(|commit| commit.id().clone())
1074        .collect_vec()
1075}
1076
1077pub struct CommitWithSelection {
1078    pub commit: Commit,
1079    pub selected_tree: MergedTree,
1080    pub parent_tree: MergedTree,
1081}
1082
1083impl CommitWithSelection {
1084    /// Returns true if the selection contains all changes in the commit.
1085    pub fn is_full_selection(&self) -> bool {
1086        &self.selected_tree.id() == self.commit.tree_id()
1087    }
1088
1089    /// Returns true if the selection matches the parent tree (contains no
1090    /// changes from the commit).
1091    ///
1092    /// Both `is_full_selection()` and `is_empty_selection()`
1093    /// can be true if the commit is itself empty.
1094    pub fn is_empty_selection(&self) -> bool {
1095        self.selected_tree.id() == self.parent_tree.id()
1096    }
1097}
1098
1099/// Resulting commit builder and stats to be returned by [`squash_commits()`].
1100#[must_use]
1101pub struct SquashedCommit<'repo> {
1102    /// New destination commit will be created by this builder.
1103    pub commit_builder: CommitBuilder<'repo>,
1104    /// List of abandoned source commits.
1105    pub abandoned_commits: Vec<Commit>,
1106}
1107
1108/// Squash `sources` into `destination` and return a [`SquashedCommit`] for the
1109/// resulting commit. Caller is responsible for setting the description and
1110/// finishing the commit.
1111pub fn squash_commits<'repo>(
1112    repo: &'repo mut MutableRepo,
1113    sources: &[CommitWithSelection],
1114    destination: &Commit,
1115    keep_emptied: bool,
1116) -> BackendResult<Option<SquashedCommit<'repo>>> {
1117    struct SourceCommit<'a> {
1118        commit: &'a CommitWithSelection,
1119        abandon: bool,
1120    }
1121    let mut source_commits = vec![];
1122    for source in sources {
1123        let abandon = !keep_emptied && source.is_full_selection();
1124        if !abandon && source.is_empty_selection() {
1125            // Nothing selected from this commit. If it's abandoned (i.e. already empty), we
1126            // still include it so `jj squash` can be used for abandoning an empty commit in
1127            // the middle of a stack.
1128            continue;
1129        }
1130
1131        // TODO: Do we want to optimize the case of moving to the parent commit (`jj
1132        // squash -r`)? The source tree will be unchanged in that case.
1133        source_commits.push(SourceCommit {
1134            commit: source,
1135            abandon,
1136        });
1137    }
1138
1139    if source_commits.is_empty() {
1140        return Ok(None);
1141    }
1142
1143    let mut abandoned_commits = vec![];
1144    for source in &source_commits {
1145        if source.abandon {
1146            repo.record_abandoned_commit(&source.commit.commit);
1147            abandoned_commits.push(source.commit.commit.clone());
1148        } else {
1149            let source_tree = source.commit.commit.tree()?;
1150            // Apply the reverse of the selected changes onto the source
1151            let new_source_tree =
1152                source_tree.merge(&source.commit.selected_tree, &source.commit.parent_tree)?;
1153            repo.rewrite_commit(&source.commit.commit)
1154                .set_tree_id(new_source_tree.id().clone())
1155                .write()?;
1156        }
1157    }
1158
1159    let mut rewritten_destination = destination.clone();
1160    if sources.iter().any(|source| {
1161        repo.index()
1162            .is_ancestor(source.commit.id(), destination.id())
1163    }) {
1164        // If we're moving changes to a descendant, first rebase descendants onto the
1165        // rewritten sources. Otherwise it will likely already have the content
1166        // changes we're moving, so applying them will have no effect and the
1167        // changes will disappear.
1168        let options = RebaseOptions::default();
1169        repo.rebase_descendants_with_options(&options, |old_commit, rebased_commit| {
1170            if old_commit.id() != destination.id() {
1171                return;
1172            }
1173            rewritten_destination = match rebased_commit {
1174                RebasedCommit::Rewritten(commit) => commit,
1175                RebasedCommit::Abandoned { .. } => panic!("all commits should be kept"),
1176            };
1177        })?;
1178    }
1179    // Apply the selected changes onto the destination
1180    let mut destination_tree = rewritten_destination.tree()?;
1181    for source in &source_commits {
1182        destination_tree =
1183            destination_tree.merge(&source.commit.parent_tree, &source.commit.selected_tree)?;
1184    }
1185    let mut predecessors = vec![destination.id().clone()];
1186    predecessors.extend(
1187        source_commits
1188            .iter()
1189            .map(|source| source.commit.commit.id().clone()),
1190    );
1191
1192    let commit_builder = repo
1193        .rewrite_commit(&rewritten_destination)
1194        .set_tree_id(destination_tree.id().clone())
1195        .set_predecessors(predecessors);
1196    Ok(Some(SquashedCommit {
1197        commit_builder,
1198        abandoned_commits,
1199    }))
1200}
1201
1202/// Find divergent commits from the target that are already present with
1203/// identical contents in the destination. These commits should be able to be
1204/// safely abandoned.
1205pub fn find_duplicate_divergent_commits(
1206    repo: &dyn Repo,
1207    new_parent_ids: &[CommitId],
1208    target: &MoveCommitsTarget,
1209) -> BackendResult<Vec<Commit>> {
1210    let target_commits: Vec<Commit> = match target {
1211        MoveCommitsTarget::Commits(commit_ids) => commit_ids
1212            .iter()
1213            .map(|commit_id| repo.store().get_commit(commit_id))
1214            .try_collect()?,
1215        MoveCommitsTarget::Roots(root_ids) => RevsetExpression::commits(root_ids.clone())
1216            .descendants()
1217            .evaluate(repo)
1218            .map_err(|err| err.into_backend_error())?
1219            .iter()
1220            .commits(repo.store())
1221            .try_collect()
1222            .map_err(|err| err.into_backend_error())?,
1223    };
1224    let target_commit_ids: HashSet<&CommitId> = target_commits.iter().map(Commit::id).collect();
1225
1226    // For each divergent change being rebased, we want to find all of the other
1227    // commits with the same change ID which are not being rebased.
1228    let divergent_changes: Vec<(&Commit, Vec<CommitId>)> = target_commits
1229        .iter()
1230        .map(|target_commit| {
1231            let mut ancestor_candidates = repo
1232                .resolve_change_id(target_commit.change_id())
1233                .unwrap_or_default();
1234            ancestor_candidates.retain(|commit_id| !target_commit_ids.contains(commit_id));
1235            (target_commit, ancestor_candidates)
1236        })
1237        .filter(|(_, candidates)| !candidates.is_empty())
1238        .collect();
1239    if divergent_changes.is_empty() {
1240        return Ok(Vec::new());
1241    }
1242
1243    let target_root_ids = match target {
1244        MoveCommitsTarget::Commits(commit_ids) => commit_ids,
1245        MoveCommitsTarget::Roots(root_ids) => root_ids,
1246    };
1247
1248    // We only care about divergent changes which are new ancestors of the rebased
1249    // commits, not ones which were already ancestors of the rebased commits.
1250    let is_new_ancestor = RevsetExpression::commits(target_root_ids.clone())
1251        .range(&RevsetExpression::commits(new_parent_ids.to_owned()))
1252        .evaluate(repo)
1253        .map_err(|err| err.into_backend_error())?
1254        .containing_fn();
1255
1256    let mut duplicate_divergent = Vec::new();
1257    // Checking every pair of commits between these two sets could be expensive if
1258    // there are several commits with the same change ID. However, it should be
1259    // uncommon to have more than a couple commits with the same change ID being
1260    // rebased at the same time, so it should be good enough in practice.
1261    for (target_commit, ancestor_candidates) in divergent_changes {
1262        for ancestor_candidate_id in ancestor_candidates {
1263            if !is_new_ancestor(&ancestor_candidate_id).map_err(|err| err.into_backend_error())? {
1264                continue;
1265            }
1266
1267            let ancestor_candidate = repo.store().get_commit(&ancestor_candidate_id)?;
1268            let new_tree =
1269                rebase_to_dest_parent(repo, slice::from_ref(target_commit), &ancestor_candidate)?;
1270            // Check whether the rebased commit would have the same tree as the existing
1271            // commit if they had the same parents. If so, we can skip this rebased commit.
1272            if new_tree.id() == *ancestor_candidate.tree_id() {
1273                duplicate_divergent.push(target_commit.clone());
1274                break;
1275            }
1276        }
1277    }
1278    Ok(duplicate_divergent)
1279}