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