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