Bookmarks are not branches, and that’s the point
Part 8 of 9.
Calling jj’s named pointers “bookmarks” instead of “branches” looked like unnecessary
vocabulary at first. It isn’t. A Git branch moves forward automatically every time you
commit while it’s checked out - the ref and your current line of work are the same thing
until you explicitly switch. A jj bookmark is decoupled from that: you can be working
several commits past the last place a bookmark points, and it just stays there until you
move it with jj bookmark set or by rebasing the commit it’s attached to.
That decoupling is what makes the actual publish loop feel lighter:
jj bookmark create feature-colors
jj git push --bookmark feature-colors
and, once a PR merges and the remote’s main moves,
jj git fetch
is often the entire “catch up” step, because a normal merge or squash-merge is still a
fast-forward from the old tip, and a tracked bookmark fast-forwards on fetch without
being told to. Compare that to remembering git branch -f or git merge origin/main
depending on mood.
The detail I appreciated most, because it removes a whole category of “did I forget to
update the branch pointer” mistake: rebasing a commit that a bookmark points to carries
the bookmark along automatically. jj rebase -d main on a feature commit moves its
bookmark with it in the same operation. And when the rebase target already contains the
same change - the exact situation after a squash-merge - jj notices the diff is already
present and drops the now-empty commit instead of leaving a stray no-op commit behind,
which is the one part of the Git equivalent I always forget the flag for
(--skip-empty).
None of this required abandoning the PR-based workflow my team already uses. It’s the same GitHub flow, same review process, same merge button. Only the local bookkeeping between “I have some commits” and “the remote reflects them” got noticeably lighter.