Jj Vs Git Habit
This is an LLM ass-isted document,
as are all in this subdirectory.
The setup in JjFirstTime makes jj git push work by
itself — until fingers win and bare git commit runs instead. That happened
here, twice, and the next push answered:
Warning: No bookmarks/tags found in the default push revset: remote_bookmarks(remote=origin)..@ Nothing changed.
Same warning as before that setup, different disease: then the bookmark merely lagged one commit; now it was stranded six behind.
1. What bare Git actually did
In a colocated workspace jj keeps .git/HEAD detached, at the working-copy
commit's parent1. Bare git commit therefore commits onto that
detached head: no branch moves, the commits land outside master, and the next
jj command swallows them as an import git head operation and stacks @ on
top2. Three tells in the log:
jj op logshows theimport git headentry, timed between the last push and the new commits;- the new commits carry the Git-config identity, not the jj one;
mastersits far behind@, not exactly one behind.
The last one is the lasting damage: experimental-advance-branches only
advances a bookmark standing at @-3, and after the import master
no longer stands there. Every further jj commit widens the gap instead of
closing it. Nothing is lost — the imported commits are ordinary ancestors of
@ — they are simply unpushed, because a push only carries bookmarks inside
remote_bookmarks(remote=origin)..@4.
2. The repair
jj bookmark set master -r @- # forward move, no flag needed jj git push --allow-empty-description # empty messages are fine by preference
Push would otherwise refuse the undescribed commits on the way; describing or squashing them (see JjFirstTime, Caveats) is the alternative, not a must — and the flag itself can be aliased away (3).
@ is the living commit: not a pointer to one, the way Git's HEAD is —
it is the working copy, as a commit. Exactly one exists per workspace,
always; jj commit creates nothing, it finishes the commit being lived in
(describes it, seals it) and spawns a fresh empty @ on top. Every jj
command snapshots the files into @ first, so an "uncommitted change"
cannot exist — only a commit still being edited. Identity is fluid (the
change id survives each rewrite, the commit id does not), contents are not:
every rewrite lands in jj op log. Hence @-: the last real commit.
Sealing it is also the one instant experimental-advance-branches ever acts
in, moving the bookmark from @- onto the commit being sealed — which is
why, once master was stranded behind, no later jj commit could set it
off: the moment had passed with the import.
3. Sparing the flag
There is no setting for --allow-empty-description; the opt-in is a flag
only, not a config key5. The jj mechanism is an alias, and repo
scope carries it — verified here by shadowing jj push with a bogus flag
and watching jj git push receive it:
jj config set --repo aliases.push '["git", "push", "--allow-empty-description"]'
A bare jj push now pushes the empty messages; the explicit jj git push
stays strict. Drop --repo to have it everywhere. Same caveat as the
advance-branches setup: repo scope is per working copy, not committed to
Git.
4. The habit
Colocation invites the relapse — every Git command keeps working, which is both its point and its trap6. What people do:
- Starve the reflex.
jjanswers the read commands,jj commit -ireplacesgit commit -p, and thejj gitsurface is onlyfetch,push,clone. Makingjj logthe home screen leaves nothing to typegitfor7. - Make the accident cheap. A Git write surfaces in
jj op logas animport git head/import git refsoperation and is undone byjj undoorjj op restore2. - Or remove the option.
jj git colocation disablehides the Git repository inside.jj/, after which bare Git commands stop working in the workspace altogether8. Training wheels off, by force.
TL;DR: jj bookmark set master -r @-.
October, 2026.
Footnotes:
Jujutsu documentation, Git compatibility,
https://docs.jj-vcs.dev/latest/git-compatibility: a colocated workspace keeps
Git's HEAD at the working-copy commit's parent. An r/git commenter calls the
detached head "irrelevant in jj", just "the closest representation in git of
what's happening",
https://www.reddit.com/r/git/comments/1gx4j0w/friendly_reminder_to_try_out_jujutsu.
Same documentation page: interleaving jj and git commands
raises the chance of branch conflicts and divergent change ids; Git-side
changes appear in jj op log as import git refs operations and can be
undone with jj undo / jj op restore.
Maintainer clarification in jj-vcs/jj discussion #3549, Feedback for the experimental advance-branches feature, https://github.com/jj-vcs/jj/discussions/3549; recorded in JjFirstTime.
Steve Klabnik, Steve's Jujutsu Tutorial — Responding to pull
request feedback, https://steveklabnik.github.io/jujutsu-tutorial/sharing-code/updating-prs.html:
jj new moves no bookmarks, so the push does nothing until jj bookmark set;
jj-vcs/jj discussion #7363, How to fix "No bookmarks found in the default
push revset", https://github.com/jj-vcs/jj/discussions/7363; and the
jj bookmark set master -r @- ritual in renerocks.ai, Jujutsu (version
control) - one week in, https://renerocks.ai/blog/jj-one-week-in.
No key for it in the configuration schema at v0.45.1, https://github.com/jj-vcs/jj/blob/v0.45.1/cli/src/config-schema.json, nor in the CLI reference, https://docs.jj-vcs.dev/latest/cli-reference; aliases (including arguments) are the sanctioned mechanism, Configuration, https://docs.jj-vcs.dev/latest/config.
zerowidth, What I've learned from jj,
https://zerowidth.com/2025/what-ive-learned-from-jj: colocated mode keeps all
Git commands working as training wheels, and rm -r .jj undoes jj wholesale.
Greg Hurrell, Jujutsu cheatsheet,
https://wincent.dev/wiki/Jujutsu_cheatsheet: mixing git commit -p with
jj git import bred orphan snapshots — "I should have been using jj ci -i
instead"; and brtkwr.com, jj for Git Users,
https://brtkwr.com/posts/2026-03-08-jj-for-git-users: don't force Git muscle
memory, jj log is the home screen.
Jujutsu documentation, CLI reference,
https://docs.jj-vcs.dev/latest/cli-reference, on jj git colocation disable
and --no-colocate, which prevent "Git tools that are unaware of jj and
regular Git commands from operating on the repo".