Branching and Checkout
Branches in jj work differently than in git—they're just names for commits, not moving pointers.
Git's Branch Model
In git, a branch is a moving pointer that advances when you commit.
git checkout main # Move HEAD to main
git commit # main pointer advances
jj's Branch Model
In jj, a branch is a name pointing to a specific commit. The @ commit is what matters, not which branch you're "on".
# Create a branch pointing to current @
jj branch create my-feature
# @ doesn't change—just created a name
jj status
Creating Branches
jj branch create feature| git | jj |
|---|---|
| git branch feature | jj branch create feature |
| git checkout -b feature |
Switching Branches
jj edit main| git | jj |
|---|---|
| git checkout main | jj edit main |
In jj, jj edit creates a new working copy (@) from the target commit. It doesn't "move" anything—commits are immutable.
Listing Branches
jj branch list| git | jj |
|---|---|
| git branch | jj branch list |
The @ vs Branches
The key insight: @ is your working copy, regardless of branches.
# Current state
jj status
Working copy: @
Parent commit: xyzk
# Create branch pointing to @
jj branch create my-work
my-work: xyzk
# @ hasn't changed
jj status
Working copy: @
Parent commit: xyzk
Practical Example
Let's create a feature branch and work on it:
# 1. Start on main
jj edit main
# 2. Create a branch for your work
jj branch create feature-auth
# 3. Make changes (these go into @)
echo "auth code" > auth.js
# 4. Name the @ commit
jj describe -m "Add auth"
# 5. Move branch to point to @
jj branch set feature-auth
# 6. Create new @ for next work
jj new
Updating Branches
When you want a branch to point to your current @ commit:
# Make changes
jj describe -m "New feature"
# Move branch to @
jj branch set my-branch
# Verify
jj branch list
my-branch: xyzk # Now points to @
Deleting Branches
jj branch delete old-feature| git | jj |
|---|---|
| git branch -D old-feature | jj branch delete old-feature |
Branch vs Commit
In jj, you don't need branches to track work. You can work directly with commits:
# Create a commit and remember its change ID
jj describe -m "Experiment"
jj new
# Change ID: abc123
# Later, come back to it
jj edit abc123
# Create new work from there
jj describe -m "Continuing experiment"
jj new
In jj, change IDs are more stable than branch names. They survive rebases and can be used to track work across history rewrites.
Workflow Comparison
Feature branch workflow:
jj edit mainjj newvim app.jsjj describe -m "Add feature"jj branch create featurejj edit mainjj new| git | jj |
|---|---|
| git checkout main | jj edit main |
| git checkout -b feature | jj new |
| vim app.js | vim app.js |
| git add app.js | jj describe -m "Add feature" |
| git commit -m "Add feature" | jj branch create feature |
| git checkout main | jj edit main |
| git merge feature | jj new |
Abandoning Work
Instead of deleting branches, in jj you abandon commits:
# Made a mistake on @
jj describe -m "Bad idea"
# Change your mind
jj abandon
# @ is removed, parent becomes @
jj status
# @ now points to previous commit
Try It Yourself
Key Takeaways
- Branches are names for commits, not moving pointers
@is your working copy, independent of branchesjj branch createcreates a name for current @jj branch setmoves a branch to point to current @jj editcreates a new @ from target commit- Change IDs are more stable than branch names
Next Steps
Now let's look at how jj handles viewing history and diffs.