-
Notifications
You must be signed in to change notification settings - Fork 312
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
WIP: docs/design: git branch mode #4670
base: main
Are you sure you want to change the base?
Conversation
I specifically did not draw a diagram of the result of applying |
6636b7d
to
bf4ff3f
Compare
`.git/HEAD` (hereafter `HEAD`) is populated with a valid `jj` bookmark, and the | ||
bookmark is currently at `@` or `@-`. In this design, the bookmark will be |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
FYI, we currently don't allow HEAD
to point to @
. At the start of the command, we import HEAD
and create a new working-copy commit on top of it if it has changed since last time.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Interesting; I assumed jj edit <bookmark>
would set HEAD
to @
.
For a merge commit <m>
, how does jj edit <m>
decide which parent to assign to .git/HEAD
? Is it arbitrary?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's probably the first parent, as far as I can remember. So, pretty close to being arbitrary.
When in branch mode, these operations preserve branch mode, and do not change | ||
which bookmark `HEAD` points to: | ||
|
||
``` | ||
rebase | ||
abandon | ||
commit | ||
new [without specifying revisions] | ||
duplicate | ||
backout | ||
``` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
None of these commands currently do anything special w.r.t. branches - it's all generic behavior that's implemented at a lower level. I'd really like to keep it that way. That ensures that the behavior is consistent between commands, including for our custom commands at Google, and in operations implemented by various UIs that use the library.
So, can you instead describe the behavior in a way that doesn't refer to a specific command (other than as an example)?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll try to rephrase, but the short version is that these commits aren't doing anything "special." If you've got an active branch, then that branch stays active, and the corresponding jj bookmark moves appropriately, i.e. it moves together with @
.
This is a proposed configuration option to replace | ||
`experimental-advance-branches`. It is not intended to replace or obviate | ||
topics. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it would help me if this document started with a problem statement. Can you list some scenarios that you think we should improve? Here's one I can think of where it should be easy to make jj behave better:
$ jj
@ n [email protected] 2024-10-19 17:22:54 70e302a0
│ (no description set)
○ w [email protected] 2024-10-19 17:05:55 main HEAD@git 873ebde8
│ (empty) (no description set)
○ u [email protected] 2024-10-19 17:05:47 52b0bdfd
│ (empty) (no description set)
◆ z root() 00000000
$ git branch
* main
$ jj desc w -m blah
Rebased 1 descendant commits
Working copy now at: n e270b828 (no description set)
Parent commit : w a316f16d main | (empty) blah
$ git branch
* (HEAD detached at a316f16)
main
I.e. we left Git in a "detached HEAD" state where we could have left main
checked out.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since describe
doesn't move @
(it changes the commit ID but not the change ID), it also wouldn't change .git/HEAD
in "active branch" mode, since branch mode means that .git/HEAD
contains a branch reference, not a commit sha.
(As before, I will add this to the doc, I just wanted to briefly answer your question now.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since
describe
doesn't move@
(it changes the commit ID but not the change ID), it also wouldn't change.git/HEAD
in "active branch" mode, since branch mode means that.git/HEAD
contains a branch reference, not a commit sha.(As before, I will add this to the doc, I just wanted to briefly answer your question now.)
But the Git branch that HEAD@git points to always points to a commit hash, not a change_id, so it does need updating. Or, in terms of bookmarks, the JJ bookmark might still point to the same change_id, but as a newly created commit is associated with the change_id from the bookmark, and the old commit becomes hidden, JJ effectively needs to run jj git export
to update the commit hash that git sees for the branch that the bookmark is tracking.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The proposal is currently only for colocated repos. In colocated repos, the export already happens automatically:
Jujutsu will import and export from and to the Git repo on every jj command automatically.
I'll need to learn a little more about how non-colocated repos currently behave before adding them to the design.
docs/design/branch-mode.md
Outdated
|
||
These Excalidraw diagrams show the effect of various `jj` commands when in | ||
branch mode: | ||
https://excalidraw.com/#json=pkmTWTuzkToGdc4xSUy2C,7uhU9FBI6rJK-U_XGCH4HA |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Possibly adding on to #4670 (comment) (which is the kind of questions these diagrams are probably meant to address), I found these diagrams hard to follow. They are very pretty, but they need more words telling the reader where to look, and what is most important about the pictures.
Another (more minor, I mostly figured it out but it could be clearer) thing I found confusing at a glance is what the red and the blue arrows mean. After looking at the text, they are meant to be bookmarks (you should label that in the picture!), but it's unclear how they are related to each other. I guess the red one is supposed to be the "current bookmark"?
I think the pictures could be very helpful once you make thing clearer. (Though if presenting a few clear examples as Martin asked is easier in other ways, that's more important). Here's one possible way to arrange them:
- Starting state
- A bit of text introducing a few examples, probably
`new` is the most important one
- A few examples (the pictures)
- A bit of text introducing a few more examples
- A few more examples
and so on.
Another suggestion for the pictures: it would be interesting what happens if there are two bookmarks pointing to Z
, only one of which is "current". You don't have to show it in every picture, though, or perhaps it could be in a separate picture.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
BTW, are the "split" and "new" diagrams switched?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In your "ambiguous update: abandon Z
example, I thing jj
would currently create a branch conflict with the red branch being conflicted between pointing at D and at Y. The thing you suggested wouldn't work if we continue to disallow HEAD pointing to @ (as we currently do).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Understood; I intentionally left the diagrams in Excalidraw rather than rendering SVGs so they could be iterated on. I'll certainly add labels and/or a legend to the diagram itself.
The basic jist is that the top left diagram is an "initial state", and then almost all the other diagrams show what the next state would be after executing the command shown from that initial state.
The branch arrows are unrelated; red happens to be the "current" branch in the initial state. The thing showing that it's "current", though, is the shading and coloring on the pointed-to commit.
Agreed the two-bookmarks example would be good to show; that's actually one of the primary things I struggle with using the current advance-branches
feature.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, split
and new
are not switched, but the use of new
as a node label is probably not helpful. split
is similar to new
in that both introduce a new commit on top of the target commit. (The docs even mention that if the target commit is empty, split
is prohibited because it would be identical to new
.)
One thing that may not be clear is that the letters/words/symbols in the commit nodes are change IDs, and @
itself is shown as a commit. The command is always shown on the upper-left of each diagram. So there are two variants of split
next to each other, one with no arguments and one with the argument @-
. The commit-node that says new
is just to indicate that a new commit has been created, but looking again, there's no good reason to use Z'
in one split
diagram and new
in the other. I think the thing I need to do with those is explicitly state that the content of the original Z
commit has been split between Z'
and Z
(on the left) and between Z
and new
(on the right).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, split and new are not switched,
It's then confusing to me how in the new
example, N
becomes a child of "new". Perhaps that's a mistake? If it is intentional, what's the logic behind it?
I think I was indeed mistaken about "split". It was partially because it is hard for me to not think of the highlighted commit as the working copy, it looks more important than the commit actually marked @
, so I find myself getting them confused.
I think the thing I need to do with those is explicitly state that the content of the original Z commit has been split between Z' and Z (on the left) and between Z and new (on the right).
Yes, something like this would probably be helpful.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, yes, you're right, I think the new
diagram is indeed incorrectly copy-pasted from split @-
. Thank you. The N
should still be a child of Z
, as in split
without args.
...speaking of args, I also forgot that the positional args of split
are paths, not refs. So that should be split -r @-
and split
(without -r
).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here are the updated diagrams: https://excalidraw.com/#json=SN8HJ4EXv3ChyzvUdra_i,lwYuJ7ddFRR5zMmGCDdI8w
|
||
**Note:** The only part of this design that is specific to `git` colocation is | ||
the use of `.git/HEAD`. The design could be implemented for any backend that | ||
has a concept of a "current" branch. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think that if we make this work, it should work on non-colocated repos as well. To me, this looks like a proposal of "what if we added the notion of a 'current branch' to jj
?". TBH, I'm not sure I'm excited about this -- I'd probably want this to be optional and to keep it off by default -- but it is probably worth thinking through, and my first impression might be wrong.
This makes more sense to me than the experimental advance-branches feature in some ways, but it would also be a much larger change to implement. E.g., you'd need to adjust the log template to show which branch is current, store this information in the repo, etc.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's also something to be said for fixing the advance-branches feature, if it's possible without storing extra info (which I'm not at all sure about).
I think your original idea was to store the extra info in the Git repo only (by storing a branch in the HEAD
ref), but I'm suspicious about this part of it. We probably want it to work with undo
, and so it must be stored on the jj
side as well.
Thank you for the writeup! |
Writeup Comments / Confusion Commenting on some parts of the writeup:
I don't see how that could work without an extra option: bookmark create foo @-
bookmark create bar @-
# What if I want HEAD@git->foo, but still create a non-advancing `bar`
# Git equivalent:
git checkout -B foo HEAD
git branch bar It gets worse if
Uhm, starting to list commands and the various modes they operate in (sometimes @ is updated, sometimes not) looks like it'd be very error prone. And definitely hard to model by a user (think cognitive load). In a similar vein, your Changing branches, Merges, Diffing sections also sound like too much magic to me. In particular having Take that all with a grain of salt, I don't know the JJ code itself. I am merely pointing out that after several months of JJ use, I still have a hard time wrapping my head around how any of the advance-branch proposals that I have seen so far could work in practice. And therefore, I wonder how newcomers would be expected to model that behavior... |
Branch Interoperability Note, one operation that already makes JJ update HEAD@git point to a Git ref, just a missing one, is this: ⮞ jj new 'root()' && jj log --no-graph -T builtin_log_oneline
Working copy now at: upqzzzok 3acbda69 (empty) (no description set)
Parent commit : zzzzzzzz 00000000 (empty) (no description set)
upqzzzok timj 2024-10-22 03:07:01 3acbda69 (empty) (no description set)
zzzzzzzz root() 00000000
⮞ cat .git/HEAD
ref: refs/jj/root
⮞ cat .git/refs/jj/root
cat: .git/refs/jj/root: No such file or directory That could just as well be a non-existing Considering that JJ will have to work for people with colocated repos for many, many years to come, I think its interoperability with Git branches should be improved. That means, make JJ properly update HEAD@git, i.e. without going into detached-head mode unless Git already is in detached-head mode. The one, comparatively simple thing, that I think JJ should do to improve Git interoperability is this:
That is it. For a start. It is really nothing more than, this:
So I am asking, are there any negative side effects to be expected in a JJ repository where the user runs The Auto-Advance Conundrum Wether a user needs a branch tip to be advance on DVCS-commit or DVCS-new/checkout/switch depends on the use case and more importantly the mental model the user applies at that moment. In this aspect, JJ is at a disadvantage compared to Git because:
The thing is, JJ doesn't have to be at a disadvantage compared to Git here. The needed information about the user intent (and mental model) is readily available in .git/HEAD. JJ should just not throw it away, instead make as much good use of it as Git does by adding support to advance the ref that .git/HEAD points to. $ jj head # Inspect branch-tip vs detached-head mode
Detached HEAD mode.
Working copy at: rwvmpwzp aba47a45 (empty) (no description set)
Parent commit : rtlqvrtv a3f650ff (empty) (no description set)
$ jj head foo # like: git checkout -B foo
Advancing branch mode, branch: foo
Working copy at: rwvmpwzp aba47a45 (empty) (no description set)
Parent commit : rtlqvrtv a3f650ff foo | (empty) (no description set)
$ jj head - # like: git checkout --detach HEAD
Detached HEAD mode.
Working copy at: rwvmpwzp aba47a45 (empty) (no description set)
Parent commit : rtlqvrtv a3f650ff foo | (empty) (no description set) Native JJ Repositories JJ repos that are not colocated could decide if they "simulate" an internal HEAD@git config setting or not. |
This design proposes keeping git branches in-sync with jj at any given time. If it's possible to do, it's great! My usecase:
It would be just fine for me to run something like:
|
@kriomant I think you can just write a shell function to do what you want; for instance, in Bash: with_branch() {
git checkout --no-guess $1
shift
"$@"
jj git import
git checkout $(git rev-parse HEAD)
} Usage would be something like The final |
45e618d
to
76c3d3d
Compare
This evolved from a discussion in Discord.
Rendered
Diagrams (aka very rough sketches)
TBD: