Open benbrittain opened 1 month ago
I'd like to preserve the ergonomics of the underlying
jj op undo
command
Why make them different? Oh, I suppose the top-level jj undo
would lose its optional operation argument?
I'm actually starting to question if jj op undo
is needed at all. I'd originally been modeling it as a lower level operation, but it's really just a special case of jj op restore
, no? I can't imagine I'd ever use jj op undo
if jj undo had ctrl-z
semantics
it's really just a special case of
jj op restore
, no?
No, jj undo @-
undoes the second-to-last operation while leaving the changes from the last operation. For example, if you abandoned some commit, and then did a bunch of unrelated changes, you can still recover that commit with jj undo <old operation>
. It can be hard to reason about, however, because it also brings back ancestors of the old commit when it makes the old abandoned commit visible.
Think of it like jj backout
but for operations (while jj op restore
is jj restore
for operations).
To reword the FR a bit:
Improve the ergonomics of jj undo
by making it a separate command, which can do undo
tracking like editors. Currently running undo
twice in a row, surprises a lot of users as it just rolls back the last operation (not surprising if you know that jj undo
is an alias of jj op undo
). This would then pave a way for a smart redo
, which has the opposite behavior.
Describe alternatives you've considered Alternatively, the jj undo command could keep track of where you are in the undo log ctrl-Z style. That probably implies a redo command as well?
I think that this alternative would be layering violation in UI terms, as imo jj op undo
shouldn't be aware of such things anyway. That's where the suggestion came from in Discord anyway.
Would this have any impact on #3428 (op log
waypoints)?
Would this have any impact on #3428 (
op log
waypoints)?
The higher level jj undo
could probably integrate with them, which then simplifies the UX again. op log
Waypoints will just be another nice building block for it.
I think undo
is the wrong idiom to think of these things with. The op log
is a continuous stream of states that is append only. Attempts to imagine the current state as a pointer to somewhere in this history of operations is appealing, but in truth you are creating a new state at the head that happens to have identical content to an earlier state.
I would recommend removing undo
entirely and instead building on restore
as the fundamental unit. Give it good usability so you can say restore <timestamp>
and then make it clear that what this command does it creates a new state now that has exactly the same contents as the system had at <timestamp>
.
I would recommend removing
undo
entirely and instead building onrestore
as the fundamental unit.
Are you saying that jj op undo <not latest operation>
is too hard to understand? I think that's fair. I practically never use it myself.
By the way, I find the suggestion of making jj undo
behave differently from jj op undo
confusing. That would be resolved by removing jj op undo
. Another option is to rename jj op undo
to jj op backout
(matching jj backout
just like jj op restore
matches jj restore
). Maybe we should start with that rename.
The
op log
is a continuous stream of states that is append only. Attempts to imagine the current state as a pointer to somewhere in this history of operations is appealing, but in truth you are creating a new state at the head that happens to have identical content to an earlier state.
I agree with this, but still think that jj undo
should be kept with the suggested semantics.
By the way, I find the suggestion of making
jj undo
behave differently fromjj op undo
confusing. That would be resolved by removingjj op undo
. Another option is to renamejj op undo
tojj op backout
(matchingjj backout
just likejj op restore
matchesjj restore
). Maybe we should start with that rename.
That sounds great and should make the implementation simpler as it can just shell out to op restore
.
I have found jj op undo
to be occasionally useful, but I like the idea of renaming it to jj op backout
.
I'm not 100% sure what jj undo
should do if we rename jj op undo
to backout
until we create a fancy new behavior. It would be a bit sad to get rid of it. I suppose it could be a version of jj op backout
that only works on the last operation, and explains to the user that 1) repeating jj undo
will undo the undo 2) try jj op log
and jj op restore
for anything fancy.
I am also not sure what a new and fancy jj undo
would do, exactly. I wrote up a long explanation of how (I think) it worked in Emacs, but that made me realize that almost any text-editor-like undo behavior will run into the same problem: what if you do a bunch of undo-s, and then a non-user-initiated operation happens unexpectedly (like snapshotting from jj log
running on a timer or because watchman notices something changed)?
Here's the write-up about the old Emacs behavior (or what I think it was)
I'd like to suggest the old Emacs behavior for consideration: if the op log is at
root -> A -> B -> C
the first jj undo
undoes C, and the second jj undo
undoes A. At that point, the log would be
root -> A -> B -> C -> undo C -> undo B
Now, if you did another undo, it would undo A. If you do any operation D other than undo (or redo), however, the op log becomes
root -> A -> B -> C -> undo C -> undo B -> D
Now, if you start undoing, you will first undo D, then you'll undo "undo B", then you'll undo "undo A", and so on.
The advantage of this is that it works well with an append-only op log, jj op log
will tell you exactly what's going on.
There are some disadvantages, most notably: what do we do if "operation D" is an unexpected non-user-initiated operation, such as snapshotting?
I am also not sure what a new and fancy
jj undo
would do, exactly. I wrote up a long explanation of how (I think) it worked in Emacs, but that made me realize that almost any text-editor-like undo behavior will run into the same problem: what if you do a bunch of undo-s, and then a non-user-initiated operation happens unexpectedly (like snapshotting fromjj log
running on a timer or because watchman notices something changed)?
I think we should be able to fix the Emacs use-case you mentioned with separately tagging the op log
transactions to distinguish them from automation and human interaction, like #3428 wants. In my opinion we deserve an actual client/daemon for jj
where direct forge integration and a VFS is built in and then having the option to tag where an op came from will be needed.
Here’s a nice exposition of a completely linear undo feature that never loses history, even as you perform new changes on top of undos; all past states remain accessible at all times: Resolving the Great Undo-Redo Quandary. I don’t know if it would work for Jujutsu, but we should at least consider and internalize its lessons when thinking about this.
(It’s possible Emacs has behaviour similar to this; I’m not sure.)
Is your feature request related to a problem? Please describe. The current behavior of the jj undo command does not seem to be what users intuitively expect. Currently if you run
jj undo
twice in a row it has the same behavior asjj op undo
, it returns you to the same place you were when you started. undo + undo = no change.Describe the solution you'd like I'd like to preserve the ergonomics of the underlying
jj op undo
command (as suggested by @PhilipMetzger on Discord), but have the higher leveljj undo
command track if a user immediately undoes an undo and warn them.Describe alternatives you've considered Alternatively, the jj undo command could keep track of where you are in the undo log ctrl-Z style. That probably implies a redo command as well?