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