Welcome back, fellow Jujutsu explorers! You’ve mastered the core concepts of jj, from its unique working-copy-as-a-commit model to navigating mutable history and leveraging the powerful operation log. Now, it’s time to truly make jj your own.
In this chapter, we’ll dive deep into customizing jj to fit your personal workflow like a glove. We’ll explore configuration files, create powerful aliases for common commands, integrate jj with your favorite editors and diff tools, and even craft custom output templates. The goal is simple: to make your jj experience as efficient, intuitive, and productive as possible.
Think of this as equipping your jj toolkit with personalized shortcuts and powerful automation, transforming it from a robust VCS into an indispensable daily companion. Let’s get started!
The Heart of Customization: jj Configuration Files
Just like many powerful command-line tools, jj uses configuration files to let you tailor its behavior. These files are written in the TOML format, a simple, human-readable data serialization language.
What are jj Configuration Files?
jj configuration files are plain text files that store settings and preferences for how jj should operate. They allow you to define everything from how commit messages are formatted to which external tools jj should use for diffing or merging.
Why do they exist?
Configuration files exist to provide flexibility and consistency. Instead of typing out long command-line arguments every time, you can set defaults that jj automatically applies. This saves time, reduces errors, and ensures your preferred workflow is always active.
What problem do they solve?
They solve the problem of repetitive command-line input and the need to adapt jj’s default behavior to individual preferences or project requirements. Without them, jj would be less adaptable and more cumbersome for daily use.
Configuration Lookup Hierarchy
jj looks for configuration in a specific, hierarchical order. This allows you to set global defaults and then override them for specific repositories:
- System-wide configuration: (Least common for individual users)
- User-specific configuration:
~/.jjconfig.toml(or%USERPROFILE%\.jjconfig.tomlon Windows). This is your primary file for global settings, aliases, and tool preferences that apply to alljjrepositories. - Repository-specific configuration:
.jj/repo/config.tomlinside ajjrepository. Settings here will override anything in your user-specific file for that particular repository, allowing project-specific customizations.
This hierarchical approach ensures you can have consistent global settings but fine-tune behavior for specific projects without affecting others.
Explanation of the flow:
- When you run a
jjcommand, it first checks if you’re inside ajjrepository. - If a repository-specific
.jj/repo/config.tomlexists,jjreads those settings first. - Next, it reads your global
~/.jjconfig.tomlfile. - Any settings found in
~/.jjconfig.tomlwill be overridden by identical settings found earlier in.jj/repo/config.toml. - Finally, any settings not specified in either file will fall back to
jj’s built-in default values.
Step-by-Step Implementation: Setting Up Your Global ~/.jjconfig.toml
The ~/.jjconfig.toml file is where most of your personal customization will live. If it doesn’t exist, jj will simply use its default settings. Let’s create one and add a common preference.
First, open your terminal.
# Create an empty config file if it doesn't exist
touch ~/.jjconfig.toml
# Open it with your favorite text editor (e.g., nano, vim, code)
# For example, using nano:
nano ~/.jjconfig.toml
Now, let’s add a basic configuration that makes jj’s output more informative: always show the commit message when running jj log or jj status.
Add the following to your ~/.jjconfig.toml file:
# ~/.jjconfig.toml
# Configuration for Jujutsu (jj) VCS
# Last updated: 2026-05-19
[ui]
# Always show the commit message in `jj log` and `jj status` output.
log-template = "commit_id: {commit_id}\nauthor: {author}\ndate: {time}\n\n{description}"
How it functions:
[ui]declares a section for User Interface related settings. Sections help organize your configuration.log-templateis a string that defines howjjshould format the output of commands likejj logandjj status.{commit_id},{author},{time}, and{description}are placeholders.jjreplaces these with the actual data from each commit. For example,{commit_id}becomes the full commit hash, and{description}becomes the commit message. The\ncharacters create new lines in the output.
Save and close the file. Now, when you run jj log or jj status in a jj repository, you’ll see the commit message included automatically!
# Try it out in a jj repo (make sure you have some commits)
# If you don't have a jj repo, create one:
# mkdir my_jj_repo && cd my_jj_repo && jj init
# echo "Hello Jujutsu" > README.md && jj commit -m "Initial commit"
# echo "Another line" >> README.md && jj commit -m "Add another line"
jj log -r @
jj status
You should now see more verbose output, including the commit message, directly in jj log and jj status.
Boosting Efficiency with Aliases
Aliases are short, custom commands that expand into longer, more complex jj commands. They are a massive productivity booster, letting you type less and achieve more. If you find yourself typing a repetitive jj command sequence, it’s a perfect candidate for an alias.
What are jj Aliases?
jj aliases are custom shortcuts you define for jj commands. They allow you to assign a short, memorable name (like st for status) to a full jj command, including its arguments and options.
Why do they exist? Aliases exist to streamline your workflow and reduce cognitive load. They make frequently used commands quicker to execute and help bridge the gap for users migrating from other VCS tools (like Git) who are used to certain command shortcuts.
What problem do they solve?
They solve the problem of typing long or complex commands repeatedly. They also allow you to create custom workflows by chaining multiple commands or applying specific revsets and templates with a single, simple command.
Step-by-Step Implementation: Defining Aliases in ~/.jjconfig.toml
Aliases are defined in the [aliases] section of your ~/.jjconfig.toml (or repository config).
Let’s add some common aliases to our ~/.jjconfig.toml file. Open it again:
nano ~/.jjconfig.toml
Add the following lines to the end of your file:
# ~/.jjconfig.toml
# ... existing ui section ...
[aliases]
# 'jj st' for 'jj status' - a common Git alias habit
st = "status"
# 'jj co' for 'jj checkout' - another Git habit, now for 'jj edit'
# In jj, 'checkout' is similar to 'edit' for moving the working copy
co = "edit"
# 'jj up' for 'jj update' - frequently used to move the working copy
up = "update"
# 'jj prev' to go to the parent of the current working-copy commit
# '@-' is the revset for the parent of the working copy
prev = "edit @-"
# 'jj next' to go to the child of the current working-copy commit
# '@+' is the revset for the child of the working copy
next = "edit @+"
# 'jj tree' to show the commit graph with a simplified template
# This combines 'log' with a custom template for a tree-like view
tree = "log -T 'commit_id.short} {branches} {description.first_line() | indent(\" \")}'"
How it functions:
[aliases]marks the start of the aliases section in the TOML file.- Each line under
[aliases]defines an alias using the format:alias_name = "full_command_string". - When you type
jj alias_name,jjreplacesalias_namewithfull_command_stringand executes it. - Notice how the
treealias encapsulates alogcommand with arevset(-T '...'), demonstrating that aliases can indeed wrap complex command arguments and templates.
Save and close the file. Now you can use these shorter commands!
Practical Alias Examples
Let’s see these aliases in action. Navigate to a jj repository.
# Instead of 'jj status', you can now type:
jj st
# Instead of 'jj edit <commit_id>', you can use 'jj co' to edit the working copy
# For example, to edit the current commit:
jj co @
# Instead of 'jj update @', you can now type:
jj up
# To go to the parent commit of your current working copy:
jj prev
# To go to the first child commit of your current working copy:
jj next
# To view a simplified commit graph with our custom template:
jj tree
These small changes add up to significant time savings and a more fluid workflow.
Mini-Challenge: Create Your Own Alias
Challenge: Imagine you frequently need to view the current working-copy commit and all its immediate children, but only showing the short commit ID, the author, and the first line of the description. Create an alias called jj children-log that runs jj log -r '@ | children(@)' -T 'commit_id.short} {author.name}: {description.first_line()}'.
Hint: Add a new entry under the [aliases] section in your ~/.jjconfig.toml file. Remember the structure alias_name = "full_command_string".
What to observe/learn: How easy it is to extend jj’s functionality with simple aliases, and how revsets (like @ | children(@)) become even more powerful and accessible when combined with templates and aliased.
Integrating Your Favorite Tools: Editor and Diff Configuration
jj plays nicely with external tools. You can configure it to use your preferred text editor for commit messages and your favorite graphical diff tool for reviewing changes. This integration is key to a seamless development experience.
Configuring Your Editor for Commit Messages
By default, jj will use your system’s EDITOR environment variable. If that’s not set or you want to specify a different editor specifically for jj, you can configure it in ~/.jjconfig.toml.
Let’s set jj to use code --wait for VS Code, which is a popular choice. The --wait flag is crucial as it tells jj to wait for the editor to close the file before jj continues its operation.
Open ~/.jjconfig.toml again:
nano ~/.jjconfig.toml
Add the following to your [ui] section:
# ~/.jjconfig.toml
# ... existing sections ...
[ui]
# ... log-template ...
editor = "code --wait" # Use VS Code for commit messages and other edits
How it functions:
editor = "code --wait"tellsjjto launch thecodeexecutable with the--waitflag whenever it needs to open a file for editing. This happens, for example, when you runjj commitwithout a-mmessage, orjj describeto edit a commit’s description.- Important: Replace
"code --wait"with the command for your preferred editor and its “wait” flag."nvim --listen"for Neovim"subl --wait"for Sublime Text"atom --wait"for Atom"nano"or"vim"if you prefer terminal editors (they inherently wait).
Save and close. Now, try creating a commit without a message:
# Make a change, then commit without a message
echo "a new feature line" >> new_feature.txt
jj commit
Your configured editor should open with a temporary file for your commit message! Type your message, save, and close the editor. jj will then finalize the commit.
Configuring External Diff Tools
jj’s built-in diff is functional, but often a graphical diff tool provides a much better visual experience for comparing changes side-by-side. jj supports integration with many popular diff tools.
Let’s configure jj to use meld, a popular cross-platform diff viewer. If you don’t have meld, you can install it (e.g., sudo apt install meld on Ubuntu, brew install meld on macOS). If you prefer another tool like vscode, kdiff3, diffmerge, etc., the setup is similar.
Open ~/.jjconfig.toml one last time:
nano ~/.jjconfig.toml
Add the [merge-tools] section and configure meld, and then set it as your default diff-tool in the [ui] section:
# ~/.jjconfig.toml
# ... existing sections ...
[merge-tools]
# Define 'meld' as a merge tool
meld.program = "meld"
meld.args = ["$left", "$right", "$base", "$output"]
meld.diff = true
meld.merge = true
meld.conflict-markup = "diff3" # Use diff3 style for conflicts
# Set meld as the default for diffing
[ui]
# ... editor and log-template ...
diff-tool = "meld"
How it functions:
[merge-tools]is a section where you define the properties of external diff and merge tools.meld.program = "meld"specifies the executable command for the tool.meld.args = ["$left", "$right", "$base", "$output"]defines the argumentsjjpasses tomeld. These placeholders are crucial formeldto understand which files to compare:$left: Represents the “left” side of the comparison (e.g., the original file).$right: Represents the “right” side of the comparison (e.g., the changed file).$base: The common ancestor of the two files (used for 3-way merges).$output: The file where the merged result should be saved (only used during merge operations).
meld.diff = trueandmeld.merge = truetelljjthatmeldis capable of handling both diffing and merging scenarios.meld.conflict-markup = "diff3"specifies thatjjshould prepare files formeldusing thediff3style conflict markers, whichmeldunderstands well for 3-way merges.[ui] diff-tool = "meld"then setsmeldas the default tooljjshould use when you runjj diff.
Save and close. Now, when you have changes and run jj diff, your configured graphical diff tool will open!
# Make some additional changes to a file
echo "yet another line" >> new_feature.txt
# Run diff
jj diff
Your meld (or chosen tool) should pop up, showing the differences!
Jujutsu’s Approach to Automation: Beyond Traditional Hooks
In traditional VCS like Git, “hooks” are scripts that run automatically at specific points in the workflow (e.g., pre-commit, post-merge). These are powerful but can also be complex to manage and rely on an immutable history model.
jj takes a different approach. It does not have traditional Git-style hooks. This is a deliberate design choice rooted in jj’s core philosophy of mutable history and a robust operation log.
Why jj avoids traditional hooks:
- Mutable History:
jj’s history is designed to be easily rewritten. Hooks tied to specific commit hashes or merge operations might become invalid or behave unexpectedly when history is regularly changed. - Operation Log: The
jjoperation log provides a powerful undo/redo mechanism for any action. This often replaces the need for pre-emptive hooks to “prevent” bad commits, as you can alwaysjj undoorjj restore. - Atomic Changes:
jjviews the working copy as an ordinary commit, and operations are designed to be atomic and reversible. This reduces the need for external scripts to validate intermediate states.
How to achieve similar goals in jj:
CI/CD Pipelines: For critical checks like linting, testing, and formatting, integrate them into your Continuous Integration/Continuous Deployment (CI/CD) pipeline. This is generally a more robust and scalable solution, as these checks run on a clean environment and provide consistent feedback for all contributors, independent of local
jjconfiguration.External Scripting: Use build tools like
make,just,npm scripts, or simple shell scripts to run checks before you execute ajj commitorjj new. You can even create ajjalias for this:# ~/.jjconfig.toml # ... [aliases] # An alias to run linting/testing before committing # (Replace 'npm run lint && npm test' with your project's actual commands) commit-checked = "sh -c 'npm run lint && npm test && jj commit'"You would then manually run
jj commit-checkedinstead of justjj commit. This gives you explicit control over when checks run.Editor Integrations: Many modern editors have built-in linting and formatting on save, providing immediate feedback without needing a VCS hook. This is often the most immediate and least intrusive way to ensure code quality.
This approach aligns with jj’s principle of giving you powerful tools for history manipulation without locking you into a rigid, hook-based workflow.
Customizing Output with Templates and Revset Aliases
We briefly touched on log-template for jj log and jj status. jj offers a rich template language that allows you to fully customize how commit information is displayed. This is incredibly powerful for debugging, reviewing, and getting specific insights from your history.
What is jj’s Template Language?
jj’s template language is a powerful mini-language that lets you define exactly how jj formats output for commands like jj log, jj status, and jj diff. It uses curly braces {} for fields (like commit_id or description) and supports basic logic, filters, and formatting.
Why does it exist?
The template language exists to give you granular control over jj’s output. Default outputs are good, but often you need specific pieces of information, formatted in a particular way, to quickly understand your repository’s state or a commit’s details.
What problem does it solve?
It solves the problem of information overload or underload. You can tailor jj’s output to show precisely what you need, highlighting important details, filtering out noise, and presenting information in a consistent, readable format, whether for quick checks or detailed reviews.
Understanding and Using Templates
You can test templates directly on the command line using jj log -T followed by your template string.
Let’s try a more detailed template than our simple ui.log-template to see the power:
jj log -r @ -T '
📌 Commit: {commit_id.short} ({branches})
👤 Author: {author.name} <{author.email}>
🗓️ Date: {committer.timestamp.relative}
💬 Description:
{description}
📂 Files Changed: {files}
'
How it functions:
jj log -r @: This command tellsjjto log the current working-copy commit (@).-T '...': This flag specifies the template string to use for the output.{commit_id.short}: Displays a shortened version of the commit ID, making it more readable.{branches}: Shows any associated branch names that point to this commit.{author.name}and{author.email}: Access specific fields within theauthorstructure to display the author’s name and email.{committer.timestamp.relative}: Shows the commit time relative to the current moment (e.g., “2 hours ago”, “yesterday”).{description}: Inserts the full commit message.{files}: Lists the paths of all files that were changed in this commit.
This gives you a much richer and more structured view of a single commit’s details.
Step-by-Step Implementation: Creating revset Aliases with Custom Templates
You can combine revsets with custom templates in your ~/.jjconfig.toml to create highly specialized views that are always just a short alias away.
Let’s create an alias jj mylog that shows a compact, yet informative, view of your recent commits, similar to a Git “oneline” log but with jj’s flavor.
Open ~/.jjconfig.toml:
nano ~/.jjconfig.toml
Add a new alias under your [aliases] section:
# ~/.jjconfig.toml
# ... existing aliases ...
[aliases]
# ...
# 'mylog' shows a concise log with short ID, branch, author, relative date, and first line of description.
mylog = "log -T 'commit_id.short} {branches} {author.name} {committer.timestamp.relative}\n{description.first_line() | indent(\" \")}'"
How it functions:
mylog = "log -T '...'"defines the alias, tellingjjto runlogwith a specific template.commit_id.short}: Displays the abbreviated commit hash.{branches}: Shows associated branch names.{author.name}: Displays the author’s name.{committer.timestamp.relative}: Shows the time of the commit relative to now.{description.first_line()}: This is a template filter that extracts only the first line of the commit message.| indent(" "): This is another template filter. It takes the output of the preceding field (description.first_line()) and indents it by two spaces, making the description visually distinct from the header line.
Save and close. Now, run jj mylog to see your custom log output!
jj mylog
This is just the tip of the iceberg for jj’s template language. Refer to the official Jujutsu documentation on templates for a full list of fields, filters, and advanced usage.
Environment Variables for Fine-Grained Control
While jjconfig.toml handles most configuration, some aspects can be controlled via environment variables. These are typically used for temporary overrides or for settings that interact with the system environment.
A few notable environment variables (though less frequently used for daily customization than jjconfig.toml):
JJ_EDITOR: Temporarily overrides theeditorconfigured injjconfig.tomlfor a single command.JJ_PAGER: Specifies the pager program (likeless) to use forjj’s output, overriding system defaults orjjconfig.tomlsettings.JJ_REASON: Sets a default reason string for commands likejj undo, which usually prompt for a reason for the operation.
Example: Temporarily use vim as your editor for a specific commit:
JJ_EDITOR=vim jj commit
This command would open vim for the commit message, even if your jjconfig.toml specifies code --wait. This is useful for quick, one-off overrides without changing your permanent configuration.
Productivity Tips & Best Practices Recap
Beyond explicit customization, adopting certain jj workflows inherently boosts productivity and makes you a more effective developer:
- 📌 Embrace the Operation Log: Make
jj op logandjj undoyour best friends. Don’t fear making mistakes;jjmakes it trivial to revert almost any operation. This encourages experimentation and reduces mental overhead, knowing you can always go back. - 🧠 Leverage Stacked Changes: For feature development, create small, focused commits stacked on top of each other. This makes code reviews easier, allows for incremental testing, and gives you flexibility to
jj rebaseandjj squashthem into a clean, linear history before sharing. - ⚡ Think Branchless:
jj’s model naturally encourages a more linear, branchless workflow. Instead of creating many short-lived Git branches, usejj newto create new working-copy commits directly and manage their position withjj rebase. This simplifies history and avoids “branch spaghetti.” - 🔥 Master
revsets: The more comfortable you are withrevsets, the faster and more precisely you can navigate, select, and manipulate commits. Practice simplerevsetslike@(working copy),@-(parent),children(@)(children),root()(initial commit), andmain()(the main branch head). - ⚡ Regularly
jj pullandjj rebase -r main: Keep your local history up-to-date with the remotemainbranch. This minimizes merge conflicts, keeps your work based on the latest codebase, and ensures your changes integrate smoothly.
Common Pitfalls & Troubleshooting
Even with powerful customization options, you might encounter a few bumps along the road. Here’s how to navigate common issues:
Over-customizing or Conflicting Configurations:
- Pitfall: Having too many aliases or overly complex templates can make
jjharder to use, especially if you forget what your aliases do or if they conflict with futurejjcommands. Also, conflicting settings between your global~/.jjconfig.tomland a repository-specific.jj/repo/config.tomlcan lead to unexpected behavior. - Troubleshooting:
- Start with a few essential aliases and templates, then add more incrementally as you identify repetitive tasks.
- If
jjisn’t behaving as expected, check both your global~/.jjconfig.tomland the repository-specific.jj/repo/config.toml(if you’re in a repo). Remember the hierarchy: repo config overrides user config. - You can temporarily disable your global config by moving it (
mv ~/.jjconfig.toml ~/.jjconfig.toml.bak) or commenting out sections to see if the issue persists. - Use
jj debug configto see the effective configurationjjis currently using, which can reveal overrides or unexpected values.
- Pitfall: Having too many aliases or overly complex templates can make
Forgetting
--waitfor Editor Configuration:- Pitfall: If your
editorconfiguration doesn’t include the--waitflag (or its equivalent for your chosen editor),jjmight continue immediately after launching the editor, potentially leading to empty commit messages, premature command execution, or other issues. - Troubleshooting: Always ensure your editor command includes the necessary flag for
jjto wait until the editor closes the file. Common ones are--wait(VS Code, Sublime Text, Atom),--listen(Neovim), or no flag for terminal-based editors likenanoorvim.
- Pitfall: If your
Complex Template Syntax Errors:
- Pitfall: The
jjtemplate language can be powerful but also prone to syntax errors, especially with nested calls, complex filters, or incorrect field names. A small typo can break the entire template. - Troubleshooting:
- Test complex templates incrementally on the command line using
jj log -T '...'. Build them piece by piece. - Refer to the official template documentation for correct syntax, available fields, and filters. This is your authoritative guide.
- Break down complex templates into smaller, simpler parts. If a filter isn’t working, test the field it operates on by itself first.
- Test complex templates incrementally on the command line using
- Pitfall: The
Summary
You’ve now learned how to personalize your jj experience, transforming it into an even more powerful and efficient tool for your daily development.
Here are the key takeaways from this chapter:
- Configuration Hierarchy:
jjuses~/.jjconfig.tomlfor global settings and.jj/repo/config.tomlfor repository-specific overrides, with repo settings taking precedence. - Aliases for Productivity: Define custom shortcuts in the
[aliases]section of your config file to simplify complex or frequently used commands, significantly boosting your typing speed and reducing errors. - Tool Integration: Configure
jjto use your preferred text editor for commit messages ([ui] editor = ...) and external graphical diff tools ([merge-tools]and[ui] diff-tool = ...) for a seamless visual experience. - No Traditional Hooks:
jjintentionally omits Git-style hooks due to its mutable history and robust operation log. Achieve similar automation goals through CI/CD pipelines, external scripts, or editor integrations. - Custom Output: Leverage
jj’s powerful template language withjj log -Tandrevsetaliases to tailor output formats for specific insights, debugging, and review processes. - Environment Variables: Use environment variables (like
JJ_EDITOR) for temporary overrides ofjjsettings without altering your permanent configuration. - Best Practices: Reinforce good
jjhabits like embracing the operation log, leveraging stacked changes, thinking branchlessly, and masteringrevsetsfor maximum productivity and a smoother workflow.
With these customization techniques, you’re well on your way to becoming a jj power user, optimizing your workflow for speed, clarity, and control.
What’s next? In our final chapter, we’ll bring everything together, discussing how jj scales to large projects, advanced debugging strategies, and a comprehensive guide to migrating from Git, solidifying your journey to jj mastery!
References
- Jujutsu GitHub Repository
- Jujutsu Configuration Documentation
- Jujutsu Template Language Documentation
- Jujutsu Aliases Documentation
- TOML (Tom’s Obvious, Minimal Language) Specification
This page is AI-assisted and reviewed. It references official documentation and recognized resources where relevant.