How to Migrate to jj Version Control (Git-Compatible) [2026]

A migration-first tutorial for Git teams: clone/init, learn jj’s working-copy model, map Git branches to bookmarks, ship stacked PRs, and keep CI unchanged.

Part of theDev Tools & AI Workflow series
Computer screen displaying lines of code
Listen to this article
--:--

If you want a Git-compatible workflow that makes rebasing and stacked work feel less like defusing a bomb, you can migrate to Jujutsu (jj) without changing your hosting, your CI, or your teammates’ Git-only setup.

Here’s the prerequisite that trips people up: `jj` is not “a nicer Git UI.” It’s a different mental model that happens to use a Git backend, so you can keep pushing to GitHub/GitLab as usual.

This tutorial is built around the target keyword jj version control git compatible because that’s the real question teams have: “Can I adopt this without breaking everything?”

What is Jujutsu (jj) version control?

Jujutsu (jj) is a version control system that can use Git as its storage backend. You work locally with jj’s workflow, and you still push and pull to regular Git remotes.

white and black remote control

The two ideas that make jj feel like “Git, but less fragile” are:

  • Working copy as a commit: you’re always sitting on a real commit, not some weird half-state split between an index and HEAD.
  • Safety for history edits: jj keeps an operation log so you can back out of a botched rewrite without turning your afternoon into a forensic exercise.

The project’s own docs describe jj as “a version control system” with deep Git interoperability, and that framing matters. jj isn’t asking your org to do a heroic migration to a new hosting platform. It’s saying: keep Git where it already works, and fix the parts that make day-to-day dev feel brittle.

If you want to go to the source: the official Jujutsu docs and the upstream repo jj-vcs/jj.

What does “Git-compatible” mean in practice?

“Git-compatible” is a phrase people throw around like it’s a magic stamp. With jj, it’s actually pretty literal:

a close up of a control panel with buttons and switches
  1. Your repo can be a normal Git repo on disk.
  2. Your remote can still be GitHub/GitLab/Bitbucket.
  3. Your CI can keep checking out with `git clone`.
  4. You can adopt jj per-developer, not as a flag day.

What it does not mean: “every Git command has a 1:1 jj equivalent.” jj has its own vocabulary, and pretending otherwise is how you end up frustrated.

If you want a concrete baseline for why this matters, look at how Git itself describes git rebase: it rewrites history by replaying commits, creates new commit IDs, and surfaces conflicts during the replay. That’s the pain point jj is responding to. The official git-rebase documentation is also a good reminder that Git keeps improving (the page notes updates in Git 2.54.0 on 2026-04-20), but the underlying model stays the underlying model.

If your team lives on stacked PRs and frequent history edits, Git’s “rewrite = new IDs everywhere” behavior is the part that starts to feel like sand in the gears.

Start with a Git repo: `jj git clone` vs `jj init`

There are two normal ways to start. No ceremony required.

Control panel with gauges, lights, and switches

Option A: Clone from a remote with jj

Use this when you’re starting fresh on a machine and the repo is already hosted.

  • jj git clone <git-remote-url> <dir>

You get a working copy that jj manages locally while still talking Git to the remote.

Option B: Initialize jj in an existing repo

Use this when you already have a local checkout you don’t want to throw away.

  • cd your-existing-repo
  • jj init

What gets created?

  • Your existing .git/ directory stays.
  • jj adds its own metadata directory (commonly .jj/).

I’m not going to pretend the internal directory layout is some sacred API. It has changed across releases. The migration point is simpler than the implementation detail: Git stays put. jj layers on top.

Day-1 checklist (Git team friendly)

When you introduce a workflow tool, “it works on my laptop” is irrelevant. The only thing that matters is whether you can adopt it without turning production into a science experiment.

Here’s the checklist I’d use:

  1. Make sure CI continues to run from Git pushes (no changes).
  2. Keep the default branch protected (same branch rules).
  3. Pick one repo and one developer for the first week.
  4. Agree on a “Git-only escape hatch” for every workflow (still possible).
  5. Write down the 5 commands the team will use daily.

If you’re doing stacked PRs today, read my Stacked PRs on GitHub post alongside this. jj makes stacks smoother, but the review mechanics still matter.

The jj working copy model: no staging area, always a commit

This is the part that either clicks instantly or makes you squint.

Git’s default model is:

  • working tree changes
  • staging/index
  • commit

jj is closer to:

  • your working copy changes are associated with a working-copy commit
  • you describe and reshape changes, instead of treating the staging area as the source of truth

If you’ve ever watched someone half-stage a file, commit the wrong hunks, then spend 20 minutes cleaning it up while muttering at their terminal, you already understand the appeal.

The practical impact on a team is real:

  • “What’s checked out?” becomes simpler because there’s less invisible state.
  • You’re encouraged to rewrite (split/squash/reorder) as part of normal work instead of treating it like a high-risk operation.

This kind of workflow pressure shows up the second teams start iterating faster. It’s the same reason I’m picky about developer-environment drift. When people’s shells and tooling setups diverge, the team ends up paying for it in debugging time and flaky scripts. I wrote up my approach in Reproducible Terminal Dev Environment: direnv + mise.

Translate Git mental models to jj (branches, commits, HEAD, remotes)

A lot of “jj vs git workflow” guides miss the real problem. Commands aren’t the problem. The problem is that your brain has 10+ years of Git shortcuts baked in.

Here’s the translation layer I use when onboarding Git-fluent engineers.

Git conceptjj conceptWhat changes for your workflow
BranchBookmarkBranches become less “where I am” and more “what I publish”
Commit (hash)Change (stable identity) + commitYou can rewrite without losing the logical thread
`HEAD``@` (current working-copy commit)Thinking becomes “what change am I on?”
RebaseRewriting changesSame goal, more safety and better stack ergonomics
`reflog`Operation logUndo becomes a normal tool, not archaeology
Remote branchesTracked bookmarksCleaner separation between what’s published and what’s local

The anchor concept: jj tracks a stable change ID distinct from commit IDs, so rewritten commits can still be treated as the same logical change. That comes directly from jj’s creator, Martin von Zweigbergk (Creator of Jujutsu).

This is the part where a lot of experienced Git users have the “wait… why didn’t Git do that?” reaction. Git commit hashes are content-addressed. That’s a feature. But it also means “amend” and “rebase” constantly invalidate identity. Change IDs keep your intent stable.

Common tasks in jj on a Git repo (with real commands)

This is the migration workhorse. You want the equivalents of your daily Git muscle memory.

Assumptions:

  • you’re on a repo that already has a Git remote
  • you’re using jj with a Git backend

Create a new change

In Git you’d create a branch, commit, and start stacking.

In jj, you typically start a new change from your current position:

  • jj new

Then edit files like you normally would.

Describe the change (commit message)

Instead of “commit now or later,” jj nudges you toward “name what this change is.” That turns out to matter when you have a stack and you’re trying to keep your own work straight.

  • jj describe

Amend / update the current change

In Git: edit + git commit --amend.

In jj, you update the working copy commit and then reshape as needed:

  • edit files
  • jj describe (if needed)

Split a change

Everyone ships the “one commit that is secretly three commits” at some point. The difference is whether your tools make that easy to fix or shameful to admit.

jj supports splitting changes as a normal operation:

  • jj split

Squash / combine changes

Stacked work often ends with “squash these two” before review.

  • jj squash

Rebase / reorder a stack

This is where Git users get tense, especially when they have 5 commits stacked and they need to edit the middle without nuking the whole thing.

  • jj rebase

Conflicts still exist. jj doesn’t delete reality. But the workflow around rewriting is built to be survivable because you can undo operations.

Resolve conflicts

jj treats conflicts as a first-class concept (there’s an entire “Conflicts” section in the docs navigation). That’s a good sign. It means the tool expects conflict resolution to be part of normal work, not an edge case.

A simple team policy that works:

  • resolve locally
  • run tests
  • only then push updated bookmarks

If you’re investing in workflow automation, it’s worth tightening your baseline Git safety too. My Advanced Git Commands Safely post pairs well with jj adoption because it forces the team to be explicit about rewrite operations.

Stacked PRs with jj: the workflow that makes the switch worth it

Stacked PRs are where jj actually earns its keep.

If you’re doing trunk-based development with short-lived branches, you can get away with “Git as usual.” The pain shows up when:

  • you have 3–7 dependent changes in flight
  • review feedback forces you to reorder work
  • you need to edit the middle change without doing surgery on the entire stack

A practical stacked workflow with jj looks like this:

  1. Create change A: jj new, work, jj describe
  2. Create change B on top: jj new, work, jj describe
  3. Create change C on top: jj new, work, jj describe
  4. Publish bookmarks for each change (more on push below)
  5. Reviewer asks: “move this helper into A”
    • update files
    • jj split or jj squash depending on direction
    • jj rebase to reorder
  6. Push updated bookmarks so PRs update

That sounds mundane until you do it a few times back-to-back.

In Git, you can absolutely run this workflow. People do. But you’re basically living in interactive rebase, and every mistake comes with that little spike of dread: “did I just lose something?” jj’s operation log and change-centric model take the edge off.

If you want the GitHub mechanics, including how to open and keep stacks reviewable, use my Stacked PRs on GitHub as the “hosting layer” and jj as the “local layer.”

Mental shift that matters: in jj, stacks feel like you’re editing a chain of changes. In Git, stacks feel like you’re editing a chain of commit hashes that keep changing.

Here’s a migration-oriented demo worth watching (I’m only embedding one because most people just want to answer the interop question quickly):

Here’s a practical walk-through:

GitHub/GitLab interop: pushing, pulling, protected branches, and CI

Teams don’t fail at adopting tools because the tool is hard. They fail because the tool collides with the boring stuff. Branch protections. CI checkout logic. The one weird hook someone added two years ago and nobody remembers.

Pushing changes to Git remotes

In jj, you usually push bookmarks to a Git remote. Think: “publish this pointer so Git hosting can see it.”

A simple policy that plays nicely with protected main:

  • never force-push main
  • only push bookmarks for review branches
  • keep merges happening through the host (GitHub/GitLab)

jj’s docs have a whole Git compatibility section in the sidebar, which is a strong signal that “push/pull to GitHub” wasn’t bolted on later.

Pulling / fetching

Treat remote updates the same way you do in Git:

  • fetch often
  • rebase / rewrite your stack when upstream changes

Keep CI unchanged

This is the migration trick that makes jj realistic for actual teams.

  • CI runners can continue doing git clone.
  • The repo in the remote is still Git.
  • Your branch protections still apply.

If you’re doing anything non-trivial in CI (secret scanning, policy checks, whatever), keep it. jj is a local workflow improvement, not a CI rewrite. My gitleaks + pre-commit + CI setup is an example of the kind of policy layer you don’t want to disturb during a VCS workflow migration.

Undo in jj: operation log vs `git reflog`

This is where jj is opinionated in a way I actually respect.

Git has reflog. It’s powerful. It’s also treated like a recovery spell you Google after you’ve already made a mess.

jj is more blunt about it. It maintains an operation log so you can restore previous repo states after rewrites.

The docs site lists “Operation log” as a core concept right alongside working copy, bookmarks, and conflicts. That’s not accidental. jj is telling you: “history editing is normal here. recovery should be normal too.”

A migration practice I like:

  • In week 1, deliberately do a “bad rebase” in a throwaway branch.
  • Then recover using jj’s undo/restore workflow.

People don’t adopt tools because the happy path is pretty. They adopt when the failure modes don’t ruin their day.

Collaborating with Git-only teammates

This is usually a political blocker, not a technical one.

If jj is using a Git backend, Git-only teammates can keep doing what they already do:

  • git clone
  • git checkout
  • open PRs
  • run CI

Your job is to keep the shared artifacts Git-native:

  • don’t introduce jj-only steps in CI
  • don’t require jj to review code
  • keep branch naming conventions consistent

If you’re leading adoption, treat jj like any other productivity tool rollout. Same principle as when I built a SOC 2 scaffolding CLI at Rise People in 2021–2022: baking the guardrails into the workflow beats relying on humans at PR time. jj is appealing because its safety rails (operation log + change IDs) are workflow-native.

When I would NOT use jj yet (even if you like it)

I like jj’s direction. I also don’t like lighting my team on fire.

I’d pause adoption if any of these are true:

  • You depend heavily on niche Git tooling that assumes branches behave a specific way.
  • Your repo uses submodules in complex ways and you can’t afford surprises.
  • You have strict compliance requirements around tooling and audit trails, and you haven’t validated jj’s workflow in your environment.
  • You’re already struggling with basic Git hygiene. jj won’t fix a team that force-pushes main and calls it “velocity.”

Also, jj is still an actively evolving project. The GitHub repo shows serious activity and adoption (the repository lists 31.4k stars at the time I pulled it), which is a healthy sign. But popularity isn’t the same thing as fit.

If you want the broader “developer workflow” framing, I collect these kinds of tools and practices under the workflow hub: Developer tools workflow.

My prediction: stacked PRs are going to become default for teams shipping fast. AI coding tools are only going to increase diff volume. Version control that makes history editing safe will stop being a niche interest, because the alternative is burning senior engineer time on avoidable rebases. Git won because it was flexible. The next wave wins by making the flexible path harder to mess up.

Photo by Bernd 📷 Dittrich on Unsplash.

Continue reading

terminal window code close up — illustration for article on How to Use Advanced Git Commands

How to Use Advanced Git Commands Safely [2026 Alias Kit]

A reflog-first way to rewrite history without fear: undo reset --hard, undo rebases, enable rerere, use worktrees daily, speed up clones, and install a hardened alias kit.

a person typing on a laptop computer on a desk

7 Metrics to Measure AI Coding Impact on Engineering Metrics [2026]

Stop justifying AI coding tools with “felt faster.” Here’s a team-level measurement framework for PR throughput, rework, defect escape, and code review load—with guardrails and rollout thresholds.

a blurry image of a bright orange and blue light

AI Coding Workflow 2026: What a YC Founder's Stack Taught Me About the Hard Parts [Guide]

AI didn't make coding easier — it eliminated the easy parts. Here's how I rebuilt my entire dev workflow around Claude Code, Cursor, and the brutal reality that architecture is now the whole job.

Abstract green digital pattern with vertical lines

Jujutsu (jj): The Git-Compatible Version Control Tool That Might Actually Fix Git's Worst Problems [2026]

Jujutsu doesn't replace Git — it wraps around it and fixes the parts that make you lose work. After using it on real repos, here's why 27,000+ developers on GitHub agree.

Cite this article
Kunal Ganglani (2026, September 5). How to Migrate to jj Version Control (Git-Compatible) [2026]. Kunal Ganglani. Retrieved September 5, 2026, from https://www.kunalganglani.com/blog/jj-version-control-git-compatible

Frequently Asked Questions

Is jj compatible with Git?

Yes. jj can use Git as its backend, which means you can work locally with jj and still push and pull to regular Git remotes like GitHub or GitLab. Your teammates can keep using Git if they want, as long as the shared repo remains Git.

How do you use jj with an existing Git repo?

Go into your existing repo and run `jj init` to add jj’s metadata while keeping the `.git` directory intact. From there, you can use jj commands locally and continue pushing to the same Git remotes.

What are change IDs in jj?

Change IDs are stable identifiers for a logical change, separate from the underlying commit hash. When you amend or rebase, the commit hash changes, but the change ID lets jj treat it as the same piece of work across rewrites.