146 Commits

Author SHA1 Message Date
Martin von Zweigbergk
bbe906b426 repo: merge rewrite state into single parent_mapping with enum
This simplifies the code and reduces the risk of inconsistencies in
the data.

Thanks to Yuya for the suggestion.
2024-03-30 09:35:45 -07:00
Yuya Nishihara
73b60903ce tree: flatten TreeMergeError into BackendError 2024-03-30 22:40:05 +09:00
Martin von Zweigbergk
bfa43d16f9 rewrite: don't collect set of heads to add unnecessarily 2024-03-30 05:21:48 -07:00
Martin von Zweigbergk
c40949208b rewrite: all rewritten commits are no longer heads
Now that we no longer bother to keep the set of heads to add and
remove updated while we rewrite descendants, we can simplify how we
find the set of heads to remove - it's simply all commits that have
been marked rewritten, divergent, or abandoned, i.e. the keys in
`parent_mapping`.
2024-03-30 05:21:48 -07:00
Martin von Zweigbergk
bb1fef3258 rewrite: drop redundant unioning of old commits with abandoned commits
We always add abandoned commits as key in `parent_mapping`.
2024-03-30 05:21:48 -07:00
Martin von Zweigbergk
9c382fd8c6 rewrite: exclude already rewritten commits from set to rebase
We currently include the commits in `parent_mapping` and `abandoned`
in the set of commits to visit when rebasing descendants. The reason
was that we used to update branches and working copies when we visited
these commits. Since we started updating refs after rebasing all
commits, there's no need to even visit these commits.
2024-03-26 09:50:50 -07:00
Martin von Zweigbergk
49ff818e97 rewrite: calculate branches later, remove it from state 2024-03-26 09:50:50 -07:00
Martin von Zweigbergk
718e54b01a rewrite: calculate heads_to_add later, remove it from state
Similar to the previous two commits.
2024-03-26 09:50:50 -07:00
Martin von Zweigbergk
2ee1147145 rewrite: calculate heads_to_remove later, remove it from state
Similar to the previous commit.
2024-03-26 09:50:50 -07:00
Martin von Zweigbergk
b3dd038907 rewrite: calculate new_commits later, remove it from state
We only use `new_commits` in `update_heads()`, so let's calculate it
there. It should also be more correct in case other commits were
created after we initialized `DescendantRebaser`.
2024-03-26 09:50:50 -07:00
Martin von Zweigbergk
5e7a4a2028 rewrite: update heads outside update_references()
Now that we only call `update_references()` in one place, there's no
reason to have it also update `heads_to_add` and `heads_to_remove`. By
moving it out of the function, we can consolidate the logic in one
place.
2024-03-26 09:50:50 -07:00
Martin von Zweigbergk
9511de486e rewrite: extract a function for updating heads 2024-03-26 09:50:50 -07:00
Martin von Zweigbergk
0f7a86d725 rewrite: move new_parents() to MutableRepo
The function only uses state from `MutableRepo`, so it should be
implemented on that type.
2024-03-26 09:50:50 -07:00
Martin von Zweigbergk
cfdb341c6b rewrite: make rebase_commit_with_options() mark abandoned commit
When `rebase_commit_with_options()` decides to abandons a commit, it
records the new parents in the `MutableRepo`, but it's currently the
caller's responsibility to remember to mark it as abandoned. Let's
move that logic into the function to reduce the risk of future bugs.
2024-03-26 09:50:50 -07:00
Martin von Zweigbergk
3ddf9f4329 repo: add parents of abandoned commit to parent_mapping
By adding the abandoned commit's parents to `parent_mapping`, we can
remove a bit more of the special handling of abandoned commitsin
`DescendantRebaser`.
2024-03-26 09:50:50 -07:00
Martin von Zweigbergk
0481e67dfd rewrite: drop now-unnecessary updating of branches map
Since we update all branches at the end now, we never update them in
several steps, so there are no intermediate locations we need to
remember.
2024-03-25 23:00:44 -07:00
Martin von Zweigbergk
5e8d7f8c6f rewrite: update references after rewriting all commits 2024-03-25 23:00:44 -07:00
Martin von Zweigbergk
e55ebd4fe6 rewrite: drop redundant update of parent_mapping after rebasing commit
In the normal case when we don't abandon a commit because it became
empty, then `CommitBuilder::write()` will have recorded the new commit
as a rewrite of the old commit. We don't need to do that again in
`rebase_one()`.
2024-03-25 23:00:44 -07:00
Martin von Zweigbergk
4406005dce rewrite: make DescendantRebaser use state stored in MutableRepo
A subset of the state in `DescendantRebaser` now matches exactly what
`MutableRepo` already stores, so we can avoid copying that state and
have `DescendantRebaser` use it directly instead. Having a single
source of truth for the state will enable further simplifications and
improvements.
2024-03-25 23:00:44 -07:00
Martin von Zweigbergk
ad16bec3a6 rewrite: move an assertion a little earlier
I'm going to make `DescendantRebaser` share the state about rewritten
commits with `MutableRepo` next. That means that the call to
`rebase_commit_with_options()` will update that state, which would
make this assertion fail. So let's move it a little earlier to avoid
that.
2024-03-25 23:00:44 -07:00
Martin von Zweigbergk
6e3ceb4d1c repo: store separate divergent field, pass into DescendantRebaser
With this patch, `MutableRepo` has the same tracking of rewritten
commits as `DescendantRebaser`, so we can simply pass that state into
`DescendantRebaser` when we create it. The next step is to remove the
state from `DescendantRebaser`.
2024-03-25 23:00:44 -07:00
Martin von Zweigbergk
890a8e282f repo: update working copy to first divergent commit 2024-03-25 06:53:14 -07:00
Martin von Zweigbergk
d2043f069e repo: delete record_rewritten_commit()
I don't think we have any callers left that call
`record_rewritten_commit()` multiple times within a transaction and
expect it to result in divergence. I think we should consider it a bug
to do that.
2024-03-25 06:53:14 -07:00
Martin von Zweigbergk
b54ace4954 rewrite: mark divergent commits in parent_mapping too
When rebasing descendants, we generally move branches, child commits,
the working copy to the rewritten commit(s). However, we don't move
the working copy to the new rewritten commit (s) if the old commit had
been abandoned, and we don't move child commits if the rewriten was
divergent.

This patch aims to make it clearer that there's only one mapping from
old to new parents, and that is in `parent_mapping`. It does so by
merging the current `divergent` map into it, and makes the `divergent`
just a set instead. When finding the new parents for a child, we leave
the existing parent if it's in the set.

My longer-term goal is to move `parent_mapping`, `abandoned`, and
`divergent` into `MutableRepo` (maybe in a nested struct), so we can
do some transformations on descendants as we rebase them. By having
the state in a single place (not moving it from `MutableRepo` to
`DescendantRebaser` as we currently do), I hope it will be easier to
write a `MutableRepo::transform_descendants(callback)`, where the
callback gets a `CommitBuilder` and can change parents of the commit,
for example.
2024-03-25 06:53:14 -07:00
Martin von Zweigbergk
ba244423e8 rewrite: avoid an unnecessary clone 2024-03-25 06:53:14 -07:00
dploch
9380f9d529 rewrite: move handling of simplified ancestry into rebase_commit_with_options
It seems incorrect that `simplify_ancestor_merge` is ignored when it's part of the helper's input.
2024-03-20 11:57:54 -04:00
Martin von Zweigbergk
1cbf2b4acf rewrite: allow working-copy to be abandoned
This removes the special handling of the working-copy commit. By
recording when an empty/emptied commit was abanoned, we rebase
descendants correctly and create a new empty working-copy commit on
top.
2024-02-25 16:39:05 -08:00
Martin von Zweigbergk
3bc3a63411 rewrite: move decision about abandoned commit into update_references() 2024-02-25 16:39:05 -08:00
Martin von Zweigbergk
3f1d75f518 rewrite: default to not simplifying ancestor merges
This means auto-rebase will no longer simplify ancestor merges.
2024-02-19 14:20:18 -08:00
Martin von Zweigbergk
a9d0300b11 rewrite: make simplification of ancestor merges optional
I think the conclusion from #2600 is that at least auto-rebasing
should not simplify merge commits that merge a commit with its
ancestor. Let's start by adding an option for that in the library.
2024-02-19 14:20:18 -08:00
Ilya Grigoriev
d439de073d rewrite.rs: revert commits cfcc7c5e and becbc889
This mostly reverts https://github.com/martinvonz/jj/pull/2901 as well as its
fixup https://github.com/martinvonz/jj/pull/2903. The related bug is reopened,
see https://github.com/martinvonz/jj/issues/2869#issuecomment-1920367932.

The problem is that while the fix did fix #2869 in most cases, it did
reintroduce the more severe bug https://github.com/martinvonz/jj/issues/2760
in one case, if the working copy is the commit being rebased.

For example, suppose you have the tree

```
root -> A -> B -> @ (empty) -> C
```

### Before this commit

#### Case 1

`jj rebase -s B -d root --skip-empty` would work perfectly before this
commit, resulting in

```
root -> A
  \-------B -> C
           \- @ (new, empty)
```

#### Case 2

Unfortunately, if you run `jj rebase -s @ -d A --skip-empty`, you'd have the
following result (before this commit), which shows the reintroduction of #2760:

```
root -> A @ -> C
         \-- B
```

with the working copy at `A`. The reason for this is explained in
https://github.com/martinvonz/jj/pull/2901#issuecomment-1920043560.

### After this commit

After this commit, both case 1 and case 2 will be wrong in the sense of #2869,
but it will no longer exhibit the worse bug #2760 in the second case.

Case 1 would result in:

```
root -> A
  \-------B -> @ (empty) -> C
```

Case 2 would result in:

```
root -> A -> @ -> C
         \-- B
```

with the working copy remaining a descendant of A
2024-02-03 15:56:44 -08:00
Martin von Zweigbergk
9efa66e8c9 rewrite: remove return value from rebase_next()
`rebase_next()` returns an `Option<RebasedDescendant>`, but the only
way we use it is to decide whether to terminate the loop over
`to_visit`. Let's simplify by making the caller iterate over
`to_visit` instead.
2024-01-30 23:27:48 -08:00
Martin von Zweigbergk
881d75e899 rewrite: drop TODO about changing the API
The `rebase_next()` method is private, so I think we've addressed the
TODO.
2024-01-30 23:27:48 -08:00
Ilya Grigoriev
becbc88915 rewrite.rs: fix working copy position after jj rebase --abandon-empty
Fixes #2869
2024-01-30 22:53:55 -08:00
Ilya Grigoriev
1fff6e37a1 rewrite.rs DescendantRebaser: rename variable for clarity
The `edit` argument seems to be true if and only if the
old commit was *not* abandoned. So, I flipped its value
and renamed it to `abandoned_old_commit`.
2024-01-30 22:53:55 -08:00
Yuya Nishihara
fa5e40719c object_id: extract ObjectId trait and macros to separate module
I'm going to add a prefix resolution method to OpStore, but OpStore is
unrelated to the index. I think ObjectId, HexPrefix, and PrefixResolution can
be extracted to this module.
2024-01-05 10:20:57 +09:00
Matt Stark
3f0a49dafe Ensure you never drop the working commit with --skip-empty
See #2766 for discussions
2024-01-04 13:33:24 +11:00
Matt Stark
a4aed2391f Rewrite instead of abandoning empty commits.
Fixes #2760


Given the tree:
```
A-B-C
 \
  B2
```
And the command `jj rebase -s B -d B2`

We were previously marking B as abandoned, despite the comment stating that we were marking it as being succeeded by B2. This resulted in a call to `rewrite(rewrites={}, abandoned={B})` instead of `rewrite(rewrites={B=>B2}, abandoned={})`, which then made the new parent of `C` into `A` instead of `B2`
2024-01-04 13:33:24 +11:00
Ilya Grigoriev
6edaa97517 DescendantRebaser: change rebased() method to into_map() that consumes the rebaser
This prevents a clone and does not affect the public API, as suggested
in https://github.com/martinvonz/jj/pull/2738#discussion_r1438903463.
2024-01-01 21:55:18 -08:00
Ilya Grigoriev
ddec3f91b2 lib: mild refactoring made possible by previous commit
Inline `create_descendant_commits`, move some functionality of
`DescendantRebaser::rebase_next` to `rebase_all`, a seemingly more logical
location.
2024-01-01 18:51:36 -08:00
Ilya Grigoriev
277b81ff6f lib: make DescendantRebaser-related APIs private.
Finally, there are no test uses of these APIs. `DescendantRebaser` is made
`pub(crate)`, since it is used by `MutRepo`. Other functions are made private.
2024-01-01 18:51:36 -08:00
Ilya Grigoriev
7cef879ef6 lib repo.rs & rewrite.rs: Move clearing of rewritten/abandoned commits
This commit is a little out of place in this sequence, but
it seems to make more sense for MutRepo to own these maps.

@yuja [pointed out] that any tests written using `create_descendant_rebaser` now
need to do this cleanup, but there are no longer any such tests after the
previous commits and a follow-up commit removes `create_descendant_rebaser`
entirely.

[pointed out]: https://github.com/martinvonz/jj/pull/2737#discussion_r1435754370
2024-01-01 18:51:36 -08:00
Ilya Grigoriev
316ab8efb8 rewrite.rs: refactor new_parents to depend only on parent_mapping
Previously, the function relied on both the `self.parent_mapping` and
`self.rebased`. If `(A,B)` was in `parent_mapping` and `(B,C)` was in `rebased`,
`new_parents` would map `A` to `C`.

Now, `self.rebased` is ignored by `new_parents`. In the same situation,
DescendantRebaser is changed so that both `(A,B)` and `(B,C)` are in
`parent_mapping` before. `new_parents` now applies `parent_mapping` repeatedly,
and will map `A` to `C` in this situation.

## Cons

- The semantics are changed; `new_parents` now panics if `self.parent_mapping`
  contain cycles. AFAICT, such cycles never happen in `jj` anyway, except for
one test that I had to fix. I think it's a sensible restriction to live with;
if you do want to swap children of two commits, you can call
`rebase_descendants` twice.

## Pros

- I find the new logic much easier to reason about. I plan to extract it into a
function, to be used in refactors for `jj rebase -r` and `jj new --after`. It
will make it much easier to have a correct implementation of `jj rebase -r
--after`, even when rebasing onto a descendant.

- The de-duplication is no longer O(n^2). I tried to keep the common case fast.

## Alternatives

- We could make `jj rebase` and `jj new` use a separate function with the
algorithm shown here, without changing DescendantRebaser. I believe that the new
algorithm makes DescendatRebaser easier to understand, though, and it feels more
elegant to reduce code duplication.

- The de-duplication optimization here is independent of other changes, and
could be used on its own.
2023-12-12 19:35:51 -08:00
Yuya Nishihara
28ab9593c3 repo_path: split RepoPath into owned and borrowed types
This enables cheap str-to-RepoPath cast, which is useful when sorting and
filtering a large Vec<(String, _)> list by using matcher for example. It
will also eliminate temporary allocation by repo_path.parent().
2023-11-28 07:33:28 +09:00
Ilya Grigoriev
6aef4bb52e cli rebase: do not allow -r --skip-empty
This follows up on 3967f63 (see that commit's description for more
motivation) and e79c8b6.

In a discussion linked below, it was decided that forbidding `-r --skip-empty`
entirely is preferable to the mixed behavior introduced in 3967f63.

3967f637dc (commitcomment-133539911)
2023-11-27 10:16:36 -08:00
Yuya Nishihara
b7543f8a08 rewrite: fix check for newly-empty commit in optimized path
'old_base_tree_id == None' means the rebased tree is unchanged, so the commit
shouldn't be considered newly-empty.
2023-11-26 14:42:17 +09:00
Yuya Nishihara
2f93de9299 rewrite: flatten mapping from EmptyBehaviour to desired action
I think this is slightly easier to follow.
2023-11-26 14:42:17 +09:00
Ilya Grigoriev
c32847696d rewrite.rs: rename new_parents to parent_mapping
The function `new_parents` makes sense, but I found the mapping
being named `new_parents` confusing.
2023-11-25 21:36:35 -08:00
Martin von Zweigbergk
550164209c revset: add a RevsetExpression::evaluate_programmatic()
We often resolve a programmatic revset and then immediately evaluate
it. This patch adds a convenience method for those two steps.
2023-11-24 21:13:58 -10:00
Martin von Zweigbergk
f2602f78cf revset: make resolve_programmatic() not return a Result
I think it's always a programming error if `resolve_programmatic()`
returns a `Result`, so it shouldn't have to return a `Result`.
2023-11-24 21:13:58 -10:00