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.