Jj Private Commits

This is an LLM ass-isted document,
as are all in this subdirectory.

Met as a flag first: jj git push --help lists --allow-private next to --allow-empty-description, and the name raised the question. A private commit is any commit matching the revset in git.private-commits. jj git push refuses to publish it — and everything descended from it — unless told otherwise. Nothing else changes: it is a push guard, not a hiding mechanism. The name echoes Mercurial's "secret" phase1.

1. What counts

The default is none(): nothing is private until configured2. The documentation's own example labels work in progress:

[git]
# Prevent pushing work in progress or anything explicitly labeled "private"
private-commits = "description('wip:') | description('private:')"

set repo-locally with:

jj config set --repo git.private-commits "description('wip:') | description('private:')"

Three refinements3:

  • a commit already on the remote is no longer private — the guard bites only before first publication;
  • immutable commits are excluded from the set;
  • descendants cannot be pushed past a private commit, since that would publish the private ancestor.

2. Pushing anyway

jj git push --allow-private

3. Getting past one

Nothing happens implicitly: a private commit inside the push range simply aborts the push, and jj never folds content on its own. Two explicit ways out besides --allow-private:

  • Un-private it. Usually only the description makes it private: rewrite it (jj describe) without the marker the revset keys on, and the commit pushes like any other.
  • Squash it away. Sitting on the child, jj squash --from @- folds the private changes into @; the emptied source is abandoned automatically, descendants rebase, and the child pushes alone4.

4. Caveats

  • It is push-only: jj log shows private commits like any other, and there is no private() revset to query the set (one is being argued for)1.
  • Description-based rules meet this repo's taste for empty messages (JjVsGitHabit): an empty description matches description(exact:""), never description('wip:').

October, 2026.

Footnotes:

1

jj-vcs/jj issue #9308, FR: Add builtinprivateroots() revset, https://github.com/jj-vcs/jj/issues/9308, which draws the Mercurial-phases analogy: the git.private-commits option and the --allow-private flag exist "to prevent accidentally pushing commits you didn't mean to".

2

cli/src/config-schema.json at v0.45.1: "Revset of commits to refuse to push to remotes", default none(), https://github.com/jj-vcs/jj/blob/v0.45.1/cli/src/config-schema.json.

3

Jujutsu documentation, Configuration, on git.private-commits, https://docs.jj-vcs.dev/latest/config.

4

CLI reference, on jj squash: "If, after moving changes out, the source revision is empty compared to its parent(s), and --keep-emptied is not set, it will be abandoned"; and of combined descriptions, "if either was empty, the other one will be used", https://docs.jj-vcs.dev/latest/cli-reference.