Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

FR: Command for resetting change ID #4451

Open
scott2000 opened this issue Sep 13, 2024 · 15 comments
Open

FR: Command for resetting change ID #4451

scott2000 opened this issue Sep 13, 2024 · 15 comments
Labels
enhancement New feature or request

Comments

@scott2000
Copy link
Contributor

scott2000 commented Sep 13, 2024

Is your feature request related to a problem? Please describe.
If there are multiple commits with the same change ID and I want to keep both versions of them for some reason, it can be difficult to resolve the divergent change ID. I think it would be nice if there was a command that just replaces all commits in a revset with identical commits, except with new change IDs.

Describe the solution you'd like
I'm not sure what a good name for such a command would be, since jj reset-change-id seems too long. Maybe it would be good to have a general command jj reset that takes different flags depending on what needs to be reset:

# Reset change IDs of commits
$ jj reset --change-id <REVSET>
# Reset author name and timestamp of commits
$ jj reset --author <REVSET>

This might be confusing with git reset, so it might not be a good idea. I do feel like this would be a better place to put "reset author" though, since the current jj describe --reset-author feels like an unintuitive place to put it.

Another option might be a change-id subcommand, but I'm not sure if there's any other operations on change IDs that would be useful. Maybe swapping change IDs could be useful (e.g. if jj split left the change ID on the wrong commit), but that seems like a weird solution.

Describe alternatives you've considered
jj duplicate followed by jj abandon of the original commits works. This could maybe be done with an alias if aliases could execute multiple commands and take arguments to pass to each. Or maybe a new option like jj duplicate --replace <REVSET> could duplicate the commits, but automatically abandon the previous versions and rebase their descendants onto the new commits.

@scott2000 scott2000 added the enhancement New feature or request label Sep 13, 2024
@mati865
Copy link
Contributor

mati865 commented Sep 13, 2024

How about jj rehash, jj reroll (this one might not be so obvious), or perhaps something else that intuitively means SHA/ID?
I suppose it should be configurable with flags to reroll only ID, only the hash or both.

@scott2000
Copy link
Contributor Author

scott2000 commented Sep 13, 2024

Those could be good names!

I think it is technically not possible to modify the change ID without also modifying the commit ID because of how the change ID is stored for the Git backend (I think if you try it, jj will decrement the committer timestamp to cause it to have a new commit ID, but I could be wrong), so it would probably have to update the committer timestamp like the other commands which modify commits do.

@mati865
Copy link
Contributor

mati865 commented Sep 14, 2024

I don't know jj code, but I've assumed it stores references to Git commits and it'd be possible to only recrate the reference.
Having to change both ID likely won't be a problem; I just imagined it'd be neater if somebody wanted to change only one of them, but I likely overengineered it, and nobody would care.

@joyously
Copy link

If there are multiple commits with the same change ID and I want to keep both versions of them for some reason, it can be difficult to resolve the divergent change ID.

Is it really the change ID that is divergent? It seems like the change ID is the thing being managed by the tool, so there are commands to manipulate commits that comprise the change.

@yuja
Copy link
Contributor

yuja commented Sep 15, 2024

I think it is technically not possible to modify the change ID without also modifying the commit ID

Correct. "commit ID" should be considered a content hash including "change ID". The change ID isn't actually stored in Git commit object, but changing it without updating commit ID would lead to internal data inconsistency.

Regarding command name, I feel reassigning change IDs is quite different operation than resetting author or description (even though the underlying representation is similar.)

@tim-janik
Copy link
Contributor

I am frequently switching between repos on the desktop and laptop, which leads to divergent revision more often that I'd like. Ideally, pushing between the repos would not create divergent change IDs in the first place, but simply create new change IDs instead. Short of that however, I'd hope for JJ to add a command:

jj new --replace OLDREV   # create a new change_id pointing to the same git commit as OLDREV

While tedious, this would allow to fixup a single divergent change with a single command.
As things currently stand, fixing up an entire branch with divergent change IDs is prohibitively involved (in case you not simply want to abandon the entire branch).

I think I have managed to script a somewhat reliable implementation of a divergent change_id reassignment in jj-fzf, that allows me to at least fixup branches with a single hotkey per divergent change at a time:
https://github.com/tim-janik/tools/blob/8cfc40e0847e893a85ec7007ef854a07ce72dc9d/jj-fzf/jj-fzf#L770

@martinvonz
Copy link
Member

As things currently stand, fixing up an entire branch with divergent change IDs is prohibitively involved (in case you not simply want to abandon the entire branch).

Does the jj duplicate + jj abandon workaround mentioned above not work in your case?

@tim-janik
Copy link
Contributor

As things currently stand, fixing up an entire branch with divergent change IDs is prohibitively involved (in case you not simply want to abandon the entire branch).

Does the jj duplicate + jj abandon workaround mentioned above not work in your case?

I really fail to understand how duplicate+abandon is supposed to work in practice.

  • If you have a divergent branch (not just a single revision), doing duplicate does not preserve the history (children) of the commit. Also, @ doesn't point to the duplicated commit and the name of the new change isn't printed to stdout, so you have to search for it in jj log.
  • Then, to actually use it, the old children would have to be rebased on top of the newly duplicated commit, and only then could the old divergent revision be abandoned - but make sure to use the commit id, otherwise the command fails.
  • So, duplicate + search + rebase + abandon is so brittle, that you really don't want to repeat it for 10 or 20 committs in a branch.
  • FWIW, I also tried jj backout and a number of other commands, but that doesn't print the newly created revision at all (duplicate at least spews a non template message to stderr)

That is why my script does new --before + squash --from, this preserves the children, and re-finding the commit id after new --before is only somewhat tricky, at least the divergent commit still points to it.
I spent several hours yesterday to clean up my branches, and AFAICS what jj is really lacking is this:

  • There should be a command like jj new --replace or similar that can just exchange a change_id for a newly generated one. This would take all the brittleness out of the divergent situation.
  • There should be a revset for divergent revisions, currently you have to jj log | fgrep '??' to find all.
  • All jj commands that create new revisions should be fixed to always produce the new revision on stdout, and support -T.

The last point is probably most important, because it affects a bunch scripting brittleness. What I mean is this:

jj duplicate - Create a new change with the same content as an existing one
Options:
  -T, --template <TEMPLATE>
          Render the new revision using the given template, defaults to builtin_log_oneline

jj backout - Apply the reverse of a revision on top of another revision
Options:
  -T, --template <TEMPLATE>
          Render the newly created backout revision using the given template, defaults to builtin_log_oneline

Here are examples that show how the commands can now reliably be used in scripts:

$ jj backout -r uosmooxm -d uosmooxm
qsotzrkz timj 2024-09-21 12:25:32 4120b2bc Back out "ase/wavgen.cc: add helpers"
$ jj backout -r uosmooxm -d uosmooxm -T commit_id 
4120b2bc5a72ab09336eccb8f24b7c9eb5de4752
$ jj duplicate -r uosmooxm 
lwqorryx timj 2024-09-21 12:28:45 8688e0e4 ase/wavgen.cc: add helpers
$ jj duplicate -r uosmooxm -T change_id 
lwqorryxtxxqkxomyzmsytqqumvwxqoq

Other commands should follow suit.

@bendk
Copy link

bendk commented Oct 24, 2024

One use case that I'd love this for is when another dev creates a commit on top of mine in git:

  • I author change-a
  • I use jj git push -c @ to push the branch and open a PR from it
  • Another dev uses my branch as the base for their PR. They create a new PR on top of mine
  • I change my commit based on feedback from the PR
  • I run jj git fetch at some point

Now there are 2 commits with the id change-a: my original commit that they've used as their base, and my new commit. It creates an awkward situation:

  • I can't abandon my commit, I'm looking to merge it.
  • I can't abandon the old version my old commit, since their commit is on top of it. That commit is marked as immutable and in any case, I want to be able to view it from my repo. Using jj duplicate + jj abandon doesn't help this AFAICT.
  • I can use jj duplicate + jj abandon on my commit to create a new change id, but that's going to break jj git push -c. It also feels wrong that I need to get a new change id because someone else based their changes off of mine.

What I would love is the ability to give the old commit a new change id to resolve this problem.

@arxanas
Copy link
Contributor

arxanas commented Oct 25, 2024

This reminded me to post #4708, where resetting the change ID could be expressed as jj duplicate but replaceing the old version of the commit(s), or as jj rebase with the explicit request to reset the change ID.

@tim-janik
Copy link
Contributor

[Merging request from #5116]

Is your feature request related to a problem? Please describe.

Currently, jj has no command that supports creating a new commit with a specific change ID or changing an existing change ID. This limitation makes it close to impossible to recreate a specific change ID that has been observed in another repository, e.g. through jj-am.sh which can be used to mirror commits across different repositories.
(Setting JJ_RANDOMNESS_SEED after an inverse chacha20 round could theoretically force a specific change ID, but this is not a practical solution.)

Additionally, there are divergent change_id cases, where keeping a commit, but under a new (different) change_id would be desirable. In such cases it would be nice to simply regenerate a new change_id with a single (simple) command for such a commit (possibly keeping a short prefix similar to the divergent change ID at the user's discretion).

Describe the solution you'd like

jj describe --help

  --reset-change-id=<CHANGE_ID>   Assign a specific change ID or prefix to an existing revision.
  --random-change-id              Assign a newly generated random change ID to an existing revision.

Implementation Notes

  • The --reset-change-id option should validate, that the provided change ID follows the expected format ^[k-z]*$
  • Overlong change_ids should be rejected (unless a case could be made for different backends having different change_id lengths, but AFAIK that is not the case).
  • Shortened change_ids (prefix specification) should be accepted, the missing tail should be randomized.
  • A --random-change-id option only needs to be added in case the above suggestion for supporting prefix change_ids is not implemented. With prefix support, jj describe --reset-change-id='' could be used to generate a new, fully randomized change_id.
  • The --reset-change-id option should permit creating divergent change_ids, that way repos with divergent change_ids can be mirrored (by tools like format-patch + jj-am.sh).

Describe alternatives you've considered

Not support change_id transfers between repos, we have that now and experience its downsides daily.
Also, keeping a divergent commit in place but with a different change_id can now be accomplished with other commands, but is more involved.

Additional context

Note that this feature will need implementation anyway, when support for a custom header like jj_change_id in Git commits is implemented. Adjusting divergent change_id cases and the jj-am.sh use case are good reasons to also expose this functionality through the CLI.

Related Discord discussion:
Martin von Zweigbergk: perhaps it could be an option to jj describe

@joyously
Copy link

This sounds to me like micromanaging the VCS tool. Instead, the scenarios where a change ID is needed should be evaluated to find a way for the tool to handle it gracefully, internally. Perhaps rules of which repo is "source of truth" could be formulated, or meta data about change hierarchy is needed.

@arxanas
Copy link
Contributor

arxanas commented Dec 19, 2024

This sounds to me like micromanaging the VCS tool. Instead, the scenarios where a change ID is needed should be evaluated to find a way for the tool to handle it gracefully, internally. Perhaps rules of which repo is "source of truth" could be formulated, or meta data about change hierarchy is needed.

I agree that we should probably figure out more details of the intended semantics for change IDs. There's some more discussion in this GitHub Discussions comment and this linked Discord thread.

  • For example: I could see a world where having the same change ID in multiple branches/bookmarks is the intended state of affairs, to represent "cherry-picking" patches, in which case it's not even agreed that multiple change IDs should be considered divergent in a meaningful way.

However, I expect that there exists a significant workflow that does involve "micromanaging" the VCS tool and very carefully assigning change IDs, etc., in the same way that there are significant workflows that involve micromanaging the specific commits/patches and rewriting large parts of the commit graph, so my expectation is that an ultimate solution will support manual change ID assignment.

Regarding "source of truth", I suspect that jj intends to preserve existing distributed VCS workflows and not consider any one repo as the "source of truth", but we should certainly consider things more carefully given that we don't have to automatically adopt all Git workflows, and that a majority of development work happens in a centralized fashion regardless.

  • For example: I could imagine a default set of configuration that applies for centralized development, and opting into another set for decentralized development, which is definitely the minority.

@bendk
Copy link

bendk commented Dec 20, 2024

Regarding "source of truth", I suspect that jj intends to preserve existing distributed VCS workflows and not consider any one repo as the "source of truth", but we should certainly consider things more carefully given that we don't have to automatically adopt all Git workflows...

This brings up an interesting question: Should change IDs be expected to be unique across repos? To me it seems like they shouldn't, because of the very use-case I posted above: If you and I both modify a change ID, which modification wins? The only solutions I can see are picking one of our changes somehow as the official one or requiring that we work together to merge our commits together. Both seem impractical and against the spirit of a distributed VCS.

Maybe part of the solution here is to decide that change IDs are local to a repo, like branch/bookmark names. If two different repos disagree on a change ID, that's fine and doesn't count as a conflict.

@tim-janik
Copy link
Contributor

This brings up an interesting question: Should change IDs be expected to be unique across repos?

I was wondering the same, but see the Discord discussion that arxanas linked to in his last comment and also this one from Martin: #5074 (reply in thread)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

8 participants