jj icon indicating copy to clipboard operation
jj copied to clipboard

FR: Working copy stack

Open buildwithzephyr opened this issue 1 year ago • 6 comments

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

One thing that JJ makes really easy is going back in your history and editing previous changes. I often find myself doing something like this:

  1. jj edit or jj new <rev>
  2. Make some changes and then jj squash.
  3. Move back to the (now automatically rebased) descendant change that I had been working on.

A frustration here is that in order to do step 2, I have to know the (short-form) change id, usually by searching in jj log.

Describe the solution you'd like

I think that an elegant solution to this problem would be to have a "working copy stack," analogous to the directory stack in most shells. In particular,

  1. Any command which explicitly changes the working copy - such as jj new <rev>, jj edit <rev>, or jj split -r <rev> - would push to the working copy stack.
  2. However, a command which simply advances the working copy by one - such as jj commit, jj next and jj prev, or jj new with no arguments - would instead just update the top of the working copy stack. (analogous to cd instead of pushd)
  3. There would be a command to pop the top of the working stack, and also one to list the working copy stack so that you can easily find previous working copies without digging through the log. One possibility is jj wc with subcommands pop and list.
    • There could also be a subcommand called push or new. This would duplicate the top of the stack, so that you can go back to it if your next command is about to overwrite the top of the stack.

The workflow above would now look like...

  1. jj edit or etc. and make some changes.
  2. jj wc pop and you're back where you were.

Because it's a stack that updates every time you manually change your working copy, it could just be generally useful whenever you're working with multiple branches or lines of work (whether or not you are tracking them as branches in Git.) I can imagine lots of scenarios where jj wc list would be very helpful to have.

Describe alternatives you've considered

This is related to #2871. It solves a lot of the same problems. However, the proposed solution is different in that it has a stack as opposed to just a history, and in that the user doesn't have to deal with revsets - which I think would make it more approachable to beginners or to people like me who are coming from Git. The two solutions could be compatible; one could implement a revset function that lists the working copy stack.

Additional context

How long to keep the stack around is an open question - do you want things in the stack from months ago? Certainly a jj wc clear would be a good thing to have.

buildwithzephyr avatar May 19 '24 00:05 buildwithzephyr

This can also be solved with a history topic which represents your stack, see #3402. It'd be less invasive too.

PhilipMetzger avatar May 19 '24 12:05 PhilipMetzger

For the first use case mentioned above, you can do something like jj new 'heads(@-:: ~ @)'. You can define an alias for that and/or define a revset alias for the revset.

martinvonz avatar May 19 '24 18:05 martinvonz

In Git, their stack-like data structure (git stash) interacts very poorly with the rest of the system because it doesn't use the same vocabulary or mechanisms. Similarly, I think creating a dedicated working-copy stack command in jj (not the same as git stash) would interact poorly with the rest of jj. If we implement this, rather than a dedicated command, I think we should make more revsets, so that you can address+manipulate those commits by the standard means.

I think it doesn't currently, but if the operation log were to store the command/subcommand that was invoked for each operation, then you might be able to implement this yourself today without special jj integration.

arxanas avatar May 26 '24 23:05 arxanas

I think that an elegant solution to this problem would be to have a "working copy stack," analogous to the directory stack in most shells.

@arxanas: If we implement this, rather than a dedicated command, I think we should make more revsets, so that you can address+manipulate those commits by the standard means.

This popped out immediately for me:

jj new -

Same as cd -. - on its own would be a revset referring to the parents of the previous @. And you could add a few enhancements to the revset parser to make -- mean the second last instead of being equivalent to (-)-.

I think it would also behave differently depending on whether you reached your previous working copy by calling jj new or jj edit.

cormacrelf avatar Nov 13 '24 19:11 cormacrelf

To add, this behaviour would be similar to the workflow I'm used to in Git git checkout - will jump to the previous branch I was working on. I understand with jj and working with headless commits, there would likely be a mix of usages between 'go back to previous change' vs 'go to previous bookmark' and there's also the revset jj edit @-

The use-case here for me would be

Jump back to previous change-commit

use case (quick-change)

jj edit foo       // start of work
jj new master     // notice I want to do a small refactor - start new commit
jj desc -m ""     // do a small refactor, only a single commit is needed
jj git push -c @
jj edit -         // continue with my primary work

use case (new branch)

jj edit foo           // start of work
jj new master         // notice I want to do a small refactor - start new commit
jj branch create bar  // create branch
jj commit -m ""
jj commit -m ""
jj git push           // push changes
jj edit -b            // continue with my primary work on previous bookmark/branch

If there's already a clean way to do this, I'd love to surface it into the FAQ as it's a pretty popular feature in Git in my circle.

andrei-cdl avatar Apr 09 '25 17:04 andrei-cdl

If there's already a clean way to do this,

I don't know if it's a clean way to do this, but for things like typo fixes that aren't conflicting, I've done it by just not moving in the first place:

❯ # doing work on psy
❯ jj log
@  psyopnms [email protected] 2025-04-14 12:40:07 5a03bd03
│  (no description set) 
○  ylvyxvnq [email protected] 2025-04-14 12:39:39 git_head() 6285f3da
│  goodbye, world
○  zrxtpvmp [email protected] 2025-04-14 12:38:50 trunk 0caca481
│  hello world
◆  zzzzzzzz root() 00000000
❯ jj new -m "typo fix"
Working copy now at: mpkkrzos 3b55aa21 (empty) typo fix
Parent commit      : psyopnms 5a03bd03 (no description set)
❯ jj prev --edit
Working copy now at: psyopnms 5a03bd03 (no description set)
Parent commit      : ylvyxvnq 6285f3da goodbye, world
Added 0 files, modified 1 files, removed 0 files
❯ # fix my typo
❯ jj rebase -r m -d trunk
Rebased 1 commits onto destination
❯ jj git push -c m

Things like refactors are going to be less amenable to this, though.

steveklabnik avatar Apr 14 '25 17:04 steveklabnik