martinvonz / jj

A Git-compatible VCS that is both simple and powerful
https://martinvonz.github.io/jj/
Apache License 2.0
8.17k stars 272 forks source link

Duplicate local branch after branch conflict + `jj branch set` + `jj undo` #4089

Open tingerrr opened 1 month ago

tingerrr commented 1 month ago

Description

This involves two local copies, and a remote on GitHub, I'll highlight these and the relevant branch names with ....

Yesterday at home, I created or updated a stash branch to keep some unfinished commits away from my feature-branch and pushed them to remote. Today at work I fetched stash and ended up in with a conflicted stash branch (see below). jj branch list showed a normal and a hidden change, so I used jj branch set to resolve the conflict and it did as expected. After I ran jj undo however, I ended up with 3 copies of the local branch pointing to the hidden change once and the normal change twice.

image

According to the op log, I had previously (like a month ago) created, pushed and afterward deleted stash at work. It turns out that between deleting the stash at work and fetching the new one today, I never actually pushed that deletion since it was never in my default push revset.

Steps to Reproduce the Problem

I cannot reproduce this, but here are the rough steps that caused the issue for me:

  1. Create a new local repo called work with the following change history: A (main) -> B -> C (stash)
  2. Push both stash and main to the remote
  3. Delete stash on work, don't push this deletion
  4. Create a new local repo by cloning from remote called home
  5. Create new changes on top of main
  6. Track stash@origin at home
  7. Update stash to point to these new changes
  8. Push this to remote
  9. Fetch from remote at work
  10. Resolve the conflict by setting the stash branch to the visible new commit
  11. Run jj undo

As noted above, I can't reproduce this. It was probably some form of race condition, so I'll add whatever info you may find helpful at the end of the issue. I'll see if I find anything in the home op log later today.

Expected Behavior

2 copies of the same stash branch, one pointing to the hidden change, the other pointing to the visible change, as it was before running jj branch set stash -r ....

Actual Behavior

3 copies of the same branch, 2 of which point to the same visible change, the other pointing to the hidden change.

Specifications

yuja commented 1 month ago

If you had some background job (or core.watchman.register_snapshot_trigger is enabled), the background process might capture intermediate state of jj undo and create conflicts. In that case, jj op log should include some snapshot/import operation around the jj undo record.

tingerrr commented 1 month ago

The only background process interacting with jj would've been the aforementioned shell prompt which, core.watchman.register_snapshot_trigger is not enabled.

I assume that this can't be the culprit since it uses --ignore-working-copy?

ilyagr commented 1 month ago

What does the op log look like around that problematic undo? Are there splits in the graph and "resolving concurrent operations"?

In principle, you can reconstruct a lot of what happened using jj branch list --at-op and jj log --at-op.

If you can share the repo (including .jj), we can all partake in that fun... But also, just looking at the op log might be enough.

tingerrr commented 1 month ago

It's linear, but I'll upload what I have at work here as a zip archive. dc99c98fed08 is where I fetched, and 0b3dc17efed1 is where I ran into the duplicate branch.

yuja commented 1 month ago
  1. at operation dc99c98fed08 "fetch from git remote(s) origin"

    • the branch "stash" has change/delete conflict.
    • since it's conflicted, "stash@git" isn't exported.
  2. at operation e11bae68416b "point branch stash to commit"

    • the branch "stash" is fixed and "stash@git" is exported.
  3. at operation f23af9257a27 "undo operation"

    • the branch "stash" is restored from dc99c98fed08.
    • "stash@git" is also restored from dc99c98fed08 (i.e. deleted.)
    • since the branch "stash" is conflicted, new "stash@git" can't be exported.
  4. at the next operation c37445898efc

    • "stash" is imported from git as a "new" branch, making 5-way conflict.
    • the conflict can be simplified to 3-way because both sides has "absent" targets.

Perhaps, the step 3 is wrong. The "stash" branch in git repo should be deleted because "stash@git" is restored to "absent" state.

tingerrr commented 1 month ago

That explains why I couldn't reproduce it, I wasn't using a colocated repo when trying to reproduce.