# Technical Documentation: jj-vcs/jj

> ℹ️ **Provenance:** Multi-Tier Ingestion · [CodeWiki Reference](https://codewiki.google/github.com/jj-vcs/jj)

### Overview (`README.md`)

<div class="title-block" style="text-align: center;" align="center">

# Jujutsu—a version control system

<p><img title="jj logo" src="docs/images/jj-logo.svg" width="320" height="320"></p>

[](https://github.com/jj-vcs/jj/releases)
[](https://github.com/jj-vcs/jj/releases)
<br/>
[](https://github.com/jj-vcs/jj/blob/main/LICENSE)
[](https://discord.gg/dkmfj3aGQN)
[](https://web.libera.chat/?channel=#jujutsu)

**[Homepage] &nbsp;&nbsp;&bull;&nbsp;&nbsp;**
**[Installation] &nbsp;&nbsp;&bull;&nbsp;&nbsp;**
**[Getting Started] &nbsp;&nbsp;&bull;&nbsp;&nbsp;**
**[Development Roadmap] &nbsp;&nbsp;&bull;&nbsp;&nbsp;**
**[Contributing](#contributing)**

[Homepage]: https://www.jj-vcs.dev
[Installation]: https://docs.jj-vcs.dev/latest/install-and-setup
[Getting Started]: https://docs.jj-vcs.dev/latest/tutorial
[Development Roadmap]: https://docs.jj-vcs.dev/latest/roadmap

</div>

## Introduction

Jujutsu is a powerful [version control system](https://en.wikipedia.org/wiki/Version_control)
for software projects. You use it to get a copy of your code, track changes
to the code, and finally publish those changes for others to see and use.
It is designed from the ground up to be easy to use—whether you're new or
experienced, working on brand new projects alone, or large scale software
projects with large histories and teams.

Jujutsu is unlike most other systems, because internally it abstracts the user
interface and version control algorithms from the *storage systems* used to
serve your content. This allows it to serve as a VCS with many possible physical
backends, that may have their own data or networking models—like [Mercurial] or
[Breezy], or hybrid systems like Google's cloud-based design, [Piper/CitC].

[Mercurial]: https://www.mercurial-scm.org/
[Breezy]: https://www.breezy-vcs.org/
[Piper/CitC]: https://youtu.be/W71BTkUbdqE?t=645

Today, we use Git repositories as a storage layer to serve and track content,
making it **compatible with many of your favorite Git-based tools, right now!**
However, note that only commits and files are stored in Git; bookmarks
(branches) and other higher-level metadata are stored in custom storage outside
of Git. All core developers use Jujutsu to develop Jujutsu, right here on
GitHub. But it should hopefully work with your favorite Git forges, too.

We combine many distinct design choices and concepts from other version control
systems into a single tool. Some of those sources of inspiration include:

- **Git**: We make an effort to [be fast][perf]—with a snappy UX, efficient
  algorithms, correct data structures, and good-old-fashioned attention to
  detail. The default storage backend uses Git repositories for "physical
  storage", for wide interoperability and ease of onboarding.

- **Mercurial & Sapling**: There are many Mercurial-inspired features, such as
  the [revset] language to select commits. There is [no explicit index][no-index]
  or staging area. Branches are "anonymous" like Mercurial, so you don't need
  to make up a name for each small change. Primitives for rewriting history are
  powerful and simple. Formatting output is done with a robust template language
  that can be configured by the user.

- **Darcs**: Jujutsu keeps track of conflicts as [first-class
  objects][conflicts] in its model; they are first-class in the same way commits
  are, while alternatives like Git simply think of conflicts as textual diffs.
  While not as rigorous as systems like Darcs (which is based on a formalized
  theory of patches, as opposed to snapshots), the effect is that many forms of
  conflict resolution can be performed and propagated automatically.

[perf]: https://github.com/jj-vcs/jj/discussions/49
[revset]: https://docs.jj-vcs.dev/latest/revsets/
[no-index]: https://docs.jj-vcs.dev/latest/git-comparison/#the-index
[conflicts]: https://docs.jj-vcs.dev/latest/conflicts/

And it adds several innovative, useful features of its own:

- **Working-copy-as-a-commit**: Changes to files are [recorded automatically][wcc]
  as normal commits, and amended on every subsequent change. This "snapshot"
  design simplifies the user-facing data model (commits are the only visible
  object), simplifies internal algorithms, and completely subsumes features like
  Git's stashes or the index/staging-area.

- **Operation log & undo**: Jujutsu records every operation that is performed on the
  repository, from commits, to pulls, to pushes. This makes debugging problems like
  "what just happened?" or "how did I end up here?" easier, *especially* when
  you're helping your coworker answer those questions about their repository!
  And because everything is recorded, you can undo that mistake you just made
  with ease. Version control has finally entered [the 1960s][undo-history]!

- **Automatic rebase and conflict resolution**: When you modify a commit, every
  descendent is automatically rebased on top of the freshly-modified one. This
  makes "patch-based" workflows a breeze. If you resolve a conflict in a commit,
  the _resolution_ of that conflict is also propagated through descendants as
  well. In effect, this is a completely transparent version of `git rebase
  --update-refs` combined with `git rerere`, supported by design.

> [!WARNING]
> The following features are available for use, but experimental; they may have
> bugs, backwards incompatible storage changes, and user-interface changes!

- **Safe, concurrent replication**: Have you ever wanted to store your version
  controlled repositories inside a Dropbox folder? Or continuously backup
  repositories to S3? No? Well, now you can!

  The fundamental problem with using filesystems like Dropbox and backup tools
  like `rsync` on your typical Git/Mercurial repositories is that they rely
  on *local filesystem operations* being atomic, serialized, and non-concurrent
  with respect to other reads and writes—which is _not_ true when operating on
  distributed file systems, or when operations like concurrent file copies (for
  backup) happen while lock files are being held.

  Jujutsu is instead designed to be [safe under concurrent scenarios][conc-safety];
  simply using rsync or Dropbox and then using that resulting repository
  should never result in a repository in a *corrupt state*. The worst that
  _should_ happen is that it will expose conflicts between the local and remote
  state, leaving you to resolve them.

[wcc]: https://docs.jj-vcs.dev/latest/working-copy/
[undo-history]: https://en.wikipedia.org/wiki/Undo#History
[conc-safety]: https://docs.jj-vcs.dev/latest/technical/concurrency/

The command-line tool is called `jj` for now because it's easy to type and easy
to replace (rare in English). The project is called "Jujutsu" because it matches
"jj".

Jujutsu is relatively young, with lots of work to still be done. If you have any
questions, or want to talk about future plans, please join us on Discord
[](https://discord.gg/dkmfj3aGQN),
start a [GitHub Discussion](https://github.com/jj-vcs/jj/discussions), or
send an IRC message to [`#jujutsu` on Libera
Chat](https://web.libera.chat/?channel=#jujutsu). The developers monitor all of
these channels[^bridge].

[^bridge]: To be more precise, the `#jujutsu` Libera IRC channel is bridged to
one of the channels on jj's Discord. Some of the developers stay on Discord and
use the bridge to follow IRC.

### News and Updates 📣

- **December 2024**: The `jj` Repository has moved to the `jj-vcs` GitHub
  organization.
- **November 2024**: Version 0.24 is released which adds `jj file annotate`,
  which is equivalent to `git blame` or `hg annotate`.
- **September 2024**: Martin gave a [presentation about Jujutsu][merge-vid-2024] at
  Git Merge 2024.
- **Feb 2024**: Version 0.14 is released, which deprecates ["jj checkout" and "jj merge"](CHANGELOG.md#0140---2024-02-07),
  as well as `jj init --git`, which is now just called `jj git init`.
- **Oct 2023**: Version 0.10.0 is released! Now includes a bundled merge and
  diff editor for all platforms, "immutable revsets" to avoid accidentally
  `edit`-ing the wrong revisions, and lots of polish.
- **Jan 2023**: Martin gave a presentation about Google's plans for Jujutsu at
  Git Merge 2022!
  See the [slides][merge-slides] or the [recording][merge-talk].

### Related Media

- **Mar 2024**: Chris Krycho started [a YouTube series about Jujutsu][krycho-yt].
- **Feb 2024**: Chris Krycho published an article about Jujutsu called [jj init][krycho]
  and Steve Klabnik followed up with the [Jujutsu Tutorial][klabnik].
- **Jan 2024**: Jujutsu was featured in an LWN.net article called
  [Jujutsu: a new, Git-compatible version control system][lwn].
- **Jan 2023**: Martin's Talk about Jujutsu at Git Merge 2022, [video][merge-talk]
  and the associated [slides][merge-slides].

The wiki also contains a more extensive list of [media references][wiki-media].

[krycho-yt]: https://www.youtube.com/playlist?list=PLelyiwKWHHAq01Pvmpf6x7J0y-yQpmtxp
[krycho]: https://v5.chriskrycho.com/essays/jj-init/
[klabnik]: https://steveklabnik.github.io/jujutsu-tutorial/
[lwn]: https://lwn.net/Articles/958468/
[merge-talk]: https://www.youtube.com/watch?v=bx_LGilOuE4
[merge-slides]: https://docs.google.com/presentation/d/1F8j9_UOOSGUN9MvHxPZX_L4bQ9NMcYOp1isn17kTC_M/view
[merge-vid-2024]: https://www.youtube.com/watch?v=LV0JzI8IcCY
[wiki-media]: https://github.com/jj-vcs/jj/wiki/Media

## Getting started

> [!IMPORTANT]
> Jujutsu is an **experimental version control system**. While Git compatibility
> is stable, and most developers use it daily for all their needs, there may
> still be work-in-progress features, suboptimal UX, and workflow gaps that make
> it unusable for your particular use.

Follow the [installation
instructions](https://docs.jj-vcs.dev/latest/install-and-setup) to
obtain and configure `jj`.

The best way to get started is probably to go through [the
tutorial](https://docs.jj-vcs.dev/latest/tutorial). Also see the [Git
comparison](https://docs.jj-vcs.dev/latest/git-comparison), which
includes a table of `jj` vs. `git` commands.

As you become more familiar with Jujutsu, the following resources may be helpful:

- The [FAQ](https://docs.jj-vcs.dev/latest/FAQ).
- The [Glossary](https://docs.jj-vcs.dev/latest/glossary).
- The `jj help` command (e.g. `jj help rebase`).
- The `jj help -k <keyword>` command (e.g. `jj help -k config`). Use `jj help --help`
  to see what keywords are available.

If you are using a **prerelease** version of `jj`, you would want to consult
[the docs for the prerelease (main branch)
version](https://docs.jj-vcs.dev/prerelease/). You can also get there
from the docs for the latest release by using the website's version switcher. The version switcher is visible in
the header of the website when you scroll to the top of any page.

## Features

### Compatible with Git

Jujutsu is designed so that the underlying data and storage model is abstract.
Today, only the Git backend is production-ready. The Git backend uses the
[gitoxide](https://github.com/Byron/gitoxide) Rust library.

[backends]: https://docs.jj-vcs.dev/latest/glossary#backend

The Git backend is fully featured and maintained, and allows you to use Jujutsu
with any Git remote. The commits you create will look like regular Git commits.
You can fetch branches from a regular Git remote and push branches to the
remote. You can always switch back to Git.

Here is how you can explore a GitHub repository with `jj`.

<img src="demos/git_compat.png" />

You can even have a [colocated local
workspace](https://docs.jj-vcs.dev/latest/git-compatibility#colocated-jujutsugit-workspaces)
where you can use both `jj` and `git` commands interchangeably.

### The working copy is automatically committed

Jujutsu uses a real commit to represent the working copy. Checking out a commit
results in a new working-copy commit on top of the target commit. Almost all
commands automatically amend the working-copy commit.

The working-copy being a commit means that commands never fail because the
working copy is dirty (no "error: Your local changes to the following
files..."), and there is no need for `git stash`. Also, because the working copy
is a commit, commands work the same way on the working-copy commit as on any
other commit, so you can set the commit message before you're done with the
changes.

<img src="demos/working_copy.png" />

### The repo is the source of truth

With Jujutsu, the working copy plays a smaller role than with Git. Commands
snapshot the working copy before they start, then they update the repo, and then
the working copy is updated (if the working-copy commit was modified). Almost
all commands (even checkout!) operate on the commits in the repo, leaving the
common functionality of snapshotting and updating of the working copy to
centralized code. For example, `jj restore` (similar to `git restore`) can
restore from any commit and into any commit, and `jj describe` can set the
commit message of any commit (defaults to the working-copy commit).

### Entire repo is under version control

All operations you perform in the repo are recorded, along with a snapshot of
the repo state after the operation. This means that you can easily restore to
an earlier repo state, simply undo your operations one-by-one or even _revert_ a
particular operation which does not have to be the most recent one.

<img src="demos/operation_log.png" />

### Conflicts can be recorded in commits

If an operation results in
[conflicts](https://docs.jj-vcs.dev/latest/glossary#conflict),
information about those conflicts will be recorded in the commit(s). The
operation will succeed. You can then resolve the conflicts later. One
consequence of this design is that there's no need to continue interrupted
operations. Instead, you get a single workflow for resolving conflicts,
regardless of which command caused them. This design also lets Jujutsu rebase
merge commits correctly (unlike both Git and Mercurial).

Basic conflict resolution:

<img src="demos/resolve_conflicts.png" />

Juggling conflicts:

<img src="demos/juggle_conflicts.png" />

### Automatic rebase

Whenever you modify a commit, any descendants of the old commit will be rebased
onto the new commit. Thanks to the conflict design described above, that can be
done even if there are conflicts. Bookmarks pointing to rebased commits will be
updated. So will the working copy if it points to a rebased commit.

### Comprehensive support for rewriting history

Besides the usual rebase command, there's `jj describe` for editing the
description (commit message) of an arbitrary commit. There's also `jj diffedit`,
which lets you edit the changes in a commit without checking it out. To split
a commit into two, use `jj split`. You can even move part of the changes in a
commit to any other commit using `jj squash -i --from X --into Y`.

## Status

The tool is fairly feature-complete, but some important features like support
for Git submodules are not yet completed. There
are also several performance bugs. It's likely that workflows and setups
different from what the core developers use are not well supported, e.g. there
is no native support for email-based workflows.

Today, all core developers use `jj` to work on `jj`. I (Martin von Zweigbergk)
have almost exclusively used `jj` to develop the project itself since early
January 2021. I haven't had to re-clone from source (I don't think I've even had
to restore from backup).

There *will* be changes to workflows and backward-incompatible changes to the
on-disk formats before version 1.0.0. For any format changes, we'll try to
implement transparent upgrades (as we've done with recent changes), or provide
upgrade commands or scripts if requested.

## Related work

There are several tools trying to solve similar problems as Jujutsu. See
[related work](https://docs.jj-vcs.dev/latest/related-work) for details.

## Contributing

We welcome outside contributions, and there's plenty of things to do, so
don't be shy. Please ask if you want a pointer on something you can help with,
and hopefully we can all figure something out.

We do have [a few policies and
suggestions](https://docs.jj-vcs.dev/prerelease/contributing/)
for contributors. The broad TL;DR:

- Bug reports are very welcome!
- Every commit that lands in the `main` branch is code reviewed.
- Please behave yourself, and obey the Community Guidelines.
- There **is** a mandatory CLA you must agree to. Importantly, it **does not**
  transfer copyright ownership to Google or anyone else; it simply gives us the
  right to safely redistribute and use your changes.

### Mandatory Google Disclaimer

I (Martin von Zweigbergk, <martinvonz@google.com>) started Jujutsu as a hobby
project in late 2019, and it has evolved into my full-time project at Google,
with several other Googlers (now) assisting development in various capacities.
That said, while Google is one of the main contributors, **this is not a
supported Google product**, i.e. support is provided by the community.

## License

Jujutsu is available as Open Source Software, under the Apache 2.0 license. See
[`LICENSE`](./LICENSE) for details about copyright and redistribution.

The `jj` logo was contributed by J. Jennings and is licensed under a Creative
Commons License, see [`docs/images/LICENSE`](docs/images/LICENSE).

---

### Overview (`demos/README.md`)

# Screenshots with demos and scripts to generate them

The `demo_*.sh` scripts in this directory demo
various features of `jj`.

The `run_scripts.sh` script can be used to:

- Run them inside a standardized environment instead of the user's local
  environment.
- Generate SVG and PNG images for the scripts.

The PNG images in the repo may be slightly older, as they take up more space in
the repo.

The SVG images have human-readable diffs, but may look different on different
computers with different fonts installed.

## Running `run_scripts.sh`

This requires ImageMagick and `term-transcript-cli` to be installed. See
`run_scripts.sh`'s error messages for some more details. On Debian Linux, it
also seems helpful to `sudo apt install inkscape`; ImageMagick seems to use
either Inkscape itself or some dependency of it.

One way to make all the images and check the output is:

```shell
cd demos
./run_scripts.sh demo_*.sh |less
```

### A note on fonts

The exact PNG output depends on the fonts you have installed on your system.

The screenshots are usually generated on a Debian Linux system and use the "Fira
Code" font. It can be installed with `sudo apt install fonts-firacode`. It seems
to include all relevant Unicode symbols and be a little bolder and thus more
readable than the "Liberation Mono" font, which is used if Fira Code is not
installed. That font also works OK. See the CSS font specification in
`run_scripts.sh` for other fonts tried (especially when viewing SVGs on the
web). If none apply, the default `monospace` font will be used.

`convert -list Fonts` will list the fonts ImageMagick is aware of.

---

### Overview (`web/docs/README.md`)

# docs.jj-vcs.dev

[![Built with
Starlight](https://astro.badg.es/v2/built-with-starlight/tiny.svg)](https://starlight.astro.build)

This application powers https://docs.jj-vcs.dev.

## 🧞 Commands

All commands are run from this directory:

| Command                   | Action                                           |
| :------------------------ | :----------------------------------------------- |
| `npm install`             | Installs dependencies                            |
| `npm run dev`             | Starts local dev server at `localhost:4321`      |
| `npm run build`           | Build your production site to `./dist/`          |
| `npm run preview`         | Preview your build locally, before deploying     |
| `npm run astro ...`       | Run CLI commands like `astro add`, `astro check` |
| `npm run astro -- --help` | Get help using the Astro CLI                     |

## 👀 Want to learn more?

Check out [Starlight’s docs](https://starlight.astro.build/), read [the Astro documentation](https://docs.astro.build), or jump into the [Astro Discord server](https://astro.build/chat).

---

### Overview (`cli/src/config/README.md`)

These files are compiled into the binary; they are not installed to the user's
system.

---

### Overview (`lib/src/config/README.md`)

These files are compiled into the binary; they are not installed to the user's
system.

---

### Index (`docs/index.md`)

# Jujutsu—a version control system

<figure>
<img title="jj logo" src="images/jj-logo.svg" width="320" height="320">
</figure>

## Welcome to `jj`'s documentation website!


The complete list of the available documentation pages is located in
the sidebar on the left of the page. The sidebar may be hidden; if so,
you can open it either by widening your browser window or by clicking
on the hamburger menu that appears in this situation.

Additional help is available using the `jj help` command if you have
`jj` installed.

You may want to jump to:

- Documentation for the [latest released version of `jj`](https://docs.jj-vcs.dev/latest).
- Documentation for the [unreleased version of `jj`](https://docs.jj-vcs.dev/prerelease). This version of the docs corresponds to the `main` branch of the `jj` repo.

## Some useful links

- [GitHub repo for `jj`](https://github.com/jj-vcs/jj)
- Overview of `jj` in the repo's [README](https://github.com/jj-vcs/jj?tab=readme-ov-file#readme)
- [Installation and setup](install-and-setup.md)
- [Tutorial and bird's eye view](tutorial.md)
- [Working with Gerrit](gerrit.md)
- [Working with GitHub](github.md)
- [Development roadmap](roadmap.md)
- [Changelog](changelog.md)

---

### Web Docs Src Content Docs Index (`web/docs/src/content/docs/index.md`)

---
title: "Jujutsu—a version control system"
---

<figure>
<img title="jj logo" src="images/jj-logo.svg" width="320" height="320">
</figure>

## Welcome to `jj`'s documentation website!


The complete list of the available documentation pages is located in
the sidebar on the left of the page. The sidebar may be hidden; if so,
you can open it either by widening your browser window or by clicking
on the hamburger menu that appears in this situation.

Additional help is available using the `jj help` command if you have
`jj` installed.

You may want to jump to:

- Documentation for the [latest released version of `jj`](https://docs.jj-vcs.dev/latest).
- Documentation for the [unreleased version of `jj`](https://docs.jj-vcs.dev/prerelease). This version of the docs corresponds to the `main` branch of the `jj` repo.

## Some useful links

- [GitHub repo for `jj`](https://github.com/jj-vcs/jj)
- Overview of `jj` in the repo's [README](https://github.com/jj-vcs/jj?tab=readme-ov-file#readme)
- [Installation and setup](install-and-setup.md)
- [Tutorial and bird's eye view](tutorial.md)
- [Working with Gerrit](gerrit.md)
- [Working with GitHub](github.md)
- [Development roadmap](roadmap.md)
- [Changelog](changelog.md)

---

### FAQ (`docs/FAQ.md`)

# Frequently asked questions

### Why does my bookmark not move to the new commit after `jj new/commit`?

If you're familiar with Git, you might expect the current bookmark to move forward
when you commit. However, Jujutsu does not have a concept of a "current bookmark".

To move bookmarks, use `jj bookmark move`.

### I made a commit and `jj git push --all` says "Nothing changed" instead of pushing it. What do I do?

`jj git push --all` pushes all _bookmarks_, not all revisions. You have two
options:

* Using `jj git push --change` will automatically create a bookmark and push it.
* Using `jj bookmark` commands to create or move a bookmark to either the commit
  you want to push or a descendant of it. Unlike Git, Jujutsu doesn't do this
  automatically (see previous question).

### Where is my commit, why is it not visible in `jj log`?

Is your commit visible with `jj log -r 'all()'`?

If yes, you should be aware that `jj log` only shows a subset of the commits in
the repo by default. Most commits that exist on a remote are not shown. Local
commits and their immediate parents (for context) are shown. The thinking is
that you are more likely to interact with this set of commits. You can configure
the set of revisions to show by default by overriding `revsets.log` as described
in [config].

If not, the revision may have been abandoned (e.g. because you
used `jj abandon`, or because it's an obsolete version that's been rewritten
with `jj rebase`, `jj describe`, etc). In that case, `jj log -r commit_id`
should show the revision as "hidden". `jj new commit_id` should make the
revision visible again.

See [revsets] and [templates] for further guidance.

### What are elided revisions in the output of `jj log`? How can I display them?

"Elided revisions" appears in the log when one revision descends from another,
both are in the revset, but the revisions connecting them are _not_ in the
revset.

For example, suppose you log the revset `tyl|mus` which contains exactly two
revisions:

```sh
$  jj log -r 'tyl|mus'
○  musnqzvt me@example.com 1 minute ago 9a09f8a5
│  Revision C
~  (elided revisions)
○  tylynnzk me@example.com 1 minute ago f26967c8
│  Revision A
```

Only the two revisions in the revset are displayed. The text "(elided
revisions)" is shown to indicate that `musnqzvt` descends from `tylynnzk`, but
the nodes connecting them are not in the revset.

To view the elided revisions, change the [revset expression](revsets.md) so it
includes the connecting revisions. The `connected()` revset function does
exactly this:

```sh
$ jj log -r 'connected(tyl|mus)'
○  musnqzvt me@example.com 43 seconds ago 9a09f8a5
│  Revision C
○  rsvnrznr me@example.com 43 seconds ago 5b490f30
│  Revision B
○  tylynnzk me@example.com 43 seconds ago f26967c8
│  Revision A
```

### How can I get `jj log` to show me what `git log` would show me?

Use `jj log -r ..`. The `..` [operator] lists all visible commits in the repo, excluding the root (which is never interesting and is shared by all repos).

### Can I monitor how `jj log` evolves?

The simplest way to monitor how the history as shown by `jj log` evolves is by using the [watch(1)](https://man7.org/linux/man-pages/man1/watch.1.html) command (or [hwatch](https://github.com/blacknon/hwatch?tab=readme-ov-file#configuration) or [viddy](https://github.com/sachaos/viddy)).
For example:

```sh
watch --color jj --ignore-working-copy log --color=always
```

This will continuously update the (colored) log output in the terminal.
The `--ignore-working-copy` option avoids conflicts with manual operations during the creation of snapshots.
Martin used watch in a [tmux](https://github.com/tmux/tmux/wiki) pane during his presentation [Jujutsu - A Git-compatible VCS](https://www.youtube.com/watch?v=LV0JzI8IcCY).

A similar result can be achieved with [watchexec](https://github.com/watchexec/watchexec),
which triggers on op log changes instead of using a two-second interval:

```sh
watchexec --quiet --clear --restart --watch=.jj/repo/op_heads/heads --ignore-nothing --wrap-process=none -- jj --ignore-working-copy log
```

Alternatively, you can use [jj-fzf](https://github.com/tim-janik/jj-fzf), where the central piece is the `jj log` view and common operations can be carried out via key bindings while the log view updates.

The wiki lists additional TUIs and GUIs beyond the terminal: [GUI-and-TUI](https://github.com/jj-vcs/jj/wiki/GUI-and-TUI)

### How do I convert an existing Git repo into a Jujutsu repo?

From inside the Git repository, run `jj git init`. This creates a `.jj/`
directory next to `.git/` and imports the existing history (a colocated
workspace).

If you instead want a separate Jujutsu working copy that shares commits with the
Git repo (similar to a [Git worktree](https://git-scm.com/docs/git-worktree)),
then run `jj git init --git-repo=path/to/git-repo path/to/new/jj-repo`.

See [Creating a repo backed by an existing Git
repo](git-compatibility.md#creating-a-repo-backed-by-an-existing-git-repo).

### <a name="should-i-co-locate-my-repository"></a>Should I colocate my repository?

Colocating a Jujutsu repository allows you to use both Jujutsu and Git in the
same working copy. The benefits of doing so are:

- You can use Git commands when you're not sure how to do something with
  Jujutsu, Jujutsu hasn't yet implemented a feature (e.g., tagging), or you
  simply prefer Git in some situations.

- Tooling that expects a Git repository still works (IDEs, build tooling, etc.)

The [colocation documentation describes the
drawbacks](git-compatibility.md#colocated-jujutsugit-workspaces) but the most
important ones are:

- Interleaving `git` and `jj` commands may create confusing bookmark conflicts
  or divergent changes.

- If the working copy commit or its parent contain any conflicted files, tools
  expecting a Git repo may interpret the commit contents or its diff in a wrong
  and confusing way. You should avoid doing mutating operations with Git tools
  and ignore the confusing information such tools present for conflicted commits
  (unless you are curious about [the details of how `jj` stores
  conflicts](technical/conflicts.md)). See
  [\#3979](https://github.com/jj-vcs/jj/issues/3979) for plans to improve
  this situation.

- Jujutsu commands may be a little slower in very large repositories due to
  importing and exporting changes to Git. Most repositories are not noticeably
  affected by this.

If you primarily use Jujutsu to modify the repository, the drawbacks are
unlikely to affect you. Try colocating while you learn Jujutsu, then switch if
you find a specific reason not to colocate.

### `jj` is said to record the working copy after `jj log` and every other command. Where can I see these automatic "saves"?

Indeed, every `jj` command updates the current "working-copy" revision, marked
with `@` in `jj log`. You can notice this by how the [commit ID] of the
working copy revision changes when it's updated. Note that, unless you move to
another revision (with `jj new` or `jj edit`, for example), the [change ID] will
not change.

If you expected to see a historical view of your working copy changes in the
parent-child relationships between commits you can see in `jj log`, this is
simply not what they mean. What you can see in `jj log` is that after the
working copy commit gets amended (after any edit), the commit ID changes.

You can see the actual history of working copy changes using `jj evolog`. This
will show the history of the commits that were previously the "working-copy
commit", since the last time the change id of the working copy commit changed.
The obsolete changes will be marked as "hidden". They are still accessible with
any `jj` command (`jj diff`, for example), but you will need to use the commit
id to refer to hidden commits.

You can also use `jj evolog -r` on revisions that were previously the
working-copy revisions (or on any other revisions). Use `jj evolog -p` as an
easy way to see the evolution of the commit's contents.

### Can I prevent Jujutsu from recording my unfinished work? I'm not ready to commit it.

Jujutsu automatically records new files in the current working-copy commit and
doesn't provide a way to prevent that.

However, you can easily record intermediate drafts of your work. If you think
you might want to go back to the current state of the working-copy commit,
simply use `jj new`. There's no need for the commit to be "finished" or even
have a description.

Then future edits will go into a new working-copy commit on top of the now
former working-copy commit. Whenever you are happy with another set of edits,
use `jj squash` to amend the previous commit.

If you have changes you _never_ want to put in a public commit, see: [How can I
keep my scratch files in the repository without committing
them?](#how-can-i-keep-my-scratch-files-in-the-repository-without-committing-them)

For more options see the next question.

### Can I interactively create a new commit from only some of the changes in the working copy, like `git add -p && git commit` or `hg commit -i`?

Since the changes are already in the working-copy commit, the equivalent to
`git add -p && git commit`/`git commit -p`/`hg commit -i` is to split the
working-copy commit with `jj split -i` (or the practically identical
`jj commit -i`).

For the equivalent of `git commit --amend -p`/`hg amend -i`, use `jj squash -i`.

### Is there something like `git rebase --interactive` or `hg histedit`?

It is often sufficient to use some form of `jj rebase` with `-A/-B`. For
example, if you have a linear chain of revisions `A` through `C` and want to
move `C` before `B`, use `jj rebase -r C -B B`. See `jj help rebase` for more
examples.

For more complicated cases, try `jj arrange`. It is still under development,
and will gain more features over time.

To squash or split commits, use `jj squash` and `jj split`.

### How can I keep my scratch files in the repository without committing them?

You can set `snapshot.auto-track` to only start tracking new files matching the
configured pattern (e.g. `"none()"`). Changes to already tracked files will
still be snapshotted by every command. Files not matching the pattern can be
tracked with `jj file track`.

You can keep your notes and other scratch files in the repository, if you add
a wildcard pattern to either the repo's `gitignore` or your global `gitignore`.
Something like `*.scratch` or `*.scratchpad` should do, after that rename the
files you want to keep around to match the pattern.

If you keep your scratch files in their own directory with no tracked files, you
can create a `.gitignore` file in that directory containing only `*`. This will
ignore everything in the directory including the `.gitignore` file itself.

If `$EDITOR` integration is important, something like `scratchpad.*` may be more
helpful, as you can keep the filename extension intact (it matches
`scratchpad.md`, `scratchpad.rs` and more). Another option is to add a directory
which would store all your temporary files and notes to your configured global
gitignore file. For example, you could add `scratch/` to it and then store
arbitrary files in `<your-git-repo>/scratch/`.

You can find more details on `gitignore` files [here][gitignore].

### How can I avoid committing my local-only changes to tracked files?

Suppose your repository tracks a file like `secret_config.json`, and you make
some changes to that file to work locally. Since Jujutsu automatically commits
the working copy, there's no way to prevent Jujutsu from committing changes to
the file. But, you never want to push those changes to the remote repository.

One solution is to keep these changes in a separate commit branched from the
trunk. To use those changes in your working copy, _merge_ the private commit
into your branch.

Suppose you have a commit "Add new feature":

```shell
$ jj log
@  xxxxxxxx me@example.com 2024-08-21 11:13:21 ef612875
│  Add new feature
◆  yyyyyyyy me@example.com 2024-08-21 11:13:09 main b624cf12
│  Existing work
~
```

First, create a new commit branched from main and add your private changes:

```shell
$ jj new main -m "private: my credentials"
Working copy  (@) now at: wwwwwwww 861de9eb (empty) private: my credentials
Parent commit (@-)      : yyyyyyyy b624cf12 main | Existing work
Added 0 files, modified 1 files, removed 0 files

$ echo '{ "password": "p@ssw0rd1" }' > secret_config.json
```

Now create a merge commit with the branch you're working on and the private
commit:

```shell
$ jj new xxxxxxxx wwwwwwww
Working copy  (@) now at: vvvvvvvv ac4d9fbe (empty) (no description set)
Parent commit (@-)      : xxxxxxxx ef612875 Add new feature
Parent commit (@-)      : wwwwwwww 2106921e private: my credentials
Added 0 files, modified 1 files, removed 0 files

$ jj log
@    vvvvvvvv me@example.com 2024-08-22 08:57:40 ac4d9fbe
├─╮  (empty) (no description set)
│ ○  wwwwwwww me@example.com 2024-08-22 08:57:40 2106921e
│ │  private: my credentials
○ │  xxxxxxxx me@example.com 2024-08-21 11:13:21 ef612875
├─╯  Add new feature
◆  yyyyyyyy me@example.com 2024-08-21 11:13:09 main b624cf12
│  Existing work
~
```

Now you're ready to work:

- Your work in progress _xxxxxxxx_ is the first parent of the merge commit.
- The private commit _wwwwwwww_ is the second parent of the merge commit.
- The working copy (_vvvvvvvv_) contains changes from both.

As you work, squash your changes using `jj squash --into xxxxxxxx`.

If you need a new empty commit on top of `xxxxxxxx` you can use the
`--insert-after` and `--insert-before` options (`-A` and `-B` for short):

```shell
# Insert a new commit after xxxxxxxx
$ jj new --no-edit -A xxxxxxxx -m "Another feature"
Working copy  (@) now at: uuuuuuuu 1c3cff09 (empty) Another feature
Parent commit (@-)      : xxxxxxxx ef612875 Add new feature

# Insert a new commit between yyyyyyyy and vvvvvvvv
$ jj new --no-edit -A yyyyyyyy -B vvvvvvvv -m "Yet another feature"
Working copy  (@) now at: tttttttt 938ab831 (empty) Yet another feature
Parent commit (@-)      : yyyyyyyy b624cf12 Existing work
```

To avoid pushing change _wwwwwwww_ by mistake, use the configuration
[git.private-commits](config.md#set-of-private-commits):

```shell
jj config set --user git.private-commits "'''description('private:*')'''"
```

### I accidentally changed files in the wrong commit, how do I move the recent changes into another commit?

Let's say we are editing a commit for "featureA", and we forgot to run `jj
new` or `jj commit` before doing some work that belongs in a new commit:

```console
$ jj log
@  lnvvtrzo jjfan@example.org 2025-02-28 21:01:10 31a347e0
│  featureA
◆  zzzzzzzz root() 00000000
$ cat file  # Oh no, the work on "feature B" should be in a separate commit!
Done with feature A
Working on feature B
```

The goal is to restore change `lnvvtrzo` to its previous state and split the
difference into a new child commit.

#### Step 1: Find the commit ID for the "last good version"

If you pushed `lnvvtrzo` already, then the version you probably want to restore
is the version on the remote. For example, if the bookmark is `feature-a`, then
the commit you want to restore is `feature-a@origin`.

Otherwise, you can find [all the past versions of the working copy revision that
`jj` has saved][predecessors] by running `jj evolog`, perhaps with the `--patch`
option. The obsolete versions will be marked as "hidden" and will have the same
change ID, but will have different commit IDs. This represents the [change]
evolving over time.

[predecessors]:
  #jj-is-said-to-record-the-working-copy-after-jj-log-and-every-other-command-where-can-i-see-these-automatic-saves

For example, this is what the evolog might look like after you made two edits to
the same change:

```console
$ # Note the word "hidden", the commit IDs on the right,
$ # and the unchanging change ID on the left.
$ jj evolog
@  lnvvtrzo jjfan@example.org 2025-02-28 21:01:10 31a347e0
│  featureA
│  -- operation 3cb7392c092c snapshot working copy
○  lnvvtrzo/1 jjfan@example.org 2025-02-28 21:00:51 b8004ab8 (hidden)
│  featureA
│  -- operation 1280bfaec893 snapshot working copy
○  lnvvtrzo/2 jjfan@example.org 2025-02-28 20:50:05 e4d831d (hidden)
   (no description set)
   -- operation 0418a5aa94b5 snapshot working copy
```

Since commit `b800` is hidden, it is considered obsolete and `jj log` (without
arguments) will not show it. However, most `jj` operations work normally on
such commits if you refer to them by their commit ID. Hidden commits can also
be referred to by their change ID, but they require a
[change offset][glossary_change_offset] to distinguish them (e.g. `b800` can
also be referred to as `lnv/1`, as shown in the evolog).

To find out which of these versions is the last time before we started working
on feature B (the point where we should have created a new change, but failed to
do so), we can look at the actual changes between the `evolog` commits by
running `jj evolog --patch`:

```console
$ # When was the last saved point before we started working on feature B?
$ jj evolog --patch --git  # We use `--git` to make diffs clear without colors
@  lnvvtrzo jjfan@example.org 2025-02-28 21:01:10 31a347e0
│  featureA
│  -- operation 3cb7392c092c snapshot working copy
│  diff --git a/file b/file
│  index 2b455c4207..2a7e05a01a 100644
│  --- a/file
│  +++ b/file
│  @@ -1,1 +1,2 @@
│   Done with feature A
│  +Working on feature B
○  lnvvtrzo/1 jjfan@example.org 2025-02-28 21:00:51 b8004ab8 (hidden)
│  featureA
│  -- operation 1280bfaec893 snapshot working copy
│  diff --git a/file b/file
│  index cb61245109..2b455c4207
│  --- a/file
│  +++ b/file
│  @@ -1,1 +1,1 @@
│  -Working on feature A
│  +Done with feature A
○  lnvvtrzo/2 jjfan@example.org 2025-02-28 20:50:05 e4d831d (hidden)
   (no description set)
   -- operation 0418a5aa94b5 snapshot working copy
   diff --git a/file b/file
   index 0000000000..cb61245109
   --- /dev/null
   +++ b/file
   @@ 0,0 +1,1 @@
   +Working on feature A
```

In this example, the version of the change when we were actually done with
feature A is when we edited the file to say "Done with feature A". This state
was saved in the commit with ID `b80` (the second one in the list). The
following edit (commit `31a`) belongs in a new change.

#### Step 2: Create a new change on top of the original revision

The "featureA" change is currently at commit `31a`:

```console
$ jj log
@  lnvvtrzo jjfan@example.org 2025-02-28 21:01:10 31a347e0
│  featureA
◆  zzzzzzzz root() 00000000
```

We'd like to create a new "featureB" change with the contents of the current
commit `31a`, and we'd like the "featureA" change to be reverted to its former
state at commit `b80` (see step 1 above for how we found that commit ID).

First, we create a new empty child commit on top of `b80`:

```console
$ jj new b80 -m "featureB"
Working copy  (@) now at: pvnrkl 47171aa (empty) featureB
Parent commit (@-)      : lnvvtr/1 b8004ab (divergent) featureA
```

There are now two visible commits with change ID `lnvvtr` (commit `b8004ab`
and `31a347e0`), so we call these [divergent][glossary_divergence]. Similarly
to hidden commits, divergent commits also require a
[change offset][glossary_change_offset] when using the change ID to refer to
them, so you can see that `b8004ab` is still shown as `lnvvtr/1` in the output.
This temporary divergence is okay and will be resolved in the next steps.

[glossary_divergence]: glossary.md#divergent-change
[glossary_change_offset]: glossary.md#change-offset

Next, restore the contents of `31a347e0` into the working copy:

```console
$ jj restore --from 31a347e0
Working copy  (@) now at: pvnrkl 468104c featureB
Parent commit (@-)      : lnvvtr/1 b8004ea (divergent) featureA
$ cat file
Done with feature A
Working on feature B
```

#### Step 3: Move any bookmarks to the original revision

```console
$ jj bookmark move --from 31a347e0 --to b8004ea8
```

#### Step 4: Abandon the unwanted revision

```console
$ jj abandon 31a347e0
```

Now, we have achieved the exact state we desired:

```
$ jj log -p --git
@  pvnrklkn jjfan@example.org 2025-02-28 21:39:29 468104c2
│  featureB
│  diff --git a/file b/file
│  index 2b455c4207..2a7e05a01a 100644
│  --- a/file
│  +++ b/file
│  @@ -1,1 +1,2 @@
│   Done with feature A
│  +Working on feature B
○  lnvvtrzo jjfan@example.org 2025-02-28 21:00:51 b8004ab8
│  featureA
│  diff --git a/file b/file
│  new file mode 100644
│  index 0000000000..2b455c4207
│  --- /dev/null
│  +++ b/file
│  @@ -0,0 +1,1 @@
│  +Done with feature A
◆  zzzzzzzz root() 00000000
$ jj diff --from b80 --to @- # No output means these are identical
$ jj diff --from 31a --to @  # No output means these are identical
```

### How do I resume working on an existing change?

There are two ways to resume working on an earlier change: `jj new` then `jj squash`,
and `jj edit`. The first is generally recommended, but `jj edit` can be useful. When
you use `jj edit`, the revision is directly amended with your new changes, making it
difficult to tell what exactly you change. You should avoid using `jj edit` when the
revision has a conflict, as you may accidentally break the plain-text annotations on
your state without realizing.

To start, use `jj new <rev>` to create a change based on that earlier revision. Make
your edits, then use `jj squash` to update the earlier revision with those edits.
For when you would use git stashing, use `jj edit <rev>` for expected behavior.
Other workflows may prefer `jj edit` as well.

### Why are most merge commits marked as "(empty)"?

Jujutsu, like Git, is a snapshot-based VCS. That means that each commit
logically records the state of all current files in the repo. The changes in a
commit are not recorded but are instead calculated when needed by comparing the
commit's state to the parent commit's state. Jujutsu defines the changes in a
commit to be relative to the auto-merged parents (if there's only one parent,
then that merge is trivial - it's the parent commit's state). As a result, a
merge commit that was a clean merge (no conflict resolution, no additional
changes) is considered empty. Conversely, if the merge commit contains conflict
resolutions or additional changes, then it will be considered non-empty.

This definition of the changes in a commit is used throughout Jujutsu. It's
used by `jj diff -r` and `jj log -p` to show the changes in a commit. It's used
by `jj rebase` to rebase the changes in a commit. It's used in `jj log` to
indicate which commits are empty. It's used in the `files()` revset function
(and by `jj log <path>`) to find commits that modify a certain path. And so on.

### How do I revert a merge commit? `jj revert -r <merge>` does nothing

Jujutsu defines the changes in a merge commit (and non-merge commits) as the
changes made compared to the auto-merged parents. That means that merge commits
are often empty. As a result, `jj revert` or a merge commit often results in an
empty commit. To revert the changes merged in from the second parent, instead
use `jj restore --from <first parent>` .

Example:

```text
@
|
C
| \
B D
|/
A
```

To revert the merge in `C`, create a new commit with `jj new C`,
then `jj restore --from B`, and then describe the message
with something like `jj desc -m "Revert the merge of D into B`. Now, commit `@`
undoes the merge of `D` into  `B`. If necessary, you can now rebase it
elsewhere, e.g. `jj rebase -r @ -o main`.

### How do I deal with divergent changes?

See: [Handling divergent commits](guides/divergence.md).

### How do I deal with conflicted bookmarks ('??' after bookmark name)?

A [conflicted bookmark][bookmarks_conflicts] is a bookmark that refers to multiple
different commits because jj couldn't fully resolve its desired position.
Resolving conflicted bookmarks is usually done by setting the bookmark to the
correct commit using `jj bookmark move <name> --to <commit ID>`.

Usually, the different commits associated with the conflicted bookmark should all
appear in the log, but if they don't you can use `jj bookmark list`to show all the
commits associated with it.

### I'm experiencing `jj` command issues in a Vite/Vitest project, how do I fix this?

When using Vite or Vitest in a Jujutsu repository, you may experience:
- Very slow vitest startup times
- Timeout errors in `jj` terminal commands
- Errors with 3rd party visual tools like `jjk` or `visual-jj`
- Corrupted `working_copy.lock` files

This happens because Vite watches the `.jj` directory where Jujutsu stores its internal state.
This creates unnecessary overhead as Vite processes Jujutsu's frequent internal file changes,
which can slow down both tools and occasionally cause file access conflicts.

**Solution**: Configure Vite to ignore the `.jj` directory by adding it to the
`server.watch.ignored` array inside your Vite configuration, for example:

```js
// vite.config.js
export default defineConfig({
  // ... other config like plugins, test setup, etc.
  server: {
    watch: {
      ignored: [
        "**/.jj/**",
      ]
    }
  },
})
```

Note: There was a [request](https://github.com/vitejs/vite/issues/20036) to include `.jj`
in the default ignore list, but manual configuration remains the recommended approach.

### How can I manually trigger (e.g., periodic) snapshots?

Most `jj` commands will automatically snapshot the working copy at the start of
the command if needed. However, if you'd like to manually trigger a snapshot for
whatever reason, such as for scripting, prompt info, or periodic snapshots in
parallel with something like a
[`watch` command](#can-i-monitor-how-jj-log-evolves), you can run
`jj util snapshot`. By default this command will print whether a snapshot was
taken, which you can silence with the global `--quiet` flag. This command is
likely most useful for scripting rather than for running on the command line by
a human.

If you want to see the ID of the current operation after this command, it would
be simpler to run `jj operation log --limit 1` directly, since that command also
takes a snapshot if needed.

### I want to write a tool which integrates with Jujutsu. Should I use the library or parse the CLI?

There are some trade-offs and there is no definitive answer yet.

* Using `jj-lib` avoids parsing command output and makes error handling easier.
* `jj-lib` is not a stable API, so you may have to make changes to your tool
when the API changes.
* The CLI is not stable either, so you may need to make your tool detect the
different versions and call the right command.
* Using the CLI means that your tool will work with custom-built `jj` binaries,
like the one at Google (if you're using the library, you will not be able to
detect custom backends and more).

### Why is Jujutsu a separate project? Why were the features not contributed to Git instead?

The project started as an experiment with the idea of representing the working
copy by a regular commit. I (@martinvonz) considered how this feature would
impact the Git CLI if it were added to Git. My conclusion was that it would
effectively result in deprecating most existing Git commands and flags in favor
of new commands and flags, especially considering I wanted to also support
revsets. This seemed unlikely to be accepted by the Git project.

[bookmarks_conflicts]: bookmarks.md#conflicts

[change]: glossary.md#change
[change ID]: glossary.md#change-id
[colocated]: glossary.md#colocated-workspaces
[commit ID]: glossary.md#commit-id
[commits]: glossary.md#commit
[config]: config.md

[gerrit-integration]: https://gist.github.com/thoughtpolice/8f2fd36ae17cd11b8e7bd93a70e31ad6
[gitignore]: https://git-scm.com/docs/gitignore

[operator]: revsets.md#operators

[revsets]: revsets.md

[templates]: templates.md

---

### Bookmarks (`docs/bookmarks.md`)

# Bookmarks

## Introduction

Bookmarks are named pointers to revisions (just like branches are in Git). You
can move them without affecting the target revision's identity. Bookmarks
automatically move when revisions are rewritten (e.g. by `jj rebase`). You can
pass a bookmark's name to commands that want a revision as argument. For example,
`jj new main` will create a new revision on top of the `main` bookmark. Use
`jj bookmark list` to list bookmarks and `jj bookmark <subcommand>` to create,
move, or delete bookmarks. There is currently no concept of an
active/current/checked-out bookmark.

## Mapping to Git branches

Jujutsu maps its bookmarks to Git branches when interacting with Git repos. For
example, `jj git push --bookmark foo` will push the state of the `foo` bookmark
to the `foo` branch on the Git remote. Similarly, if you create a `bar` branch
in the backing Git repo, then a subsequent `jj git import` will create a `bar`
bookmark (reminder: that import happens automatically in
[colocated workspaces][colocated-workspaces]).

## Remotes and tracked bookmarks

Jujutsu records the last seen position of a bookmark on each remote (just like
Git's remote-tracking branches). This record is updated on every `jj git fetch`
and `jj git push` of the bookmark. You can refer to the remembered remote bookmark
positions with `<bookmark name>@<remote name>`, such as `jj new main@origin`. `jj`
does not provide a way to manually edit these recorded positions.

A remote bookmark can be associated with a local bookmark of the same name. This
is called a **tracked remote bookmark** (which maps to a Git remote branch when
using the Git backend). When you pull a tracked bookmark from a remote, any
changes compared to the current record of the remote's state will be propagated
to the corresponding local bookmark, which will be created if it doesn't exist
already.

!!! note "Details: how `fetch` pulls bookmarks"

    Let's say you run `jj git fetch --remote origin` and, during the fetch, `jj`
    determines that the remote's `main` bookmark has been moved so that its target is
    now ahead of the local record in `main@origin`.

    `jj` will then update `main@origin` to the new target. If `main@origin` is
    **tracked**, `jj` will also apply the change to the local bookmark `main`. If the
    local target has also been moved compared to `main@origin` (probably because you
    ran `jj bookmark set main`), then the two updates will be merged. If one is ahead
    of the other, then that target will become the new target. Otherwise, the local
    bookmark will become conflicted (see the ["Conflicts" section](#conflicts) below
    for details).

Most commands don't show the tracked remote bookmark if it has the same target as
the local bookmark. The local bookmark (without `@<remote name>`) is considered the
bookmark's desired target. Consequently, if you want to update a bookmark on a
remote, you first update the bookmark locally and then push the update to the
remote. If a local bookmark also exists on some remote but points to a different
target there, `jj log` will show the bookmark name with an asterisk suffix (e.g.
`main*`). That is meant to remind you that you may want to push the bookmark to
some remote.

If you want to know the internals of bookmark tracking, consult the
[Design Doc][design].

### Terminology summary

- A **remote bookmark** is a bookmark ref on the remote. `jj` can find out its
  actual state only when it's actively communicating with the remote. However,
  `jj` does store the last-seen position of the remote bookmark; this is the
  commit `jj show <bookmark name>@<remote name>` would show. This notion is
  completely analogous to Git's "remote-tracking branches".
- A **tracked (remote) bookmark** is defined above. You can make a remote bookmark
  tracked with the [`jj bookmark track` command](#manually-tracking-a-bookmark), for
  example.
- A **tracking (local) bookmark** is the local bookmark that `jj` tries to keep
  in sync with the tracked remote bookmark. For example, after `jj bookmark
  track mybookmark --remote=origin`, there will be a local bookmark `mybookmark`
  that's tracking the remote `mybookmark@origin` bookmark. A local bookmark can
  track a bookmark of the same name on 0 or more remotes.

The notion of tracked bookmarks serves a similar function to the Git notion of an
"upstream branch". Unlike Git, a single local bookmark can be tracking remote
bookmarks on multiple remotes, and the names of the local and remote bookmarks
must match.

### Manually tracking a bookmark

To track a bookmark permanently use `jj bookmark track <bookmark name>
--remote=<remote name>`. It will now be imported as a local bookmark until you
untrack it or it is deleted on the remote.

Example:

```sh
$ # List all available bookmarks, as we want our colleague's bookmark.
$ jj bookmark list --all
$ # Find the bookmark.
$ # [...]
$ # Actually track the bookmark.
$ jj bookmark track <bookmark name> --remote=<remote name> # Example: jj bookmark track my-feature --remote=origin
$ # From this point on, <bookmark name> will be imported when fetching from <remote name>.
$ jj git fetch --remote <remote name>
$ # A local bookmark <bookmark name> should have been created or updated while fetching.
$ jj new <bookmark name> # Do some local testing, etc.
```

### Untracking a bookmark

To stop following a remote bookmark, you can `jj bookmark untrack` it. After that,
subsequent fetches of that remote will no longer move the local bookmark to match
the position of the remote bookmark.

Example:

```sh
$ # List all local and remote bookmarks.
$ jj bookmark list --all
$ # Find the bookmark we no longer want to track.
$ # [...]
# # Actually untrack it.
$ jj bookmark untrack <bookmark name> --remote=<remote name> # Example: jj bookmark untrack stuff --remote=origin
$ # From this point on, this remote bookmark won't be imported anymore.
$ # The local bookmark (e.g. stuff) is unaffected. It may or may not still
$ # be tracking bookmarks on other remotes (e.g. stuff@upstream).
```

### Listing tracked bookmarks

To list tracked bookmarks, you can `jj bookmark list --tracked` or `jj bookmark list -t`.
This command omits local Git-tracking bookmarks by default.

You can see if a specific bookmark is tracked with `jj bookmark list --tracked <bookmark name>`.

### Automatic tracking of bookmarks & `auto-track-bookmarks` option

There are two situations where `jj` tracks bookmarks automatically. `jj git
clone` automatically sets up the default remote bookmark (e.g. `main@origin`) as
tracked. When you push a local bookmark, the newly created bookmark on the remote is
marked as tracked.

By default, every other remote bookmark is marked as "not tracked" when it's
fetched. If desired, you need to manually `jj bookmark track` them. This works
well for repositories where multiple people work on a large number of bookmarks.

The default can be changed by setting the config `remotes.<name>.auto-track-bookmarks = "*"`.
Then, `jj git fetch` tracks every *newly fetched* bookmark with a local bookmark.
Branches that already existed before the `jj git fetch` are not affected. This
is similar to Mercurial, which fetches all its bookmarks (equivalent to Git's
branches) by default. Similarly, all newly created local bookmarks
will be marked as "tracked", preparing them to be pushed with
the next `jj git push` command. See ["Automatic tracking of
bookmarks"](config.md#automatic-tracking-of-bookmarks) for details.

## Bookmark updates

Currently Jujutsu automatically updates local bookmarks when these conditions are
met:

 * When a commit has been rewritten (e.g, when you rebase) bookmarks and the
   working-copy will move along with it.
 * When a commit has been abandoned, all associated bookmarks will be deleted.

You could describe the updates as following along the change-id of the
current bookmark commit, even if it isn't entirely accurate.

## Pushing bookmarks: Safety checks

Before `jj git push` actually moves, creates, or deletes a remote bookmark, it
makes several safety checks.

1. `jj` will contact the remote and check that the actual state of the remote
   bookmark matches `jj`'s record of its last known position. If there is a
   conflict, `jj` will refuse to push the bookmark. In this case, you need to run
   `jj git fetch --remote <remote name>` and resolve the resulting bookmark
   conflict. Then, you can try `jj git push` again.

   If you are familiar with Git, this makes `jj git push` similar to `git
   push --force-with-lease`.

   There are a few cases where `jj git push` will succeed even though the remote
   bookmark is in an unexpected location. These are the cases where `jj git fetch`
   would not create a bookmark conflict and would not move the local bookmark, e.g.
   if the unexpected location is identical to the local position of the bookmark.

2. The local bookmark must not be [conflicted](#conflicts). If it is, you would
   need to use `jj bookmark move`, for example, to resolve the conflict.

   This makes `jj git push` safe even if `jj git fetch` is performed on a timer
   in the background (this situation is a known issue[^known-issue] with some
   forms of `git push --force-with-lease`). If the bookmark moves on a remote in a
   problematic way, `jj git fetch` will create a conflict. This should ensure
   that the user becomes aware of the conflict before they can `jj git push` and
   override the bookmark on the remote.

3. If the remote bookmark already exists on the remote, it must be
   [tracked](#remotes-and-tracked-bookmarks).

[^known-issue]: See "A general note on safety" in
    <https://git-scm.com/docs/git-push#Documentation/git-push.txt---no-force-with-lease>

## Conflicts

Bookmarks can end up in a conflicted state. When that happens, `jj status` will
include information about the conflicted bookmarks (and instructions for how to
mitigate it). `jj bookmark list` will have details. `jj log` will show the bookmark
name with a double question mark suffix (e.g. `main??`) on each of the
conflicted bookmark's potential target revisions. Using the bookmark name to look up
a revision will resolve to all potential targets. That means that `jj new main`
will error out, complaining that the revset resolved to multiple revisions.

Both local bookmarks (e.g. `main`) and the remote bookmark (e.g. `main@origin`) can
have conflicts. Both can end up in that state if concurrent operations were run
in the repo. The local bookmark more typically becomes conflicted because it was
updated both locally and on a remote.

To resolve a conflicted state in a local bookmark (e.g. `main`), you can move the
bookmark to the desired target with `jj bookmark move`. You may want to first either
merge the conflicted targets with `jj new` (e.g. `jj new main`), or you may want to
rebase one side on top of the other with `jj rebase`.

To resolve a conflicted state in a remote bookmark (e.g. `main@origin`), simply
pull from the remote (e.g. `jj git fetch`). The conflict resolution will also
propagate to the local bookmark (which was presumably also conflicted).

## Ease of use

The use of bookmarks is frequent in some workflows, for example, when
interacting with Git repositories containing branches. To this end,
one-letter shortcuts have been implemented, both for the `jj bookmark`
command itself through an alias (as `jj b`), and for its subcommands.
For example, `jj bookmark create BOOKMARK-NAME -r@` can be abbreviated as
`jj b c BOOKMARK-NAME -r@`.

[colocated-workspaces]: git-compatibility.md#colocated-jujutsugit-workspaces
[design]: design/tracking-branches.md

---

### Changelog (`docs/changelog.md`)

{%
  include-markdown "../CHANGELOG.md"
  rewrite-relative-urls=true
  comments=true
%}

---

### Cli Reference (`docs/cli-reference.md`)

!!! warning

    This CLI reference is experimental. It is automatically generated, but
    does not match the `jj help` output exactly.

    Run `jj help <COMMAND>` for more authoritative documentation.

    If you see a significant difference, feel free to file a bug, or a PR to note the difference here.

{%
  include-markdown "../cli/tests/cli-reference@.md.snap"
  rewrite-relative-urls=false
  start=""
  comments=true
%}

---

### Code Of Conduct (`docs/code-of-conduct.md`)

# Contributor Covenant Code of Conduct

## Our Pledge

We as members, contributors, and leaders pledge to make participation in our
community a harassment-free experience for everyone, regardless of age, body
size, visible or invisible disability, ethnicity, sex characteristics, gender
identity and expression, level of experience, education, socioeconomic status,
nationality, personal appearance, race, caste, color, religion, or sexual
identity and orientation.

We pledge to act and interact in ways that contribute to an open, welcoming,
diverse, inclusive, and healthy community.

## Our Standards

Examples of behavior that contributes to a positive environment for our
community include:

* Demonstrating empathy and kindness toward other people
* Being respectful of differing opinions, viewpoints, and experiences
* Giving and gracefully accepting constructive feedback
* Accepting responsibility and apologizing to those affected by our mistakes,
  and learning from the experience
* Focusing on what is best not just for us as individuals, but for the overall
  community

Examples of unacceptable behavior include:

* The use of sexualized language or imagery, and sexual attention or advances of
  any kind
* Trolling, insulting or derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or email address,
  without their explicit permission
* Other conduct which could reasonably be considered inappropriate in a
  professional setting

## Enforcement Responsibilities

Community leaders are responsible for clarifying and enforcing our standards of
acceptable behavior and will take appropriate and fair corrective action in
response to any behavior that they deem inappropriate, threatening, offensive,
or harmful.

Community leaders have the right and responsibility to remove, edit, or reject
comments, commits, code, wiki edits, issues, and other contributions that are
not aligned to this Code of Conduct, and will communicate reasons for moderation
decisions when appropriate.

## Scope

This Code of Conduct applies within all community spaces, and also applies when
an individual is officially representing the community in public spaces.
Examples of representing our community include using an official e-mail address,
posting via an official social media account, or acting as an appointed
representative at an online or offline event.

## Enforcement

Instances of abusive, harassing, or otherwise unacceptable behavior may
be reported to the community leaders responsible for enforcement at two or
more of jaraco@jaraco.com, community@isaaccorbrey.com, me@waleedkhan.name, and
opensource@google.com. All complaints will be reviewed and investigated promptly
and fairly.

All community leaders are obligated to respect the privacy and security of the
reporter of any incident.

## Enforcement Guidelines

Community leaders will follow these Community Impact Guidelines in determining
the consequences for any action they deem in violation of this Code of Conduct:

### 1. Correction

**Community Impact**: Use of inappropriate language or other behavior deemed
unprofessional or unwelcome in the community.

**Consequence**: A private, written warning from community leaders, providing
clarity around the nature of the violation and an explanation of why the
behavior was inappropriate. A public apology may be requested.

### 2. Warning

**Community Impact**: A violation through a single incident or series of
actions.

**Consequence**: A warning with consequences for continued behavior. No
interaction with the people involved, including unsolicited interaction with
those enforcing the Code of Conduct, for a specified period of time. This
includes avoiding interactions in community spaces as well as external channels
like social media. Violating these terms may lead to a temporary or permanent
ban.

### 3. Temporary Ban

**Community Impact**: A serious violation of community standards, including
sustained inappropriate behavior.

**Consequence**: A temporary ban from any sort of interaction or public
communication with the community for a specified period of time. No public or
private interaction with the people involved, including unsolicited interaction
with those enforcing the Code of Conduct, is allowed during this period.
Violating these terms may lead to a permanent ban.

### 4. Permanent Ban

**Community Impact**: Demonstrating a pattern of violation of community
standards, including sustained inappropriate behavior, harassment of an
individual, or aggression toward or disparagement of classes of individuals.

**Consequence**: A permanent ban from any sort of public interaction within the
community.

## Attribution

This Code of Conduct is adapted from the [Contributor Covenant][homepage],
version 2.1, available at
[https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1].

Community Impact Guidelines were inspired by
[Mozilla's code of conduct enforcement ladder][Mozilla CoC].

For answers to common questions about this code of conduct, see the FAQ at
[https://www.contributor-covenant.org/faq][FAQ]. Translations are available at
[https://www.contributor-covenant.org/translations][translations].

[homepage]: https://www.contributor-covenant.org
[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html
[Mozilla CoC]: https://github.com/mozilla/diversity
[FAQ]: https://www.contributor-covenant.org/faq
[translations]: https://www.contributor-covenant.org/translations

---

### Community Tools (`docs/community_tools.md`)

# Community-built tools around Jujutsu

Many of these tools are not complete yet, just like Jujutsu itself. But they
already simplify many workflows and can improve your experience.

!!! warning
    The listed tools are community‑maintained; the Jujutsu project does not
    review, endorse, or guarantee their quality or security.

## Diffedit3

Diffedit3 is a web-based alternate to Meld, as it no longer is packaged and
available for all Distros. Its creator is also a frequent contributor.

Find it [here][diffedit3]

## GG - GUI for JJ

GG is a cross platform GUI for Jujutsu which makes all graph manipulating
workflows quite easy. Take a look at its README.md as it quite descriptive.

Find it [here][gg].

## Hunk.nvim

Hunk.nvim is a Neovim based diff-editor for Jujutsu which can be used as an
alternative to the default `:builtin` diff-editor.

Find it [here][hunk.nvim].

## JayJay

JayJay is a native macOS GUI for Jujutsu built with Rust and SwiftUI. It features
a DAG graph view, Myers diff with syntax highlighting, side-by-side diffs, conflict
resolution, bookmark manager, command palette, and AI commit messages. The diff
engine is available as a standalone Rust crate (`jj-diff`).

Find it [here][jayjay].

## JJ-FZF

Centered around the `jj log` graph view, jj-fzf provides previews of diffs, the
evolution-log, browses the op log and offers a large number of key bindings for
commonly used `jj` operations from rebase to undo, and helps with divergent commits.

Find it [here][jj-fzf].

## jj-hunk

jj-hunk is a command-line tool for programmatic hunk selection. It can split,
commit, and squash selected hunks without opening an interactive diff editor,
which is useful for scripts and AI coding agents.

Find it [here][jj-hunk].

## JJ TUI

This is TUI for Jujutsu built in Ocaml, it is unopiniated and its creator is
open to feedback.

Find it [here][jj_tui].

## Jujutsu Kaizen

Jujutsu Kaizen is a plugin for Visual Studio Code. The goal of this extension is to bring the great UX of Jujutsu into the VS Code UI.
Its developers are currently focused on achieving parity for commonly used features of VS Code's built-in Git extension, such as the various operations possible via the Source Control view.

Find it [here][jjk].

## LazyJJ

lazyjj is a lazygit inspired TUI for Jujutsu.

Find it [here][lazyjj].

## Visual Jujutsu

VJJ is a fzf (fuzzy finder) wrapper for Jujutsu, which is meant to be used
interactively in the terminal.

Find it [here][vjj].

## VisualJJ

VisualJJ is a plugin for Visual Studio Code which provides native integration
for Jujutsu, not relying on Git colocation. Unlike other tools on this page,
VisualJJ is not open-source.

Find it [here][visualjj].

## Jujutsu UI

jjui is a terminal user interface for working with Jujutsu version control system.

Find it [here][jjui].

## Selvejj

Selvejj is a JetBrains plugin for integrating Jujutsu as a first-class VCS within the IDE.

Find it [here][selvejj] and [here (Marketplace)][selvejj-marketplace].

## Jujutsu plugin for IntelliJ IDEA

Native IntelliJ integration for Jujutsu.

Find it [here][jj-idea] and [here (Marketplace)][jj-idea-marketplace].

## PSCompletions

PSCompletions is a completion manager for a better and simpler tab-completion experience in PowerShell.

It can provide completions via `psc add jj`, or offer a better completion menu for the official completions.

Find it [here][PSCompletions].

## LightJJ

A fast, keyboard-driven browser UI for Jujutsu.

Find it [here][LightJJ].

## JJ View

JJ View is an open-source Visual Studio Code extension that provides a rich, native graphical interface for Jujutsu. It features a Git-style Source Control (SCM) view for easy change management, an interactive visual commit history graph, and built-in commands for common `jj` operations like squashing, abandoning, and absorbing modifications directly from the editor. It also includes native Gerrit integration for streamlined code review workflows.

Find it [here][jj-view].

## jj-stack

`jj-stack` turns a linear series of local `jj` changes into a stack of GitHub pull requests.
Rewrite, split, squash, or reorder your changes with `jj`, then run `jj-stack submit` to update
GitHub. Existing PRs follow their change IDs, keeping comments and review history together.

Find it [here][jj-stack].

## Finding other integrations

You can find other community contributed tools and integrations in our
[Wiki].

[diffedit3]: https://github.com/ilyagr/diffedit3
[gg]: https://github.com/gulbanana/gg
[hunk.nvim]: https://github.com/julienvincent/hunk.nvim
[jayjay]: https://github.com/hewigovens/jayjay
[jj-fzf]: https://github.com/tim-janik/jj-fzf
[jj-hunk]: https://github.com/laulauland/jj-hunk
[jj_tui]: https://github.com/faldor20/jj_tui
[jj-idea]: https://github.com/kkkev/jj-idea
[jj-idea-marketplace]: https://plugins.jetbrains.com/plugin/30576-jujutsu-vcs-integration
[jjk]: https://github.com/keanemind/jjk
[jjui]: https://github.com/idursun/jjui
[lazyjj]: https://github.com/Cretezy/lazyjj
[PSCompletions]: https://github.com/abgox/PSCompletions
[LightJJ]: https://github.com/chronologos/lightjj
[selvejj]: https://selvejj.com
[selvejj-marketplace]: https://plugins.jetbrains.com/plugin/28081-selvejj
[visualjj]: https://www.visualjj.com
[vjj]: https://github.com/noahmayr/vjj
[jj-view]: https://github.com/brychanrobot/jj-view
[jj-stack]: https://www.serpentine.com/software/jj-stack
[Wiki]: https://github.com/jj-vcs/jj/wiki

---

### Config (`docs/config.md`)

# Configuration

These are the config settings available to jj/Jujutsu.

## Config files and TOML

`jj` loads several types of config settings:

- The built-in settings. These cannot be edited. They can be viewed in the
  [`cli/src/config/`][config dir] directory in `jj`'s source repo.

- The user settings. These can be edited with `jj config edit --user`. User
  settings are located in [the user config files], which can be found with
  `jj config path --user`.

- The repo settings. These can be edited with `jj config edit --repo`, or found
  with `jj config path --repo`. For security reasons, they are not located
  inside the repo.

- The workspace settings. These can be edited with `jj config edit --workspace`,
  or found with `jj config path --workspace`. For security reasons, they are not
  located inside the workspace.

- Settings from [environment variable overrides](#environment-variable-overrides)
  (for example `$JJ_USER` / `$JJ_EMAIL`).

- Settings [specified on the command-line].

These are listed in the order they are loaded; the settings from earlier items
in the list are overridden by the settings from later items if they disagree.
Every type of config except for the built-in settings is optional.

Individual config files can also be targeted with
`jj config {edit,set,unset} --file <PATH>`.

You can enable JSON Schema validation in your editor by adding a `#:schema`
reference at the top of your TOML config files. See [JSON Schema Support] for
details.

See the [TOML site] and the [syntax guide] for a detailed description of the
syntax. We cover some of the basics below.

The first thing to remember is that the value of a setting (the part to the
right of the `=` sign) should be surrounded in quotes if it's a string.

[config dir]: https://github.com/jj-vcs/jj/blob/main/cli/src/config/
[JSON Schema Support]: #json-schema-support
[specified on the command-line]: #specifying-config-on-the-command-line
[syntax guide]: https://toml.io/en/latest
[the user config files]: #user-config-files
[TOML site]: https://toml.io/en/

### Dotted style and headings

In TOML, anything under a heading can be dotted instead. For example,
`user.name = "YOUR NAME"` is equivalent to:

```toml
[user]
name = "YOUR NAME"
```

For future reference, here are a couple of more complicated examples,

```toml
# Dotted style
template-aliases."format_short_id(id)" = "id.shortest(12)"
colors."commit_id prefix".bold = true

# is equivalent to:
[template-aliases]
"format_short_id(id)" = "id.shortest(12)"

[colors]
"commit_id prefix" = { bold = true }
```

The docs below refer to keys in text using dotted notation, but example blocks
will use heading notation to be unambiguous. If you are confident with TOML then
use whichever suits you in your config. If you mix dotted keys and headings,
**you must put the dotted keys before the first heading**.

### Escapes in strings

There are two (or four) types of [strings in TOML]: basic and literal, with
multi-line versions of each. Basic strings use double quotes (`"`) and support
escape sequences, such as `"\n"`, `"\""`, `"\\"`, etc. Literal strings use
single quotes (`'`) and do not support escape sequences. Using 3 double quotes
or 3 single quotes to wrap a string makes it multi-line, and then you can use an
unescaped double quote or single quote respectively without ending the string.

A string in your config may be interpreted twice, first by the TOML parser and
then by a Jujutsu parser. To avoid putting strings in your config that the TOML
parser processes before it gets to the Jujutsu parser, you may want to use
literal strings or literal multi-line strings in your `config.toml` files. For
example:

```toml
[template-aliases]
# basic_template = "'hello' ++ \"\n\""
literal_template = '"hello" ++ "\n"'
```

`basic_template` is actually equivalent to this string:

```
'hello' ++ "
"
```

because the TOML parser interprets the `\n` character as part of the string.
This causes the Jujutsu parser to receive a different string than what you
literally see in the TOML file, which may result in seemingly confusing parse
error messages (such as referencing a line that you think shouldn't exist).

On the other hand, `literal_template` is equivalent to:

```
"hello" ++ "\n"
```

because the TOML parser does not escape anything in the literal string.

The same rules of basic and literal strings apply to custom Jujutsu languages:
double quotes (`"`) allow escapes whereas single quotes (`'`) do not. So
`template-aliases.my_template = "'hello\\n'"` would probably not be what you
want.

[strings in TOML]: https://toml.io/en/latest#string

## User settings

```toml
[user]
name = "YOUR NAME"
email = "YOUR_EMAIL@example.com"
```

Don't forget to change these to your own details!

These can also be set with environment variables (handy with tools like
[direnv](https://direenv.net/)):

```bash
export JJ_USER="Your Name"
export JJ_EMAIL="you@example.com"
```

`$JJ_USER` overrides `user.name` and `$JJ_EMAIL` overrides `user.email`. They
take precedence over values in config files (but not over `--config` /
`--config-file`). See [Environment variable overrides](#environment-variable-overrides)
for the full list. For directory-based switching without env vars, use
[conditional variables](#conditional-variables).

## UI settings

### Colorizing output

Possible values are `always`, `never`, `debug` and `auto` (default: `auto`).
`auto` will use color only when writing to a terminal. `debug` will print the
active labels alongside the regular colorized output.

This setting overrides the `NO_COLOR` environment variable (if set).

```toml
[ui]
color = "never" # Turn off color
```

### Custom colors and styles

You can customize the colors used for various elements of the UI. For example:

```toml
[colors]
commit_id = "green"
```

The following colors are available:

* black
* red
* green
* yellow
* blue
* magenta
* cyan
* white
* default

All of them but "default" come in a bright version too, e.g. "bright red". The
"default" color can be used to override a color defined by a parent style
(explained below).

You can also use a 6-digit hex code for more control over the exact color used:

```toml
[colors]
change_id = "#ff1525"
```

`jj` also supports colors from the [ANSI 256-color palette] as `ansi-color-<N>`,
where `<N>` is a number between 0 and 255:

```toml
[colors]
commit_id = "ansi-color-81"
```

[ANSI 256-color palette]: https://en.wikipedia.org/wiki/ANSI_escape_code#8-bit

If you use a string value for a color, as in the examples above, it will be used
for the foreground color. You can also set the background color, reverse colors
(swap foreground and background), or make the text bold, dim, italic,
underlined, or crossed-out. For that, you need to use a table:

```toml
[colors]
commit_id = { fg = "green", bg = "#ff1525", bold = true, underline = true }
change_id = { reverse = true, dim = false, italic = true }
```

The key names are called "labels". The above used `commit_id` as label. You can
also create rules combining multiple labels. The rules work a bit like CSS
selectors. For example, if you want to color commit IDs green in general but
make the commit ID of the working-copy commit also be underlined, you can do
this:

```toml
[colors]
commit_id = "green"
"working_copy commit_id" = { underline = true }
```

Parts of the style that are not overridden - such as the foreground color in the
example above - are inherited from the style of the parent label.

Which elements can be colored is not yet documented, but see the
[default color configuration][colors.toml] for some examples of what's possible.

[colors.toml]: https://github.com/jj-vcs/jj/blob/main/cli/src/config/colors.toml

### Default command

When `jj` is run with no explicit subcommand, the value of the
`ui.default-command` setting will be used instead. Possible values are any valid
subcommand name, subcommand alias, or user-defined alias (defaults to `"log"`).

```toml
[ui]
default-command = ["log", "--reversed"]
```

### Default description

The editor content of a commit description can be populated by the
`draft_commit_description` template. `self` is a [`Commit`
object](templates.md#commit-type).

```toml
[templates]
draft_commit_description = '''
concat(
  builtin_draft_commit_description,
  "\nJJ: ignore-rest\n",
  diff.git(),
)
'''
```

You can override only the `default_commit_description` value if you like, e.g.:

```toml
[template-aliases]
default_commit_description = '''
"

Closes #NNNN
"
'''
```

### Duplicate commit description

By default, `jj duplicate` copies the descriptions from the original commits.
You can customize this behavior by specifying the `duplicate_description`
template, which is given a [`Commit` object](templates.md#commit-type) of the
original commit.

```toml
[templates]
duplicate_description = '''
concat(
  description.trim_end(),
  "\n\n(cherry picked from commit ",
  commit_id,
  ")\n"
)
'''
```

Note that `description` usually ends with a `\n` if it is not blank. Use
`.trim_end()` to remove the `\n`.

### New commit description

When `jj new` creates a commit without an explicit `-m` message, it evaluates
the `new_description` template to populate the description. By default, this
template is empty.

You can customize this to automatically generate descriptions for new commits.
For example, to auto-generate merge commit messages:

```toml
[templates]
new_description = '''
if(parents.len() > 1,
  "Merge " ++ parents.skip(1).map(|p| try(
    p.bookmarks().first().name(),
    p.change_id().shortest(8)
  )).join(", ") ++ " into " ++ try(
    parents.first().bookmarks().first().name(),
    parents.first().change_id().shortest(8)
  ) ++ "\n",
  ""
)
'''
```

### Bookmark / tag listing order

By default, `jj bookmark list` and `jj tag list` display bookmarks and tags
sorted alphabetically by name. You can customize this sorting behavior by
specifying sort keys in your config file:

```toml
[ui]
bookmark-list-sort-keys = ["name"]
tag-list-sort-keys = ["name"]
```

The configuration works identically to using the `--sort` option. The following
sort keys are supported: `name`, `author-name`, `author-email`, `author-date`,
`committer-name`, `committer-email`, `committer-date`. Suffix the key with `-`
to sort in descending order. Multiple keys can be supplied here, the first key
is the most significant.

When the `--sort` option is used, the configuration is ignored.

### Bookmark advance default targets

The `jj bookmark advance` command moves bookmarks forward in the graph. The
default values for what bookmarks to advance and where to advance them to are:

```toml
[revsets]
bookmark-advance-from = 'heads(::to & bookmarks())'  # The closest bookmarks
bookmark-advance-to = '@'  # To the current working copy
```

The default `from` is likely to be a good fit for almost everyone. The `from`
revset has access to `to`, allowing it to find bookmarks relative to the
destination revision.

The default `to` is largely up to your preference and workflow. One simple
alternative which fits squash-heavy workflows is `@-`, while a more involved
and versatile alternative that advances to the closest "pushable" revision is:

```toml
[revsets]
bookmark-advance-to = 'closest_pushable(@)'

[revset-aliases]
# Closest revision that is mutable, described and either non-empty or a merge
'closest_pushable(to)' = '''
  heads(::to & mutable() & ~description(exact:"") & (~empty() | merges()))
'''
```

### Commit trailers

You can configure automatic addition of one or more trailers to commit
descriptions using the `commit_trailers` template.

Each line of the template is an individual trailer, usually in `Key: Value`
format.

Trailers defined in this template are deduplicated with the existing
description: if the entire line of a trailer is already present, it will not be
added again. To deduplicate based only on the trailer key, use the
`trailers.contains_key(key)` method within the template.

```toml
[templates]
commit_trailers = '''
format_signed_off_by_trailer(self)
++ if(!trailers.contains_key("Change-Id"), format_gerrit_change_id_trailer(self))'''
```

Some ready-to-use trailer templates are available for frequently used trailers:
* `format_signed_off_by_trailer(commit)` creates a "Signed-off-by" trailer
  using the committer info.
* `format_gerrit_change_id_trailer(commit)` creates a "Change-Id" trailer
  suitable to be used with Gerrit. It is based Jujutsu's change id.

Existing trailers are also accessible via `commit.trailers()`.

### Diff colors and styles

In color-words and git diffs, word-level hunks are rendered with underline. You
can override the default style with the following keys:

```toml
[colors]
# Highlight hunks with background
"diff removed token" = { bg = "#221111", underline = false }
"diff added token" = { bg = "#002200", underline = false }
# Alternatively, swap colors
"diff token" = { reverse = true, underline = false }
```

In color-words diffs, line numbers of context lines are rendered with decreased
intensity to highlight changed lines. You can also decrease intensity of context
contents.

```toml
[colors]
# Dim context line numbers (default)
"diff context line_number" = { dim = true }
# Dim context line numbers and contents (applies to color-words and git diffs)
"diff context" = { dim = true }
```

Colors and styles can be configured differently for each diff format:

```toml
[colors]
# Dim unchanged parts of changed lines in color-words diffs
"diff color_words removed" = { dim = true }
"diff color_words added" = { dim = true }
"diff color_words removed token" = { dim = false }
"diff color_words added token" = { dim = false }
```

### Diff format

```toml
[ui]
# Builtin formats: ":color-words" (default), ":git",
#                  ":summary", ":stat", ":types", ":name-only"
# or external command name and arguments (see below)
diff-formatter = ":git"
```

#### Color-words diff options

In color-words diffs, changed words are displayed inline by default when color
is enabled. When the formatter cannot emit color, changed words are always shown
on separate lines because inline changes would not be distinguishable.

Because it's difficult to read a diff line with many removed/added words, there's
a threshold to switch to traditional separate-line format in colorized output.
You can also change the default number of lines of context shown.

* `max-inline-alternation`: Maximum number of removed/added word alternation to
  inline. For example, `<added> ... <added>` sequence has 1 alternation, so the
  line will be inline if `max-inline-alternation >= 1`. `<added> ... <removed>
  ... <added>` sequence has 3 alternation.

  * `0`: disable inlining, making `--color-words` more similar to `--git`
  * `1`: inline removes-only or adds-only lines
  * `2`, `3`, ..: inline up to `2`, `3`, .. alternation
  * `-1`: inline all lines

  The default is `3`.

  **This parameter is experimental.** The definition is subject to change.

* `conflict`: How conflicts are processed and displayed.

   * `"materialize"`: compare materialized contents (default)
   * `"pair"`: compare individual pairs

   **This parameter is experimental.**

* `context`: Number of lines of context to show in the diff. The default is `3`.

```toml
[diff.color-words]
max-inline-alternation = 3
context = 3
```

#### Git diff options

In git diffs you can change the default number of lines of context shown.

* `context`: Number of lines of context to show in the diff. The default is `3`.

* `show-path-prefix`: Whether to show the `a/` and `b/` path prefixes in
  `diff --git` output. The default is `true`.

```toml
[diff.git]
context = 3
show-path-prefix = true
```

#### Diff stat options

When using a subcommand that displays the diff stat menu like `jj show --stat`,
the bar portion that renders as `++--` can be limited to a maximum width in
characters.

```toml
[diff.stat]
max-bar-width = 10
```

By default without this option set, the bar width is unbounded and will use the
remaining available space, with a minimum of 30% of the total width.

### Generating diffs by external command

If `ui.diff-formatter` is not a builtin format, the specified diff command will
be called.

```toml
[ui]
# Use Difftastic by default
diff-formatter = ["difft", "--color=always", "$left", "$right"]
# Use tool named "<name>" (see below)
diff-formatter = "<name>"
```

The external diff tool can also be enabled by `diff --tool <name>` argument.
For the tool named `<name>`, command arguments can be configured as follows.

```toml
[merge-tools.<name>]
# program = "<name>"  # Defaults to the name of the tool if not specified
diff-args = ["--color=always", "$left", "$right"]
```

- `$left` and `$right` are replaced with the paths to the left and right
  directories to diff respectively.

- `$width` is replaced with the number of terminal columns available to the diff
  content.

- If `diff-args` is not specified, `["$left", "$right"]` will be used by default.

- If `diff-args = []`, `jj` will refuse to use this tool for diff formatting.
  This is a way to explicitly state that a certain tool (e.g. `mergiraf`) does
  not work for viewing diffs.

By default `jj` will invoke external tools with a directory containing the left
and right sides. The `diff-invocation-mode` config can change this to file by file
invocations as follows:

```toml
[ui]
diff-formatter = "vimdiff"

[merge-tools.vimdiff]
diff-invocation-mode = "file-by-file"
```

By default `jj` will display a warning when the command exits with a non-success
error code. The `diff-expected-exit-codes` config can suppress this warning
message for specific exit codes:

```toml
[merge-tools.delta]
diff-expected-exit-codes = [0, 1]
```

### Conflict marker style

You can configure which style of conflict markers to use when materializing
conflicts:

```toml
[ui]
# Shows a single snapshot and one or more diffs to apply to it
conflict-marker-style = "diff"
# Shows a snapshot for each side and base of the conflict
conflict-marker-style = "snapshot"
# Uses Git's "diff3" conflict markers to support tools that depend on it
conflict-marker-style = "git"
```

For more details about these conflict marker styles, see the [conflicts
page](conflicts.md#conflict-markers).

### Set of immutable commits

You can configure the set of immutable commits via
`revset-aliases."immutable_heads()"`. The default set of immutable heads is
`builtin_immutable_heads()`, which in turn is defined as `trunk() | tags() |
untracked_remote_bookmarks() | untracked_remote_tags()`. For example, to also
consider the `release@origin` bookmark immutable:

```toml
[revset-aliases]
"immutable_heads()" = "builtin_immutable_heads() | release@origin"
```

To prevent rewriting commits authored by other users:

```toml
# The `trunk().. &` bit is an optimization to scan for non-`mine()` commits
# only among commits that are not in `trunk()`.
[revset-aliases]
"immutable_heads()" = "builtin_immutable_heads() | (trunk().. & ~mine())"
```

Ancestors of the configured set are also immutable. The root commit is always
immutable even if the set is empty.

Immutable commits (other than the root commit) can be rewritten using the
`--ignore-immutable` CLI flag.

!!! warning

    Using `--ignore-immutable` will allow you to rewrite any commit in the
    history, and all descendants, without warning. Use this power wisely, and
    remember `jj undo`.

### Behavior of prev and next commands

If you prefer using an "edit-based" workflow, rather than squashing
modifications into parent changes, you may find yourself using the `prev` and
`next` commands with their `--edit` flag often to move between your changes. You
can avoid having to type the `--edit` flag every time you need it by actually
making it the default:

```toml
[ui.movement]
edit = true
```

You can pass the `--no-edit` flag to `prev` and `next` if you find yourself
needing the original behavior.

## List

### Default Template

You can configure the template used when no `-T` is specified.

- `templates.config_list` for `jj config list`
- `templates.workspace_list` for `jj workspace list`

If you want to see the config variable origin (type and path) when you do `jj config list`
you can add this to your config:

```toml
[templates]
config_list = "builtin_config_list_detailed"
```

## Log

### Default revisions

You can configure the revisions `jj log` would show when neither `-r` nor any paths are specified.

```toml
[revsets]
# Show commits that are not in `main@origin`
log = "main@origin.."
```

The default value for `revsets.log` is `builtin_log()`, which is defined as
`'present(@) | ancestors(immutable_heads().., 2) | trunk()'`. You can use
`builtin_log()` as a starting point for your own custom log revset.

!!! warning

    When `revsets.short-prefixes` is not specified, it defaults to the value of `revsets.log`. This
    affects how change and commit id references are resolved when multiple commits share a common
    prefix. Changing `revsets.log` without also setting `revsets.short-prefixes` might increase the
    length of prefixes needed on the command-line, especially in large repositories.

### Default revisions for operation diffs

You can configure the set of revisions that are considered "interesting" when
showing the difference between two operations (e.g. in `jj op show`,
`jj op diff`, or `jj op log -p`). Revisions that are not in this set are
elided into a summary count.

```toml
[revsets]
# Only show mutable revisions and the trunk
op-diff-changes-in = "mutable() | trunk()"
```

The default value for `revsets.op-diff-changes-in` is
`'mutable() | immutable_heads()'`.

### Prioritize Revsets in the Log over @

In some situations the default graph can be hard to read, for example when working with big merges.
To improve this behavior you can configure which revset in the `jj log` graph is displayed on
the left instead of `@`.

The following example will prioritize the change with the description "megamerge" with a fallback
to `trunk()` in case no such change exists:

```toml
[revsets]
log-graph-prioritize = 'coalesce(description("megamerge\n"), trunk())'
```

### Default Template

You can configure the template used when no `-T` is specified.

- `templates.evolog` for `jj evolog`
- `templates.log` for `jj log`
- `templates.show` for `jj show`
- `templates.op_log` for `jj op log`
- `templates.op_show` for `jj op show`

```toml
[templates]
# Use builtin evolog template
evolog = "builtin_evolog_compact"
# Use builtin log template
log = "builtin_log_compact"
# Use builtin show template
show = "builtin_log_detailed"
# Use builtin op log template
op_log = "builtin_op_log_compact"
# Use builtin op log template
op_show = "builtin_op_log_compact"
```

If you want to see the full description when you do `jj log` you can add this to
your config:

```toml
[templates]
log = "builtin_log_compact_full_description"
```

### Graph style

```toml
[ui]
# Possible values: "curved" (default), "square", "ascii", "ascii-large"
graph.style = "square"
```

#### Node style

The symbols used to represent commits or operations can be customized via
templates.

- `templates.log_node` for commits (with `Option<Commit>` keywords)
- `templates.op_log_node` for operations (with `Operation` keywords)

For example:

```toml
[templates]
log_node = '''
coalesce(
  if(!self, "🮀"),
  if(current_working_copy, "@"),
  if(root, "┴"),
  if(immutable, "●", "○"),
)
'''
op_log_node = 'if(current_operation, "@", "○")'
```

### Wrap log content

If enabled, `log`/`evolog`/`op log` content will be wrapped based on
the terminal width.

```toml
[ui]
log-word-wrap = true
```

### Display of commit and change ids

Can be customized by the `format_short_id()` template alias.

```toml
[template-aliases]
# Highlight unique prefix and show at least 12 characters (default)
'format_short_id(id)' = 'id.shortest(12)'
# Just the shortest possible unique prefix
'format_short_id(id)' = 'id.shortest()'
# Show unique prefix and the rest surrounded by brackets
'format_short_id(id)' = 'id.shortest(12).prefix() ++ "[" ++ id.shortest(12).rest() ++ "]"'
# Always show 12 characters
'format_short_id(id)' = 'id.short(12)'
```

To customize these separately, use the `format_short_commit_id()` and
`format_short_change_id()` aliases:

```toml
[template-aliases]
# Uppercase change ids. `jj` treats change and commit ids as case-insensitive.
'format_short_change_id(id)' = 'format_short_id(id).upper()'
```

Operation ids can be customized by the `format_short_operation_id()` alias:

```toml
[template-aliases]
# Always show 12 characters
'format_short_operation_id(id)' = 'id.short(12)'
```

To get shorter prefixes for certain revisions, set `revsets.short-prefixes`:

```toml
[revsets]
# Prioritize the current bookmark
short-prefixes = "(main..@)::"
```

### Relative timestamps

Can be customized by the `format_timestamp()` template alias.

```toml
[template-aliases]
# Full timestamp in ISO 8601 format
'format_timestamp(timestamp)' = 'timestamp'
# Relative timestamp rendered as "x days/hours/seconds ago"
'format_timestamp(timestamp)' = 'timestamp.ago()'
```

`jj op log` defaults to relative timestamps. To use absolute timestamps, you
will need to modify the `format_time_range()` template alias.

```toml
[template-aliases]
'format_time_range(time_range)' = 'time_range.start() ++ " - " ++ time_range.end()'
```

### Author format

Can be customized by the `format_short_signature()` template alias.

```toml
[template-aliases]
# Full email address (default)
'format_short_signature(signature)' = 'signature.email()'
# Both name and email address
'format_short_signature(signature)' = 'signature'
# Username part of the email address
'format_short_signature(signature)' = 'signature.email().local()'
```

### Commit timestamp

Commits have both an "author timestamp" and "committer timestamp". By default,
jj displays the committer timestamp, but can be changed to show the author
timestamp instead.

The function must return a timestamp because the return value will likely be
formatted with `format_timestamp()`.

```toml
[template-aliases]
'commit_timestamp(commit)' = 'commit.author().timestamp()'
```

### Signature format

Can be enabled with `ui.show-cryptographic-signatures`, and
customized with `format_short_cryptographic_signature(sig)` and
`format_detailed_cryptographic_signature(sig)`.

Note that the formatting functions take an `Option<CryptographicSignature>`.
This allows you to emit a custom message if a signature is not present, but
will raise an error if you try to access methods on a signature that is not
available.

```toml
[ui]
# default is false
show-cryptographic-signatures = true

[template-aliases]
'format_short_cryptographic_signature(sig)' = '''
  if(sig,
    sig.status(),
    "(no sig)",
  )
'''
```

## Pager

By default, jj will paginate output that would scroll off the screen. It does
this by passing output through `less -FRXK` on most platforms (on Windows it uses
[the pager](#builtin-pager) that is built-in to jj).

Which pager to use can be customized by setting `ui.pager`. When choosing a
pager, ensure that it either supports color codes or that you disable color (see
[Colorizing output](#colorizing-output)).

The `JJ_PAGER` environment variable overrides `ui.pager`, and is useful for
tool-specific environments that should not affect other programs. The generic
`PAGER` environment variable is ignored.

Examples:

```shell
# Pipe output through `less -FRXK` (default on non-Windows platforms)
$ jj config set --user ui.pager "less -FRXK"

# Use the built-in pager (default on Windows)
$ jj config set --user ui.pager :builtin

# Use `$PAGER` environment variable if set (on non-Windows platforms)
$ jj config set --user ui.pager '["sh", "-c", "exec ${PAGER:-less -FRXK}"]'
```

Additionally, paging behavior can be toggled via `ui.paginate` like so:

```toml
[ui]
# Enable pagination for commands that support it (default)
paginate = "auto"
# Disable all pagination, equivalent to using --no-pager
paginate = "never"
```

### Builtin pager

Our builtin pager is based on
[`streampager`](https://github.com/markbt/streampager/) but is configured within
`jj`'s config. It is configured via the `ui.streampager` table.

#### Key bindings

The built-in pager supports both navigation via arrows and Vim-style navigation.
Beyond that, here are some useful keybindings for the pager:

| Key             | Action                |
| :-------------- | :-------------------- |
| `Ctrl-c` or `q` | Quit                  |
| `h` or `F1`     | Show all key bindings |
| `Esc`           | Close help or prompt  |
| `\`             | Toggle line wrapping  |
| `#`             | Toggle line numbers   |
| `Ctrl-r`        | Toggle the ruler      |

The built-in pager does not support mouse input.

#### Wrapping config

Wrapping performed by the pager happens *in addition to* any
wrapping that `jj` itself does.

```toml
[ui.streampager]
wrapping = "anywhere"  # wrap at screen edge (default)
wrapping = "word"      # wrap on word boundaries
wrapping = "none"      # strip long lines, allow scrolling
                       # left and right like `less -S`
```

#### Auto-exit, clearing the screen on startup or exit

You can configure whether the pager clears the screen on startup or exit, and
whether it quits automatically on short inputs. When the pager auto-quits,
features like word-wrapping are disabled.

```toml
[ui.streampager]
# Do not clear screen on exit. Use a full-screen interface for long
# output only. Like `less -FX`.
interface = "quit-if-one-page"  # (default).
# Always use a full-screen interface, ask the terminal to clear the
# screen on exit. Like `less -+FX`.
interface = "full-screen-clear-output"
# Use the alternate screen if the input is either long or takes more
# than 2 seconds to finish. Similar but not identical to `less -F -+X`.
interface = "quit-quickly-or-clear-output"
```

#### Showing the ruler on startup

```toml
[ui.streampager]
# Start with the ruler showing
show-ruler = true # (default)
# Start with the ruler hidden
show-ruler = false
```

### Processing contents to be paged

If you'd like to pass the output through a formatter e.g.
[`diff-so-fancy`](https://github.com/so-fancy/diff-so-fancy) before piping it
through a pager you must do it using a subshell as, unlike `git` or `hg`, the
command will be executed directly. For example:

```toml
[ui]
pager = ["sh", "-c", "diff-so-fancy | less -RFX"]
```

Some formatters (like [`delta`](https://github.com/dandavison/delta)) require
git style diffs for formatting. You can configure this style of
diff as the default with the `ui.diff-formatter` setting. For example:

```toml
[ui]
pager = "delta"
diff-formatter = ":git"
```

## Aliases

You can define aliases for commands, including their arguments. For example:

```toml
[aliases]
# `jj l` shows commits on the working-copy commit's (anonymous) bookmark
# compared to the `main` bookmark
l = ["log", "-r", "(main..@):: | (main..@)-"]
```

### Alias descriptions

Alias descriptions can be surfaced in shell completions by defining the alias
as a table with `.doc` and `.definition` properties. For example:

```toml
[aliases]
l = {
    definition = ["log", "-r", "(main..@):: | (main..@)-"],
    doc = "Log pending changes"
}
```

You can also use the dotted key syntax:

```toml
[aliases]
l.definition = ["log", "-r", "(main..@):: | (main..@)-"]
l.doc = "Log pending changes"
```

This alias syntax can only run a single jj command. However, you may want to
execute multiple jj commands with a single alias, or run arbitrary scripts that
complement your version control workflow. This can be done, but be aware of the
danger:

!!! warning

    The following technique just provides a convenient syntax for running
    arbitrary code on your system. Using it irresponsibly may cause damage
    ranging from breaking the behavior of `jj undo` to wiping your file system.
    Exercise the same amount of caution while writing these aliases as you would
    when typing commands into the terminal!

    This feature may be removed or replaced by an embedded scripting language in
    the future.

The command `jj util exec` will simply run any command you pass to it as an
argument. Additional arguments are passed through. Here are some examples:

```toml
[aliases]
my-script = ["util", "exec", "--", "my-jj-script"]
#                            ^^^^
# This makes sure that flags are passed to your script instead of parsed by jj.
my-inline-script = ["util", "exec", "--", "bash", "-c", """
set -euo pipefail
echo "Look Ma, everything in one file!"
echo "args: $@"
""", ""]
#    ^^
# This last empty string will become "$0" in bash, so your actual arguments
# are all included in "$@" and start at "$1" as expected.
```

> Note: Shebangs (e.g. `#!/usr/bin/env`) aren't necessary since you're already
> explicitly passing your script into the right shell.

### Disabling aliases

Sometimes you may want to disable an alias, such as a built-in alias that you
prefer not to use (for example, `jj des<tab>` may result in `jj desc<cursor>`
instead of `jj describe <cursor>` because there are two possible completions,
`desc` and `describe`, with the same prefix) or an alias defined in an earlier
config that you want to disable in a later layer (such as in a repo config or
in a [conditional variables](#conditional-variables) scope). To do so, set
`aliases.<name>.enabled` to `false`:

```toml
[aliases]
desc.enabled = false

best-log = {
  enabled = false,  # WIP; don't enable yet
  doc = "The best `jj log`",
  definition = ["log", "-r", "the_perfect_revset()"]
}
```

Normal alias overriding rules apply, which means another configuration file may
define the alias again later. Disabling aliases only applies to the `aliases`
table; `template-aliases`, `revset-aliases`, and `fileset-aliases` cannot be
disabled.

## Editor

The default editor is set via `ui.editor`, though there are several places to
set it. The priority is as follows (environment variables are marked with
a `$`):

`$JJ_EDITOR` > `ui.editor` > `$VISUAL` > `$EDITOR`

Nano is the default editor (Notepad on Windows) in the absence of any other
setting, but you could set it explicitly too.

```toml
[ui]
editor = "pico"
```

To use NeoVim instead:

```toml
[ui]
editor = ["nvim", "+$line"]
```

For GUI editors you possibly need to use a `-w` or `--wait`. Some examples:

```toml
[ui]
editor = "code -w"       # VS Code
editor = "code.cmd -w"   # VS Code on Windows
editor = "bbedit -w"     # BBEdit
editor = "subl -n -w"    # Sublime Text
editor = "mate -w"       # TextMate
editor = ["C:/Program Files/Notepad++/notepad++.exe",
    "-multiInst", "-notabbar", "-nosession", "-noPlugin"] # Notepad++
editor = "idea --temp-project --wait"   #IntelliJ
```

Obviously, you would only set one line, don't copy them all in!

The following variables will be substituted:

- `$path`: File path (optional)
- `$line`: 1-based line number for cursor positioning

## Editing diffs

The `ui.diff-editor` setting affects the default tool used for editing diffs
(e.g. `jj split`, `jj squash -i`). If it is not set, the special value
`:builtin` is used. It launches a built-in TUI tool (known as [scm-diff-editor])
to edit the diff in your terminal.

[scm-diff-editor]: https://github.com/arxanas/scm-record?tab=readme-ov-file#scm-diff-editor

You can try a different tool temporarily by doing e.g. `jj split --tool meld` or
you can set the option to change the default. This requires that you have an
appropriate tool installed, see for example [the instructions for using
Meld](#using-meld-as-a-diff-editor).

**Suggestion:** If possible, it is recommended to try an external diff tool like
[Meld](#using-meld-as-a-diff-editor) (see below for some other possibilities)
for splitting commits and other diff editing, in addition to the built-in diff
editor. It is good to know the capabilities of both. The built-in diff editor
does not require external tools to be available, is faster for tasks like
picking hunks, and does not require leaving the terminal. External tools give
you the flexibility of picking out portions of lines from the diff or even
arbitrarily editing the text of the files.

If `ui.diff-editor` is a string, e.g. `"meld"`, the arguments will be read from
the following config keys.

```toml
[merge-tools.meld]
# program = "meld"      # Defaults to the name of the tool if not specified
program = "/path/to/meld" # May be necessary if `meld` is not in the PATH
edit-args = ["--newtab", "$left", "$right"]
```

`jj` makes the following substitutions:

- `$left` is a directory containing the original contents before any changes.

- `$right` is the directory containing the changed contents.
  Edits are read back from here, so this is where tools should make changes.

- If no `edit-args` are specified, `["$left", "$right"]` are set by default.

- If `edit-args = []`, `jj` will refuse to use this tool for diff editing. This is a way to explicitly state that a certain tool (e.g. `mergiraf`) does not work for diff editing.

Like diff viewing, diff editors are invoked with a directory containing the left
and right sides by default. The `edit-invocation-mode` config controls this
independently of `diff-invocation-mode`, so you can, for example, keep
directory-based diff viewing but launch the diff editor once per changed file:

```toml
[merge-tools.my-tool]
diff-invocation-mode = "dir"
edit-invocation-mode = "file-by-file"
```

Finally, `ui.diff-editor` can be a list that specifies a command and its arguments.

Some examples:

```toml
[ui]
# Use merge-tools.meld.edit-args
diff-editor = "meld"  # Or `kdiff3`, or `diffedit3`, ...
# Specify edit-args inline
diff-editor = ["/path/to/binary", "--be-helpful", "$left", "$right"]
# Equivalent to ["binary", "$left", "$right"] arguments by default
diff-editor = "binary"
```

### Experimental 3-pane diff editing

We offer two special "3-pane" diff editor configs:

- `meld-3`, which requires installing [Meld](https://meldmerge.org/), and
- `diffedit3`, which requires installing [`diffedit3`](https://github.com/ilyagr/diffedit3/releases).

`Meld` is a graphical application that is recommended, but can be difficult to
install in some situations. `diffedit3` is designed to be easy to install and to
be usable in environments where Meld is difficult to use (e.g. over SSH via port
forwarding). `diffedit3` starts a local server that can be accessed via a web
browser, similarly to [Jupyter](https://jupyter.org/).

There is also the `diffedit3-ssh` which is similar to `diffedit3` but does not
try to open the web browser pointing to the local server (the URL
printed to the terminal) automatically. `diffedit3-ssh` also always uses ports in between
17376-17380 and fails if they are all busy. This can be useful when working
over SSH. Open the fold below for more details of how to set that up.

<details>
<summary> Tips for using `diffedit3-ssh` over SSH </summary>

To use `diffedit3` over SSH, you need to set up port forwarding. One way to do
this is to start SSH as follows (copy-paste the relevant lines):

```shell
ssh -L 17376:localhost:17376 \
    -L 17377:localhost:17377 \
    -L 17378:localhost:17378 \
    -L 17379:localhost:17379 \
    -L 17380:localhost:17380 \
    myhost.example.com
```

`diffedit3-ssh` is set up to use these 5 ports by default. Usually, only the
first of them will be used. The rest are used if another program happens to use
one of them, or if you run multiple instances of `diffedit3` at the same time.

Another way is to add a snippet to `~/.ssh/config`:

```ssh-config
Host myhost
    User     myself
    Hostname myhost.example.com
    LocalForward 17376 localhost:17376
    LocalForward 17377 localhost:17377
    LocalForward 17378 localhost:17378
    LocalForward 17379 localhost:17379
    LocalForward 17380 localhost:17380
```

With that configuration, you should be able to simply `ssh myhost`.

</details>

Setting either `ui.diff-editor = "meld-3"` or `ui.diff-editor = "diffedit3"`
will result in the diff editor showing 3 panes: the diff on the left and right,
and an editing pane in the middle. This allow you to see both sides of the
original diff while editing.

If you use `ui.diff-editor = "meld-3"`, note that you can still get the 2-pane
Meld view using `jj diff --tool meld`. `diffedit3` has a button you can use to
switch to a 2-pane view.

To configure other diff editors in this way, you can include `$output` together
with `$left` and `$right` in `merge-tools.TOOL.edit-args`. `jj` will replace
`$output` with the directory where the diff editor will be expected to put the
result of the user's edits. Initially, the contents of `$output` will be the
same as the contents of `$right`.

### `JJ-INSTRUCTIONS`

When editing a diff, jj will include a synthetic file called `JJ-INSTRUCTIONS`
in the diff with instructions on how to edit the diff. Any changes you make to
this file will be ignored. To suppress the creation of this file, set
`ui.diff-instructions = false`.

### Using Meld as a diff editor

[Meld](https://meldmerge.org) is a nice and polished free diff editor. It can be
obtained as follows:

- **Linux:** use your favorite package manager, e.g. `sudo apt install meld`.

- **Windows:** Meld can be downloaded from <https://meldmerge.org/>.

- **Mac OS:** Install Homebrew and run `brew install --cask meld`.
  This will install both an app in `/Applications/Meld.app` and the command-line
  `meld` command that `jj` uses. You can read about [more details and other
  options](https://gist.github.com/ilyagr/1b40f6061d8ad320cee4c12843df1a23) but,
  as of this writing, this is by far the easiest.

`jj` has two diff editing configurations that use Meld: `meld` for a 2-pane view
and `meld-3` for a [three-pane view](#experimental-3-pane-diff-editing).

There is also a `meld` [merge tool](#3-way-merge-tools-for-conflict-resolution)
that can be useful, but does not support displaying the merge base while
merging.

### Using Vim as a diff editor

Using `ui.diff-editor = "vimdiff"` is possible but not recommended. For a better
experience, you can follow [instructions from the Wiki] to configure the
[DirDiff Vim plugin] and/or the [vimtabdiff Python script].

[instructions from the Wiki]: https://github.com/jj-vcs/jj/wiki/Vim,-Neovim#using-vim-as-a-diff-tool

[DirDiff Vim plugin]: https://github.com/will133/vim-dirdiff
[vimtabdiff Python script]: https://github.com/balki/vimtabdiff

## 3-way merge tools for conflict resolution

The `ui.merge-editor` key specifies the tool used for three-way merge tools
by `jj resolve`. For example:

```toml
[ui]
# Use merge-tools.meld.merge-args
merge-editor = "meld"  # Or "vscode" or "vscodium" or "kdiff3" or "vimdiff"
# Specify merge-args inline
merge-editor = ["meld", "$left", "$base", "$right", "-o", "$output"]
```

The following built-in tools are always available:

- `:builtin` launches a TUI tool for 3-way merges (the default).
- `:editor` opens the conflicted file with conflict markers in your text editor
  (`ui.editor`). Unlike the other tools, it supports conflicts with any number
  of sides. Conflict markers left in the file are kept as a partially resolved
  conflict.
- `:ours` and `:theirs` pick side #1 or side #2 of the conflict respectively.

The following tools can be used out of the box, as long as they are installed:
- "kdiff3"
- "meld"
- "mergiraf"
- "smerge"
- "vimdiff"
- "vscode"
- "vscodium"

Using VS Code as a merge tool works well with VS Code's [Remote
Development](https://code.visualstudio.com/docs/remote/remote-overview)
functionality, as long as `jj` is called from VS Code's terminal.

### Setting up a custom merge tool

To use a different tool named `TOOL`, the arguments to pass to the tool MUST be
specified either inline or in the `merge-tools.TOOL.merge-args` key. As an
example of how to set this key and other tool configuration options, here is
the out-of-the-box configuration of the three default tools. (There is no need
to copy it to your config file verbatim, but you are welcome to customize it.)

```toml
[merge-tools.kdiff3]
# program  = "kdiff3"     # Defaults to the name of the tool if not specified
merge-args = ["$base", "$left", "$right", "-o", "$output", "--auto"]
[merge-tools.meld]
merge-args = ["$left", "$base", "$right", "-o", "$output", "--auto-merge"]

[merge-tools.vimdiff]
merge-args = ["-f", "-d", "$output", "-M",
    "$left", "$base", "$right",
    "-c", "wincmd J", "-c", "set modifiable",
    "-c", "set write"]
program = "vim"
merge-tool-edits-conflict-markers = true    # See below for an explanation
```

`jj` makes the following substitutions:

- `$output` (REQUIRED) is replaced with the name of the file that the merge tool
  should output. `jj` will read this file after the merge tool exits.

- `$left` and `$right` are replaced with the paths to two files containing the
  content of each side of the conflict.

- `$base` is replaced with the path to a file containing the contents of the
  conflicted file in the last common ancestor of the two sides of the conflict.

- `$marker_length` is replaced with the length of the conflict markers which
  should be used for the file. This can be useful if the merge tool parses
  and/or generates conflict markers. Usually, `jj` uses conflict markers of
  length 7, but they can be longer if necessary to make parsing unambiguous.

- `$path` is replaced with the path in the repository at which the file
  will be eventually stored. It is relative to the root directory of the
  repository and uses `/` as separators.

Unlike `diff-args` or `edit-args`, there is no default value for `merge-args`.
If `merge-args` are not specified, the tool cannot be used for conflict
resolution.

### Editing conflict markers with a tool or a text editor

By default, the merge tool starts with an empty output file. If the tool puts
anything into the output file and exits with the 0 exit code,
`jj` assumes that the conflict is fully resolved, while if the tool exits with
a non-zero exit code, `jj` assumes that the merge should be canceled.
This is appropriate for most graphical merge tools.

For merge tools which try to automatically resolve conflicts without user input,
this behavior may not be desired. For instance, some automatic merge tools use
an exit code of 1 to indicate that some conflicts were unable to be resolved and
that the output file should contain conflict markers. In that case, you could
set the config option `merge-tools.TOOL.merge-conflict-exit-codes = [1]` to tell
`jj` to expect conflict markers in the output file if the exit code is 1. If a
merge tool produces output using Git's "diff3" conflict style, `jj` should be
able to parse it correctly, so many Git merge drivers should be usable with `jj`
as well.

Some tools (e.g. `vimdiff`) can present a multi-way diff but don't resolve
conflict themselves. When using such tools, `jj`
can help you by populating the output file with conflict markers before starting
the merge tool (instead of leaving the output file empty and letting the merge
tool fill it in). To do that, set the
`merge-tools.vimdiff.merge-tool-edits-conflict-markers = true` option.

With this option set, if the output file still contains conflict markers after
the conflict is done, `jj` assumes that the conflict was only partially resolved
and parses the conflict markers to get the new state of the conflict. The
conflict is considered fully resolved when there are no conflict markers left.
The conflict marker style can also be customized per tool using the
`merge-tools.TOOL.conflict-marker-style` option, which takes the same values as
[`ui.conflict-marker-style`](#conflict-marker-style).

## Code formatting and other file content transformations

The `jj fix` command allows you to efficiently rewrite files in complex commit
graphs with no risk of introducing conflicts, using tools like `clang-format` or
`prettier`. The tools run as subprocesses that take a file content on standard
input and repeat it, with any desired changes, on standard output.

This enables the command to format many versions of the same file concurrently
and without touching on the working copy. The local files are not and must not
be used.

The file is only rewritten if the subprocess produces a successful exit code.

### Configuration

Tools are defined in a table where the keys are arbitrary identifiers and the
values have the following properties:

- `command`: The arguments used to run the tool. The first argument is the path
  to an executable file. Arguments can contain these variables that will be
  replaced:
  - `$root` will be replaced with the workspace root path (the directory
    containing the .jj directory).
  - `$path` will be replaced with the repo-relative path of the file being
    fixed. It is useful to provide the path to tools that include the path in
    error messages, or behave differently based on the directory or file name.
- `patterns`: List of filesets (see: `jj help -k filesets`), determining which
  files the tool will affect based on their path. If this list is empty, no
  files will be affected by the tool. If there are multiple patterns, the tool
  is applied only once to each file in the union of the patterns.
- `enabled`: Enables or disables the tool. If omitted, the tool is enabled. This
  is useful for defining disabled tools in user configuration that can be
  enabled in individual repositories with one config setting.
- `line-range-args`: Optional array of template strings specifying how to pass a
  line range to the tool. It may include the variables `$first` and `$last`,
  which will be replaced with the 1-based line numbers of the first and last
  lines inside the modified range, respectively. A range containing a single
  line will have equal values for `$first` and `$last`. Empty ranges
  representing deletions will be skipped because they cannot be represented this
  way. If the tool does not support formatting specific line ranges, this
  setting should be omitted, and the tool will always process the entire file.
  - Example: `line-range-args = ["--lines=$first:$last"]`
  - Example: `line-range-args = ["--line-start=$first", "--line-end=$last"]`
- `run-tool-if-zero-line-ranges`: Whether to run the tool invocation for this
  tool on files that had zero line ranges to format in the revision being fixed.
  Defaults to `false`, meaning the tool is skipped for these files. This default
  behavior serves two main purposes:
  - Avoiding unnecessary executions of tools when no line ranges are modified.
  - Preventing tools configured with `line-range-args` from making overly broad
    changes (formatting the whole file) when no line ranges are available.

  Setting this to `true` is useful if the tool should run regardless of diffs
  (e.g., to sort imports, or run with `--include-unchanged-files`).

`jj fix` provides the file content anonymously on standard input, but the name
of the file being formatted may be important for include sorting or other output
like error messages. To address this, you can use the `$root` and `$path` in
your arguments.

### Enforce coding style rules

Suppose you want to use `clang-format` to format your `*.c` and `*.h` files,
as well as sorting their `#include` directives.

```toml
[fix.tools.clang-format]
command = ["/usr/bin/clang-format", "--sort-includes", "--assume-filename=$path"]
patterns = ["glob:'**/*.c'",
            "glob:'**/*.h'"]
```

### Sort and remove duplicate lines from a file

`jj fix` can also be used with tools that are not considered code formatters.

Suppose you have a list of words in a text file in your repository, and you want
to keep the file sorted alphabetically and remove any duplicate words.

```toml
[fix.tools.sort-word-list]
command = ["sort", "-u"]
patterns = ["word_list.txt"]
```

### Enforce code formatting on modified lines

You can configure `jj fix` to run a formatter only on the modified lines of a
file, rather than formatting the entire file. This is particularly useful for
avoiding unrelated formatting changes in untouched parts of a file.

To enable this, specify the `line-range-args` configuration to match the line
range argument format expected by your tool. The formatter will be invoked with
these arguments repeated for each modified line range (e.g. `--lines=10-20
--lines=30-40`).

Additionally, you can use `run-tool-if-zero-line-ranges` to control the
behavior when a diff results in zero line ranges (which can happen when lines
are only deleted or with `--include-unchanged-files`). If `false` (the default),
`jj` will skip the tool entirely for that file. If `true`, `jj` will run the tool
even when there are zero line ranges calculated. This is useful for tools that
perform file-wide operations, such as import sorting.

Note that you can use the `--all-lines` CLI flag for `jj fix` to ignore these
line ranges and format the entire modified file instead.

Example of `clang-format` with line ranges:

```toml
[fix.tools.clang-format]
command = ["/usr/bin/clang-format", "--assume-filename=$path"]
patterns = ["glob:'**/*.cc'",
            "glob:'**/*.h'"]
enabled = true
line-range-args = ["--lines=$first:$last"]
run-tool-if-zero-line-ranges = false
```

### Tools stored inside the workspace

Some fix tools may be stored inside the workspace. For example, a binary may be
stored inside `node_modules`. Use the `$root` variable to create an absolute
path to such a program:

```toml
[fix.tools.biome]
command = ["$root/node_modules/@biomejs/biome/bin/biome", "format", "--stdin-file-path=$path"]
patterns = ["glob:'**/*.ts'", "glob:'**/*.tsx'"]
```

### Execution order of tools

If two or more tools affect the same file, they are executed in the ascending
lexicographical order of their configured names. Any tool, but the first one to
be executed, get as input the content of the file produced by the previous
tool. This will remain as a tie breaker if other ordering mechanisms are
introduced in the future.

If you use numbers in tool names to control execution order, remember to
include enough leading zeros so that, for example, `09` sorts before `10`.

Suppose you want to keep only the 10 smallest numbers in a text file that
contains one number on each line. This can be accomplished with `sort` and
`head`, but execution order is important.

```toml
[fix.tools.1-sort-numbers-file]
command = ["sort", "-n"]
patterns = ["numbers.txt"]

[fix.tools.2-truncate-numbers-file]
command = ["head", "-n", "10"]
patterns = ["numbers.txt"]
```

### Disabling and enabling tools

Tools can be disabled and enabled with the optional `enabled` config. This
allows you to define tools globally but enable them only for specific
repositories.

In the user configuration, define a disabled tool for running rustfmt:

```toml
[fix.tools.rustfmt]
enabled = false
command = ["rustfmt", "--emit", "stdout"]
patterns = ["glob:'**/*.rs'"]
```

Then to use the tool in a specific repository, set the `enabled` config:

```shell
$ jj config set --repo fix.tools.rustfmt.enabled true
```

## `run`: Running commands across revisions {: #run }

The `jj run` command executes a command against each revision in a set,
checking out each one into an isolated working copy, running the command, and
amending the revision with any resulting changes.

### `run.jobs`: Default parallelism {: #run.jobs }

By default `jj run` processes one revision at a time. You can increase
parallelism with the `run.jobs` setting:

```toml
[run]
jobs = 8
```

The value must be a positive integer. The `--jobs` / `-j` CLI flag overrides
this setting for a single invocation:

```shell
jj run -j 4 -- cargo fmt
```

## Commit Signing

`jj` can be configured to sign and verify the commits it creates using either
GnuPG or SSH signing keys.

To do this you need to configure a signing backend.

Setting the backend to `"none"` disables signing.

### GnuPG Signing

```toml
[signing]
behavior = "own"
backend = "gpg"
## You can set `key` to anything accepted by `gpg -u`
## If not set then defaults to the key associated with `user.email`
# key = "4ED556E9729E000F"
# key = "signing@example.com"
```

By default the gpg backend will look for a `gpg` binary on your path. If you want
to change the program used or specify a path to `gpg` explicitly you can set:

```toml
[signing]
backends.gpg.program = "gpg2"
```

Also by default the gpg backend will consider key expiry when verifying commit signatures.
To consider expired keys as valid you can set:

```toml
[signing]
backends.gpg.allow-expired-keys = true
```

#### PKCS#12 Certificates

PKCS#12 certificates can be used to sign commits using the `gpgsm` backend.

```toml
[signing]
behavior = "own"
backend = "gpgsm"
## You can set `key` to anything accepted by `gpgsm -u`
## If not set then defaults to the key associated with `user.email`
# key = "4ED556E9729E000F"
# key = "signing@example.com"
```

By default the gpgsm backend will look for a `gpgsm` binary on your path. If you want
to change the program used or specify a path to `gpgsm` explicitly you can set:

```toml
[signing]
backends.gpgsm.program = "gpgsm"
```

Also by default the gpgsm backend will consider key expiry when verifying commit signatures.
To consider expired keys as valid you can set:

```toml
[signing]
backends.gpgsm.allow-expired-keys = true
```

### SSH Signing

```toml
[signing]
behavior = "own"
backend = "ssh"
key = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGj+J6N6SO+4P8dOZqfR1oiay2yxhhHnagH52avUqw5h"
## You can also use a path instead of embedding the key
# key = "~/.ssh/id_for_signing.pub"
```

By default the ssh backend will look for a `ssh-keygen` binary on your path. If you want
to change the program used or specify a path to `ssh-keygen` explicitly you can set:

```toml
[signing]
backends.ssh.program = "/path/to/ssh-keygen"
```

When verifying commit signatures the ssh backend needs to be provided with an allowed-signers
file containing the public keys of authors whose signatures you want to be able to verify.

You can find the format for this file in the
[ssh-keygen man page](https://man.openbsd.org/ssh-keygen#ALLOWED_SIGNERS). This can be provided
as follows:

```toml
[signing]
backends.ssh.allowed-signers = "/path/to/allowed-signers"
```

Additionally, an SSH KRL or list of revoked public keys (see
[ssh-keygen man page](https://man.openbsd.org/ssh-keygen#KEY_REVOCATION_LISTS)) can be provided
with the `revocation-list` option. If a public key is found in this file then any signature
relating to it is marked as invalid.

```toml
[signing]
backends.ssh.revocation-list = "/path/to/revocation-list"
```

### Manually signing commits

You can use [`jj sign`](./cli-reference.md#jj-sign)/[`jj unsign`](./cli-reference.md#jj-unsign)
to sign/unsign commits manually.

!!! warning

    `jj sign` always signs commits, even if they are already signed by the
    user. While this is cumbersome for users signing via hardware devices, we
    cannot reliably check if a commit is already signed without creating a
    signature (see [this issue](https://github.com/jj-vcs/jj/issues/5786)).

### Automatically signing commits

The `signing.behavior` configuration option has four different options for what
to do with signing commits on modification of a change (e.g., rebasing or edits).

- `drop`: do not automatically sign; if a change was signed before
  modification, drop that signing after modification.
- `keep`: if a change was signed before modification, and it was authored by
  you, attempt to sign it again after the modification.
- `own`: sign all commits that were authored by you when you modify them.
- `force`: sign all commits after modification, always, even if you are not the
  author.

To sign commits only when pushing, set `signing.behavior = "drop"` and
`git.sign-on-push = true`:

```toml
# Configure signing backend as before, but lazily signing only on push.
[signing]
behavior = "drop"
backend = "ssh"
key = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGj+J6N6SO+4P8dOZqfR1oiay2yxhhHnagH52avUqw5h"

[git]
sign-on-push = true
```

With this setup, commits are not signed when they are created or rewritten. At
push time, `git.sign-on-push` only signs commits that are not yet signed (that
are mutable and authored by you, behaving like `own` even though write-time
signing uses `drop`).

This is useful when the signing backend requires user interaction or is slow,
because the signing work is deferred to push time. If `signing.behavior` is set
to `own` or `force`, commits may already be signed when written, and
`git.sign-on-push` would have little or nothing left to sign.

The commits are signed in their own transaction which is committed before
the push starts. If the push fails, the commits stay signed and retrying
does not have to sign them again. If nothing needs signing, no extra
transaction is created.

Because signing and pushing are separate operations, `jj undo` of the push
operation would not undo the commit signatures.

## Commit Signature Verification

By default signature verification and display is **disabled** as it incurs a
performance cost when rendering medium to large change logs. You can enable it
by setting `ui.show-cryptographic-signatures` to true in your configuration.

If you want to display commit signatures in your templates, you can use
`commit.signature()` (see [Commit type](./templates.md#commit-type)). The
returned [CryptographicSignature
Type](./templates.md#cryptographicsignature-type) provides methods to retrieve
signature details.

## Git settings

### Default colocation

When creating a Git-backed Jujutsu repository, they use "colocated" mode by
default, which places the `.git` directory next to the `.jj` directory.
Colocation allows some amount of two-way interoperability, but it can perform
worse in large repos.

The setting `git.colocate` is a boolean option that controls whether or not the
`jj git init` and `jj git clone` commands should create colocated workspace by
default. Set `git.colocate` to `false` to disable it.

See [Colocated Jujutsu/Git workspaces](git-compatibility.md#colocated-jujutsugit-workspaces)
for more information.

### Default object hash format

Traditionally, Git used the SHA-1 hash function to compute the identifiers for
[objects]. Because SHA-1 is not considered cryptographically secure anymore, Git
is in the [process of transitioning][transition] to a stronger hash function,
namely SHA-256.

Currently, the object hash function can only be set when initializing a new Git
repository. The setting `git.object-hash` controls the default choice for that
purpose. It can take the values `sha1` (default) and `sha256`.

Note that at the moment there is no interopability between the formats, and not
all code forges support SHA-256 repositories yet.

[objects]: https://git-scm.com/book/en/v2/Git-Internals-Git-Objects
[transition]: https://git-scm.com/docs/hash-function-transition

### Default remotes for `jj git fetch` and `jj git push`

By default, if a single remote exists it is used for `jj git fetch` and `jj git
push`; however if multiple remotes exist, the default remote is assumed to be
named `"origin"`, just like in Git. Sometimes this is undesirable, e.g. when you
want to fetch from a different remote than you push to, such as a GitHub fork.

To change this behavior, you can modify the [repository
configuration](#config-files-and-toml) variable `git.fetch`, which can be a
single remote, or a list of remotes to fetch from multiple places:

```sh
jj config set --repo git.fetch "upstream"
jj config set --repo git.fetch '["origin", "upstream"]'
```

By default, the specified pattern matches remote names with glob syntax. You can
also use other [string pattern syntax](revsets.md#string-patterns):

```sh
jj config set --repo git.fetch "regex:'^(remote|upstream)'"
jj config set --repo git.fetch '["remote*", "upstream*"]'
```

Similarly, you can also set the variable `git.push` to cause `jj git push` to
push to a different remote:

```sh
jj config set --repo git.push "github"
jj config set --repo git.push "regex:'^(remote|upstream)'"
jj config set --repo git.push '["remote*", "upstream*"]'
```

### Default bookmarks and tags to fetch

You can configure which bookmarks and tags to fetch by default per remote, using
the `remotes.<name>.fetch-bookmarks`/`fetch-tags` config. The value is a [string
pattern](./revsets.md#string-patterns) that matches the names of the bookmarks
and tags to fetch. If `remotes.<name>.fetch-bookmarks` is not configured, the
default fetch refspecs for the remotes are read from the Git configuration.

The glob pattern supports only `*` (other wildcard characters like `?` are *not*
supported). You can combine patterns with logical operators to specify multiple
bookmarks and tags, but only union and negative intersection are supported.

```toml
[remotes.origin]
fetch-bookmarks = "~gh-pages"
fetch-tags = "v*"
```

### Automatic tracking of bookmarks

You can configure which bookmarks to track automatically per remote, using the
`remotes.<name>.auto-track-bookmarks` config. The value is a
[string pattern](./revsets.md#string-patterns) that matches the names of the
bookmarks to track. This setting is applied to all new bookmarks, including ones
created locally and ones fetched from the remote. For example:

```toml
[remotes.origin]
auto-track-bookmarks = "*"
```

This will simply track all bookmarks for the remote "origin". There are various
reasons to restrict which bookmarks to track:

- When collaborating with other people via the same remote, you may not want to
  track all the bookmarks of your collaborators. Similarly, you may not want to
  push some of your bookmarks to the remote at all; these may only be intended
  for local use. Many people use a "personal prefix" in their bookmark names.
  This marks a bookmark as belonging to one person, making it possible to track
  and push only bookmarks with that prefix. For example, Alice may set her
  configuration like so:

  ```toml
  [remotes.origin]
  auto-track-bookmarks = "alice/*"
  ```

  That way, bookmarks pushed by other people (who probably use a different
  prefix or none at all) are not tracked automatically. At the same time, Alice
  can create bookmarks without the prefix for local-only use. You can also
  configure Jujutsu to use your prefix for generated bookmark names; see the
  section
  ["Generated bookmark names on push"](#generated-bookmark-names-on-push).

- Another reason to restrict the bookmarks to track may be that you're not
  collaborating with people using the same remote, but a different one. For
  example, if you make a fork on GitHub, your fork will usually be called
  "origin", while the repository you forked from is usually called "upstream".
  In that case, you may want to track all bookmarks from your fork, but only the
  "main" bookmark from upstream. Here's an example how to achieve that:

  ```toml
  [remotes.origin]
  auto-track-bookmarks = "*"
  [remotes.upstream]
  auto-track-bookmarks = "main"
  ```

- If you're collaborating with people on the same remote, but your bookmark
  names don't follow a pattern that allows a bookmark's "owner" to be identified
  (e.g. `alice/*`), you can use `auto-track-created-bookmarks` instead:

  ```toml
  [remotes.origin]
  auto-track-created-bookmarks = "*"
  ```

  This pattern will only apply to bookmarks you create with `jj bookmark create`
  and `jj bookmark set`. The disadvantage of this option is the fact that you
  will have to track your own bookmarks manually if you fetch them from the
  remote. This may happen regularly if you use multiple computers to work on the
  same project.

- Lastly, you may be working on various projects with different conventions for
  bookmark names. In that case, it can be handy to apply different configuration
  to different (groups of) repositories. Read about how to do that in the
  section ["Conditional variables"](#conditional-variables).

### Default revisions to push

You can configure the default target revisions to push. By default, any tracking
bookmarks and tags pointing to these revisions are pushed.

The `remote` symbol expands to the name of the remote (e.g. `"origin"`).

```toml
[revsets]
git-push = 'remote_bookmarks(remote=exact:remote)..@'
```

### Automatic local bookmark creation on `jj git clone`

When cloning a new Git repository, `jj` by default creates a local bookmark
tracking the default remote bookmark (such as `main` for `main@origin`.) If you
aren't going to update the `main` bookmark locally, the tracking bookmark isn't
necessary.

This behavior can be disabled by

```toml
[git]
track-default-bookmark-on-clone = false
```

### Abandon commits that became unreachable in Git

By default, when `jj` imports refs from Git, it will look for commits that used
to be [reachable][reachable] but no longer are reachable. Those commits will
then be abandoned, and any descendant commits will be rebased off of them (as
usual when commits are abandoned). You can disable this behavior and instead
leave the Git-unreachable commits in your repo by setting:

```toml
[git]
abandon-unreachable-commits = false
```

[reachable]: https://git-scm.com/docs/gitglossary/#Documentation/gitglossary.txt-aiddefreachableareachable

### Replicating evolution history for fetched or imported Git commits

By default, when `jj` imports commits from Git, it attempts to reconstruct their
evolution history using change IDs. If you are importing a large number of
commits, you can temporarily disable this behavior to improve performance:

```toml
[git]
record-synthetic-predecessors = false
```

### Generated bookmark names on push

`jj git push --change` generates bookmark names with a prefix of "push-" by
default. You can pick a different prefix and formatting by setting the
`templates.git_push_bookmark` template. For example:

```toml
[templates]
git_push_bookmark = '"martinvonz/push-" ++ change_id.short()'
```

This template should include expressions like `change_id` to generate unique and
stable bookmark.

### Set of private commits

You can configure the set of private commits by setting `git.private-commits` to
a revset. The value is a revset of commits that Jujutsu will refuse to push. If
unset, all commits are eligible to be pushed.

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

If a commit is in `git.private-commits` but is already on the remote, then it is
not considered a private commit. Commits that are immutable are also excluded
from the private set.

Private commits prevent their descendants from being pushed, since doing so
would require pushing the private commit as well.

### Git subprocessing behavior

Git remote interactions are handled by spawning a `git` subprocess.
If `git` is not on your OS path, or you want to specify a particular binary,
you can:

```toml
[git]
executable-path = "/path/to/git"
```

## Gerrit settings

### Default remote

To configure the default remote that `jj gerrit upload` will push to, set
`gerrit.default-remote`. This can be overridden with the `--remote` option.

```toml
[gerrit]
default-remote = "gerrit"
```

### Default remote branch

To configure the default branch that `jj gerrit upload` will push to, set
`gerrit.default-remote-branch`. This can be overridden with the
`--remote-branch` option.

```toml
[gerrit]
default-remote-branch = "main"
```

### `Link` trailer

Since version 3.3.1 Gerrit supports an alternative to the `Change-Id` trailer,
using a [`Link` trailer][Gerrit Link trailer] in the format of
`<review-url>/id/I<change-id>`. To have `jj gerrit upload` automatically add a
`Link` trailer instead of a `Change-Id` trailer, set `gerrit.review-url` to the
URL preview you'd like to use.

```toml
[gerrit]
review-url = "https://review.gerrithub.io/your/project"
```

Note that if a `Change-Id` or `Link` trailer already exists in a change, then
`jj gerrit upload` will not add any trailer.

[Gerrit Link trailer]: https://gerrit-documentation.storage.googleapis.com/Documentation/3.3.1/cmd-hook-commit-msg.html

## Merge settings

### Granularity of hunks

`jj` by default resolves content conflicts by splitting text into line-level
hunks and merge them. This can be configured to split hunks further into
word-level hunks.

* `line`: split into line hunks (default)
* `word`: split into word hunks

```toml
[merge]
hunk-level = "line"
```

### Resolution of same-change conflicts

`jj` by default resolves conflicts if all sides made the same change. This
matches what Git and Mercurial do (in the 3-way case at least), but not what
Darcs does. See the technical docs on the
[same-change rule](technical/conflicts.md#same-change-rule) for background and
trade-offs. It also means that repeated 3-way merging of multiple trees may give
different results depending on the order of merging. To turn it off, set
`same-change = "keep"`.

* `keep`: leave same-change conflict unresolved
* `accept`: resolve same-change conflict to new value (default)

```toml
[merge]
same-change = "accept"
```

## Converge settings

The `jj converge` command attempts to resolve divergence by replacing two or
more divergent commits with a single commit. The command accepts a
`--revisions REVSET` argument, and looks for divergence within the commits that
match that revset. The `--revisions` argument is optional. By default
`jj converge` uses the `revsets.converge` revset:

```toml
[revsets]
converge = "mutable() & divergent()"
```

## Filesystem monitor

In large repositories, it may be beneficial to use a "filesystem monitor" to
track changes to the working copy. This allows `jj` to take working copy
snapshots without having to rescan the entire working copy.

This is governed by the `fsmonitor.backend` option. Currently, the valid values
are `"none"` or `"watchman"`.

### Watchman

To configure the Watchman filesystem monitor, set
`fsmonitor.backend = "watchman"`. Ensure that you have [installed the Watchman
executable on your system](https://facebook.github.io/watchman/docs/install).

You can configure `jj` to use watchman triggers to automatically create
snapshots on filesystem changes by setting
`fsmonitor.watchman.register-snapshot-trigger = true`.

A full configuration for watchman can look like this:

```toml
[fsmonitor]
backend = "watchman"

[fsmonitor.watchman]
register-snapshot-trigger = true
```

You can check whether Watchman is enabled and whether it is installed correctly
using `jj debug watchman status`.

Note: `watchman` heavily uses `inotify` and sets up a user watch per-file. On
large repositories, this may cause `watchman` to fail and commands like
`jj status` to take longer than expected. If you experience this run
`jj debug watchman status` and tune your `inotify` limits.

## Snapshot settings

### Paths to automatically track

All new files in the working copy that don't match the ignore patterns are
tracked by default. You can set the `snapshot.auto-track` to set which paths
get automatically tracked when they're added to the working copy. See the
[fileset documentation](filesets.md) for the syntax. Files with paths matching
[ignore files](working-copy.md#ignored-files) are never tracked automatically.

If you set `snapshot.auto-track` to a non-default value, untracked files can be
tracked with `jj file track`.

You can use `jj file untrack` to untrack a file while keeping it in the working
copy. However, first [ignore](working-copy.md#ignored-files) them or remove them
from the `snapshot.auto-track` patterns; otherwise they will be immediately
tracked again.

### Maximum size for new files

By default, as an anti-footgun measure, `jj` will refuse to add new files to the
snapshot that are larger than a certain size; the default is 1MiB. This can be
changed by setting `snapshot.max-new-file-size` to a different value. For
example:

```toml
[snapshot]
max-new-file-size = "10MiB"
# the following is equivalent
max-new-file-size = 10485760
```

The value can be specified using a human readable string with typical suffixes;
`B`, `MiB`, `GB`, etc. By default, if no suffix is provided, or the value is a
raw integer literal, the value is interpreted as if it were specified in bytes.

Files that already exist in the working copy are not subject to this limit.

Setting this value to zero will disable the limit entirely.

### Automatic update of stale working copies

When a working copy becomes stale (meaning the working copy's recorded commit is
no longer the current commit for that workspace), `jj` will normally prompt you
to update it with `jj workspace update-stale`. You can configure `jj` to
automatically update stale working copies by setting:

```toml
[snapshot]
auto-update-stale = true
```

This is particularly useful if you have [multiple workspaces](working-copy.md#workspaces)
and want to avoid manually updating each one.

For more information on stale working copies, see the [working copy
documentation](working-copy.md#stale-working-copy).

## Working copy settings

### EOL conversion setting

This setting serves the same purpose as the [`core.autocrlf`][git-autocrlf] Git
config.

Regardless of this setting, the line endings conversion is skipped on binary
files based on a [heuristic](#binary-file-detection). This is similar to Git.

```toml
[working-copy]
# No EOL conversion. Similar to `core.autocrlf = false`.
eol-conversion = "none"
# Apply CRLF-to-LF EOL conversion when we check files in to the backend store
# from the local file system, but do not apply LF-to-CRLF EOL conversion when we
# check out the code from the backend store to the local file system. Similar to
# `core.autocrlf = input`.
eol-conversion = "input"
# Apply EOL conversion on both file check-in to and file check-out from the
# backend store, so you have CRLF line endings in your working directory while
# the repository has LF line endings. Similar to `core.autocrlf = true`.
eol-conversion = "input-output"
```

[git-autocrlf]: https://git-scm.com/book/en/v2/Customizing-Git-Git-Configuration#_core_autocrlf

#### Binary file detection

To detect if a file is binary, Jujutsu currently checks if there is a NULL byte
in the file, which is different from the algorithm of
[`gitoxide`][gitoxide-is-binary] or [`Git`][git-is-binary]. Jujutsu doesn't plan
to align the binary detection logic with Git.

[gitoxide-is-binary]: https://github.com/GitoxideLabs/gitoxide/blob/073487b38ed40bcd7eb45dc110ae1ce84f9275a9/gix-filter/src/eol/utils.rs#L98-L100
[git-is-binary]: https://github.com/git/git/blob/f1ca98f609f9a730b9accf24e5558a10a0b41b6c/convert.c#L94-L103

### Respect or ignore executable bit permission changes

Whether to respect or ignore changes to the executable bit for files on Unix.
This option is unused on Windows.

```toml
[working-copy]
exec-bit-change = "respect" | "ignore" | "auto" (default)
```

On Unix systems, files have a permission bit for whether they are executable as
scripts or binary code. Jujutsu stores this state in the repository and will
update it for files as you operate on a repository. If you set your working
copy to a commit where a file is recorded as executable or not, `jj` will
adjust the permission of that file. If you change a file's executable bit
through the filesystem, `jj` will record that change when taking a snapshot.

Setting this to `"ignore"` will make Jujutsu ignore the executable bit on the
filesystem when updating the state for the repository. In addition, `jj` will
not attempt to modify a file's executable bit and will add new files as
"not executable." This is already the behavior on Windows, and having the
option to enable this behavior is useful for Unix users dual-booting Windows,
Windows users accessing files from WSL, or anyone experimenting with other
filesystem configurations.

On Unix if `"auto"` is set (the default), `jj` will try to detect whether the
filesystem supports changing executable bits to choose between `"respect"` or
`"ignore"`. On errors, we assume `"respect"`, except if permission was denied
which will assume `"ignore"`.

On Windows, files have no executable bit so this option is unused.

You can always use `jj file chmod` to update the recorded executable bit for a
file manually. If this option is `"respect"`, `jj` will also attempt to
propagate that change to the filesystem.

Note that if you modify a file's executable bit before changing this setting
from `"ignore"` or  `"auto"` to `"respect"`, `jj` may not update the stored
executable bit until you modify the file's contents or update its modification
time, e.g. with `touch`.

## Ways to specify `jj` config: details

### User config files

An easy way to find the user config file/directory is:

```bash
jj config path --user
```

On all platforms, the user's global `jj` configurations are by default loaded in
the following precedence order (with later configs overriding earlier ones):

- `$HOME/.jjconfig.toml`
- `<PLATFORM_SPECIFIC>/jj/config.toml` (preferred)
- `<PLATFORM_SPECIFIC>/jj/conf.d/*.toml`

where `$HOME` represents the user's home directory (`%USERPROFILE%` on Windows),
and `<PLATFORM_SPECIFIC>` represents the platform-specific configuration
directory shown in the table below. The platform-specific location is
recommended for better integration with platform services.

The files in the `conf.d` directory are loaded in lexicographic order. This
allows configs to be split across multiple files and combines well with
[Conditional Variables](#conditional-variables). Modifying user configuration
with `jj config {edit,set,unset} --user` targets the primary user config file
(or the first loaded file in `conf.d/`). Individual files in `conf.d/` can be
targeted with `--file <PATH>`.

| Platform        | Location of `<PLATFORM_SPECIFIC>` dir | Example config file location                              |
| :-------------- | :------------------------------------ | :-------------------------------------------------------- |
| Linux and macOS | `$XDG_CONFIG_HOME` or `$HOME/.config` | `/home/alice/.config/jj/config.toml`                      |
| Windows         | `{FOLDERID_RoamingAppData}`           | `C:\Users\Alice\AppData\Roaming\jj\config.toml`           |

The location of the `jj` user config files/directories can also be overridden
with the `JJ_CONFIG` environment variable. If the environment variable is set
(even to an empty string), it will be used instead of any configuration files in
the default locations. It can be a path to a TOML file or a directory of TOML
files, which will be loaded in lexicographic order and merged. Multiple paths
can be specified by separating them with a platform-specific path separator (`:`
on Unix-like systems, `;` on Windows). Note that this variable only affects
user-level and system-level config files; repo and workspace configs are
unaffected and will continue to be loaded from their respective locations.
(There is no environment variable to disable repo or workspace configs.)

For example, the following could be used to run `jj` without loading any user or
system configs:

```bash
# Ignores any settings specified in `{PLATFORM,/etc}/jj/{config.toml,conf.d/*.toml}`,
# but still loads repo and workspace configs.
JJ_CONFIG= jj log
```

There are also the `--config-file <PATH>` and `--config <NAME=VALUE>`
[global options](#specifying-config-on-the-command-line) which work with any
`jj` command.

### System config files

On Unix-like platforms, system-wide `jj` configurations are by default loaded in
the following precedence order (with later configs overriding earlier ones).

- `/etc/jj/config.toml`
- `/etc/jj/conf.d/*.toml`

These configs can be overridden by [the user config files], and will be disabled
in favor of the `JJ_CONFIG` environment variable if it is set.

Existing system config files loaded by `jj` can be edited using
`jj config {edit,set,unset} --file <PATH>`.

### JSON Schema Support

Many popular editors support TOML file syntax highlighting and validation. To
enable schema validation in your editor, add this line at the top of your TOML
config files:

```toml
#:schema https://docs.jj-vcs.dev/latest/config-schema.json
```

This enables features like:

- Autocomplete for config keys
- Type checking of values
- Documentation on hover
- Validation of settings

Here are some popular editors with TOML schema validation support:

- VS Code
  - Install [Even Better TOML](https://marketplace.visualstudio.com/items?itemName=tamasfe.even-better-toml)

- Neovim/Vim
  - Use with [nvim-lspconfig](https://github.com/neovim/nvim-lspconfig) and [taplo](https://github.com/tamasfe/taplo)

- Helix
  - Install [taplo](https://github.com/tamasfe/taplo)

- JetBrains IDEs (IntelliJ, PyCharm, etc)
  - Install [TOML](https://plugins.jetbrains.com/plugin/8195-toml) plugin

- Emacs
  - Install [lsp-mode](https://github.com/emacs-lsp/lsp-mode) and [toml-mode](https://github.com/dryman/toml-mode.el)
  - Configure [taplo](https://github.com/tamasfe/taplo) as the LSP server

### Environment variable overrides

Besides `$JJ_CONFIG` (which controls [where user config files are loaded
from](#user-config-files)), these environment variables override individual
config keys. They take precedence over config files and are themselves
overridden by `--config` / `--config-file`.

| Variable          | Config key          |
| :---------------- | :------------------ |
| `$JJ_USER`        | `user.name`         |
| `$JJ_EMAIL`       | `user.email`        |
| `$JJ_EDITOR`      | `ui.editor`         |
| `$JJ_PAGER`       | `ui.pager`          |
| `$JJ_OP_HOSTNAME` | `operation.hostname` |
| `$JJ_OP_USERNAME` | `operation.username` |

A few other variables act as fallbacks when no matching config is set (for
example `$VISUAL` / `$EDITOR` for the editor, and `$NO_COLOR` for color), rather
than hard overrides of configured values. See [Editor](#editor) and
[Colorizing output](#colorizing-output).

### Specifying config on the command-line

You can use one or more `--config`/`--config-file` options on the command line
to specify additional configuration settings. This overrides settings defined in
config files or environment variables. For example,

```shell
# Must not have spaces around the `=`
jj --config ui.color=always --config ui.diff-editor=meld split
```

The config value should be specified as a TOML expression. If it is a string
value and isn't enclosed by any TOML constructs (such as array notation), quotes
can be omitted. Here is an example with more advanced TOML constructs:

```shell
# Single quotes and the '\' are interpreted by the shell (assuming a POSIX shell)
# Double quotes are passed to jj and are parsed as TOML syntax
jj log --config \
  'template-aliases."format_timestamp(timestamp)"="""timestamp.format("%Y-%m-%d %H:%M %:::z")"""'
```

To load an entire TOML document, use `--config-file`:

```shell
jj --config-file=extra-config.toml log
```

### Conditional variables

You can conditionally enable config variables by using `--when`.

#### Using `[[--scope]]` tables

Variables defined in `[[--scope]]` tables are expanded to the root table.
`--when` specifies the condition to enable the scope table.

If no conditions are specified, the table is always enabled. If multiple
conditions are specified, their intersection is used.

```toml
[user]
name = "YOUR NAME"
email = "YOUR_DEFAULT_EMAIL@example.com"

# override user.email if the repository is located under ~/oss
[[--scope]]
--when.repositories = ["~/oss"]
[--scope.user]
email = "YOUR_OSS_EMAIL@example.org"

# override ui.pager on specific machines
[[--scope]]
--when.hostnames = ["work-laptop", "work-desktop"]
[--scope.ui]
pager = "delta"

# disable pagination for `jj status`, use `delta` for `jj diff` and `jj show`
[[--scope]]
--when.commands = ["status"]
[--scope.ui]
paginate = "never"
[[--scope]]
--when.commands = ["diff", "show"]
[--scope.ui]
pager = "delta"
```

#### Using multiple files

`--when` can also be used on the top level of a TOML file, which is convenient
when splitting your config across multiple files.
The behavior of conditions are the same as when using `[[--scope]]` tables.

```toml
# In $XDG_CONFIG_HOME/jj/config.toml
[user]
name = "YOUR NAME"
email = "YOUR_DEFAULT_EMAIL@example.com"
```

```toml
# In $XDG_CONFIG_HOME/jj/conf.d/work.toml
--when.repositories = ["~/the/work/repo"]

[user]
email = "YOUR_WORK_EMAIL@workplace.com"

[revset-aliases]
work = "heads(::@ ~ description(''))::"

[aliases]
wip = ["log", "-r", "work"]
```

You can modify specific configuration files such as those in
`conf.d/` directly using the `--file` option (the file will be created if it
doesn't already exist):

```shell
jj config set --file ~/.config/jj/conf.d/work.toml user.email "YOUR_WORK_EMAIL@workplace.com"
jj config edit --file ~/.config/jj/conf.d/work.toml
```

#### Available condition keys

* `--when.repositories`: List of paths to match the repository path prefix.

  Paths should be absolute. Each path component (directory or file name, drive
  letter, etc.) is compared case-sensitively on all platforms. A path starting
  with `~` is expanded to the home directory. On Windows, directory separator may
  be either `\` or `/`. (Beware that `\` needs escape in double-quoted strings.)

  Use `jj root` to see the workspace root directory. Note that the repository path
  is in the main workspace if you're using multiple workspaces with `jj
  workspace`.

* `--when.workspaces`: List of paths to match the workspace path prefix.

  The same concerns about the path as for `--when.repositories` applies.

  Use `jj root` to see the workspace root directory.

* `--when.hostnames`: List of hostnames to match against the `operation.hostname`
  config setting.

  Hostnames are compared case-sensitively and must match exactly.

  ```toml
  --when.hostnames = ["work-laptop"]               # matches only "work-laptop"
  --when.hostnames = ["home-desktop", "laptop"]    # matches "home-desktop" *OR* "laptop"
  ```

* `--when.commands`: List of subcommands to match.

  Subcommands are space-separated and matched by prefix.

  ```toml
  --when.commands = ["file"]        # matches `jj file show`, `jj file list`, etc
  --when.commands = ["file show"]   # matches `jj file show` but *NOT* `jj file list`
  --when.commands = ["file", "log"] # matches `jj file` *OR* `jj log` (*OR* subcommand of either)
  ```

* `--when.platforms`: List of platforms to match.

  The values are defined by both
  [`std::env::consts::FAMILY`](https://doc.rust-lang.org/std/env/consts/constant.FAMILY.html)
  and
  [`std::env::consts::OS`](https://doc.rust-lang.org/std/env/consts/constant.OS.html).

  ```toml
  --when.platforms = ["windows"]            # matches only Windows
  --when.platforms = ["linux", "freebsd"]   # matches Linux *OR* FreeBSD, but *NOT* macOS
  --when.platforms = ["unix"]               # matches anything in the Unix family (Linux, FreeBSD, macOS, etc.)
  ```

* `--when.environments`: List of environment variable conditions in
  `"NAME=VALUE"` format (check if the variable has the value), or `NAME` to
  check if the variable is set.

  ```toml
  --when.environments = ["CI=true"]                     # matches when CI is set to "true"
  --when.environments = ["CI"]                          # matches when CI is set to anything
  --when.environments = ["CI=true", "CI=1"]             # matches when CI is "true" *OR* "1"
  --when.environments = ["CI=true", "GITHUB_ACTIONS=1"] # matches when *EITHER* condition holds
  ```

---

### Conflicts (`docs/conflicts.md`)

# First-class conflicts

## Introduction

Conflicts happen when Jujutsu can't figure out how to merge different changes
made to the same file. For instance, this can happen if two people are working
on the same file and make different changes to the same part of the file, and
then their commits are merged together with `jj new` (or one is rebased onto the
other with `jj rebase`).

Unlike most other VCSs, Jujutsu can record conflicted states in commits. For
example, if you rebase a commit and it results in a conflict, the conflict will
be recorded in the rebased commit and the rebase operation will succeed. You can
then resolve the conflict whenever you want. Conflicted states can be further
rebased, merged, or backed out. Note that what's stored in the commit is a
logical representation of the conflict, not conflict *markers*; rebasing a
conflict doesn't result in a nested conflict markers (see
[technical doc](technical/conflicts.md) for how this works).

## Advantages

The deeper understanding of conflicts has many advantages:

* Removes the need for things like
  `git rebase/merge/cherry-pick/etc --continue`. Instead, you get a single
  workflow for resolving conflicts: check out the conflicted commit, resolve
  conflicts, and amend.
* Enables the "auto-rebase" feature, where descendants of rewritten commits
  automatically get rewritten. This feature mostly replaces Mercurial's
  [Changeset Evolution](https://www.mercurial-scm.org/wiki/ChangesetEvolution).
* Lets us define the change in a merge commit as being compared to the merged
  parents. That way, we can rebase merge commits correctly (unlike both Git and
  Mercurial). That includes conflict resolutions done in the merge commit,
  addressing a common use case for
  [git rerere](https://git-scm.com/docs/git-rerere).
  Since the changes in a merge commit are displayed and rebased as expected,
  [evil merges](https://git-scm.com/docs/gitglossary/2.22.0#Documentation/gitglossary.txt-aiddefevilmergeaevilmerge)
  are arguably not as evil anymore.
* Allows you to postpone conflict resolution until you're ready for it. You
  can easily keep all your work-in-progress commits rebased onto upstream's head
  if you like.
* [Criss-cross merges](https://stackoverflow.com/questions/26370185/how-do-criss-cross-merges-arise-in-git)
  and [octopus merges](https://git-scm.com/docs/git-merge#Documentation/git-merge.txt-octopus)
  become trivial (implementation-wise); some cases that Git can't currently
  handle, or that would result in nested conflict markers, can be automatically
  resolved.
* Enables collaborative conflict resolution. (This assumes that you can share
  the conflicts with others, which you probably shouldn't do if some people
  interact with your project using Git.)

For information about how conflicts are handled in the working copy, see
[here](working-copy.md#conflicts).

## Conflict markers

Conflicts are "materialized" using *conflict markers* in various contexts. For
example, when you run `jj new` or `jj edit` on a commit with a conflict, it will
be materialized in the working copy. Conflicts are also materialized when they
are part of diff output (e.g. `jj show` on a commit that introduces or resolves
a conflict).

As an example, imagine that you have a file which contains the following text,
all in lowercase:

```text
apple
grape
orange
```

One person replaces the word "grape" with "grapefruit" in commit A, while
another person changes every line to uppercase in commit B. If you merge the
changes together with `jj new A B`, the resulting commit will have a conflict
since Jujutsu can't figure out how to combine these changes. Therefore, Jujutsu
will materialize the conflict in the working copy using conflict markers, which
would look like this:

```text
<<<<<<< conflict 1 of 1
%%%%%%% diff from: vpxusssl 38d49363 "merge base"
\\\\\\\        to: rtsqusxu 2768b0b9 "commit A"
 apple
-grape
+grapefruit
 orange
+++++++ ysrnknol 7a20f389 "commit B"
APPLE
GRAPE
ORANGE
>>>>>>> conflict 1 of 1 ends
```

The markers `<<<<<<<` and `>>>>>>>` indicate the start and end of a conflict
respectively. The marker `+++++++` indicates the start of a snapshot, while the
marker `%%%%%%%` indicates the start of a diff to apply to the snapshot. The
`\\\\\\\` marker just allows the label to be split across two lines to make it
more readable.

Therefore, to resolve this conflict, you would apply the diff (changing "grape"
to "grapefruit") to the snapshot (the side with every line in uppercase),
editing the file to look like this:

```text
APPLE
GRAPEFRUIT
ORANGE
```

In practice, conflicts are usually 2-sided, meaning that there's only 2
conflicting changes being merged together at a time, but Jujutsu supports
conflicts with arbitrarily many sides, which can happen when merging 3 or more
commits at once. In that case, you would see a single snapshot section and
multiple diff sections.

Compared to just showing the content of each side of the conflict, the main
benefit of Jujutsu's style of conflict markers is that you don't need to spend
time manually comparing the sides to spot the differences between them. This is
especially beneficial for many-sided conflicts, since resolving them just
requires applying each diff to the snapshot one-by-one.

## Alternative conflict marker styles

If you prefer to just see the contents of each side of the conflict without the
diff, Jujutsu also supports a "snapshot" style, which can be enabled by setting
the `ui.conflict-marker-style` config option to "snapshot":

```text
<<<<<<< conflict 1 of 1
+++++++ rtsqusxu 2768b0b9 "commit A"
apple
grapefruit
orange
------- vpxusssl 38d49363 "merge base"
apple
grape
orange
+++++++ ysrnknol 7a20f389 "commit B"
APPLE
GRAPE
ORANGE
>>>>>>> conflict 1 of 1 ends
```

Some tools expect Git-style conflict markers, so Jujutsu also supports [Git's
"diff3" style](https://git-scm.com/docs/git-merge#_how_conflicts_are_presented)
conflict markers by setting the `ui.conflict-marker-style` config option to
"git":

```text
<<<<<<< rtsqusxu 2768b0b9 "commit A"
apple
grapefruit
orange
||||||| vpxusssl 38d49363 "merge base"
apple
grape
orange
=======
APPLE
GRAPE
ORANGE
>>>>>>> ysrnknol 7a20f389 "commit B"
```

This conflict marker style only supports 2-sided conflicts though, so it falls
back to the similar "snapshot" conflict markers if there are more than 2 sides
to the conflict.

## Long conflict markers

Some files may contain lines which could be confused for conflict markers. For
instance, a line could start with `=======`, which looks like a Git-style
conflict marker. To ensure that it's always unambiguous which lines are conflict
markers and which are just part of the file contents, `jj` sometimes uses
conflict markers which are longer than normal:

```text
<<<<<<<<<<<<<<< conflict 1 of 1
%%%%%%%%%%%%%%% diff from: wqvuxsty cb9217d5 "merge base"
\\\\\\\\\\\\\\\        to: kwntsput 0e15b770 "commit A"
-Heading
+HEADING
 =======
+++++++++++++++ mpnwrytz 52020ed6 "commit B"
New Heading
===========
>>>>>>>>>>>>>>> conflict 1 of 1 ends
```

## Conflicts with missing terminating newline

When materializing conflicts, `jj` outputs them in a line-based format, with
each conflict marker line terminated by a newline character (`\n`). This format
works well for text files that consist of a series of lines, with each line
terminated by a newline character as well.

While most text files follow this convention, some do not. Since conflict
markers must appear on their own line, when `jj` encounters a missing
terminating newline character in a conflict, it still needs to ensure that
there is a newline before the next conflict marker. To do this, it adds an
extra newline to each term of the conflict. Then, to compensate for this extra
newline, it also omits the terminating newline from the "end of conflict"
marker (`>>>>>>>`).

For instance, if a file originally contained `grape` with no terminating newline
character, and one person changed `grape` to `grapefruit`, while another person
added the missing newline character to make `grape\n`, the resulting conflict
would look like this (`␊` demonstrates the `\n` character explicitly; note that
the last line doesn't have the terminating `\n` character):

```text
<<<<<<< conflict 1 of 1␊
+++++++ tlwwkqxk d121763d "commit A" (no terminating newline)␊
grapefruit␊
%%%%%%% diff from: qwpqssno fe561d93 "merge base" (no terminating newline)␊
\\\\\\\        to: poxkmrxy c735fe02 "commit B"␊
 grape␊
+␊
>>>>>>> conflict 1 of 1 ends
```

Therefore, a resolution of this conflict could be `grapefruit\n`, with the
terminating newline character added.

---

### Contributing (`docs/contributing.md`)

# How to Contribute

## Policies

We'd love to accept your patches and contributions to this project. There are
just a few small guidelines you need to follow.

### Contributor License Agreement

Contributions to this project must be accompanied by a Contributor License
Agreement. You (or your employer) retain the copyright to your contribution;
this simply gives us permission to use and redistribute your contributions as
part of the project. Head over to <https://cla.developers.google.com/> to see
whether you have already signed the CLA or to sign it.

You generally only need to submit a CLA once, so if you've already submitted one
(even if it was for a different project), you probably don't need to do it
again. In other words, the Google CLA is not scoped to a specific project. If
you sign the Google CLA, it applies to other Google open-source projects as
well (including future projects).

{% include-markdown '../AI_POLICY.md' heading-offset=2 %}

### Commit guidelines

Unlike many GitHub projects (but like many VCS projects), we care more about the
contents of commits than about the contents of PRs. We review each commit
separately, and we don't squash-merge the PR (so please manually squash any
fixup commits before sending for review).

Each commit should ideally do one thing. For example, if you need to refactor a
function in order to add a new feature cleanly, put the refactoring in one
commit and the new feature in a different commit. If the refactoring itself
consists of many parts, try to separate out those into separate commits. You can
use `jj split` to do it if you didn't realize ahead of time how it should be
split up. Include tests and documentation in the same commit as the code they
test and document.

The commit message should describe the changes in the commit;
the PR description can even be empty, but feel free to include a personal
message. We don't use [Conventional Commits] and instead start the commit
message with `<topic>: ` rather than like `chore: `, `feat: ` and `fix: `.
This means if you modified a command in the CLI, use its name as the topic, e.g.
`next/prev: <your-modification>` or `conflicts: <your-modification>`. We don't
currently have a specific guidelines on what to write in the topic field, but
the reviewers will help you provide a topic if you have difficulties choosing
it. [How to Write a Git Commit Message] is a good
guide if you're new to writing good commit messages. We are not particularly
strict about the style, but please do explain the reason for the change unless
it's obvious. If the PR closes an open issue or feature request for the
project, include the `Closes #<issue-number>` or `Fixes #<issue-number>` in
the commit description.

If you have been invited to the `jj-vcs/` organization feel free to create
branches directly in the repo, ideally prefixed by your name or username. This
allows other people to directly check out your work and possibly integrate them
in their custom builds of jj.

[Conventional Commits]: https://www.conventionalcommits.org/en/v1.0.0/
[How to Write a Git Commit Message]: https://cbea.ms/git-commit/

### Code reviews

All submissions, including submissions by project members, require review. We
use GitHub pull requests for this purpose. Consult
[GitHub Help](https://help.github.com/articles/about-pull-requests/) for more
information on using pull requests.

The project unfortunately has a shortage of reviewers (a common problem in
open-source projects). To maximize the chances of getting a timely review, do
what you can to provide reviewers (and other future readers) with the necessary
context. For example, if you're adding a new feature, explain why that feature
is useful, how it works from the user's point of view, how it's designed, and
what the limitations of your design are. If you have considered other designs,
it may be useful to mention them. Another way of speeding up reviews is to
review others' PRs so the maintainers don't need to spend as much time on them.

When you address comments on a PR, don't make the changes in a commit on top (as
is typical on GitHub). Instead, please make the changes in the appropriate
commit. You can do that by creating a new commit on top of the initial commit
 (`jj new <commit>`) and then squash in the changes when you're done (`jj squash`).
`jj git push`
will automatically force-push the bookmark.

Mark conversation threads as resolved when you have addressed them. If you
realize that a comment requires non-trivial changes, write a reply instead and
ask your reviewer to take another look after updating the code.

When your first PR has been approved, we typically invite you to the
`jj-vcs/contributors` team to give you contributor access,
so you can address any remaining minor comments and then merge the PR yourself
when you're ready.

If your employer pays anyone (not necessarily you) to contribute to Jujutsu,
please make sure your GitHub username is [recorded](paid_contributors.md).
To avoid conflicts of interest, please don't merge a PR that has only been
approved by someone from the same organization. Similarly, as a reviewer, there
is no need to approve your coworkers' PRs, since the author should await an
approval from someone else anyway. It is of course still appreciated if you
review and comment on their PRs. Also, if the PR seems completely unrelated to
your company's interests, do feel free to approve it.

### Changelog entries

When creating PRs, please update [`CHANGELOG.md`][changelog] with the new
changes under the "Unreleased" section. Make sure to highlight any breaking
changes or deprecations under the appropriate subsections. If you fixed a bug
that has a corresponding issue, link the issue with Markdown link format, so
that the changelog page on the generated docs can point to the actual issue.
Generally a maintainer will update "Release highlights" during a release as
needed, but feel free to add your change there if it makes sense. The order in
each subsection does not really matter, so you can add your entry to the
beginning or the end.

Not every change requires a changelog entry. Generally, changelog entries are
useful for two reasons:

1. Teach existing users about changes that affect their behavior. For example
   new features or fixed bugs would be good to know, as well as deprecations.
2. Attract new users. Maybe there was some limitation (lack of support for
   something they needed), or maybe a shiny new feature really spoke to them.

By this reasoning, it's not necessary to mention changes that people will simply
notice when they run into them (or not notice at all), such as a reworded error
message, backend refactors, or documentation updates.

[changelog]: https://github.com/jj-vcs/jj/blob/main/CHANGELOG.md

### Community Guidelines

This project follows [Google's Open Source Community
Guidelines](https://opensource.google/conduct/).

### Deprecations and Removals

If your PR removes or renames an option, a command, or config option don't
remove it immediately. Instead implement a warning that the command, command
option, or config option is deprecated and will be removed in
jj `v<current> + 6`. Also add a `// TODO: remove in jj<version+6>+` comment so
other contributors can remove it later. As we currently release monthly this
translates to a six month deprecation period. This is something the project
does to not break workflows and gives users the the option to
provide feedback on features we plan to remove or supersede with other
approaches.

Changes to the repo format (everything which is in `.jj/`) are more disruptive.
We aim to be backwards-compatible for at least a year (12 releases). Also
consider if it's worth being forwards-compatible (i.e making sure that the last
N versions of `jj` can read the new format). Examples of such changes are moving
repo-level configuration out of the repository or deriving the evolog from the
op-log.

Some things are required to be supported for even longer but that is in the end
a maintainer decision. Some examples of this were the `jj checkout/merge`
commands (removed after 24 months/releases) or the `--destination` flag for
which there is no final removal date yet.

## Contributing large patches

We welcome contributions from anyone.

If you wish to contribute a new large feature, you are encouraged to discuss
it in Discord or a GitHub Discussion first before diving in. Before spending
too much time on a particular design, you should see if the community and the
maintainers have any feedback on your idea and general approach. It also is a
better experience for you as an author if you contributed smaller patches
first, so you understand how the project and the community around it works.

If you open a pull request without discussion first, you may find that the
community already has discussed this and found a better approach, or that it
already was deemed not to be a fit for the project.

Since many ideas have come up before, but they haven't been implemented because
they might be deceptively nuanced, or there may be strong opinions around them,
bringing it up to the community first helps discover past discussions, so your
design can be informed by them.

Once you have vetted your idea with the community, please write a
[Design Doc](design_docs.md) which requires an architecture review from
multiple stakeholders, see the [process here](design_docs.md#process).

## Contributing to the documentation

We appreciate [bug
reports](https://github.com/jj-vcs/jj/issues/new?template=bug_report.md)
about any problems, however small, lurking in [our documentation
website](https://docs.jj-vcs.dev/prerelease) or in the `jj help
<command>` docs. If a part of the bug report template does not apply, you can
just delete it.

Before reporting a problem with the documentation website, we'd appreciate it if
you could check that the problem still exists in the "prerelease" version of the
documentation (as opposed to the docs for one of the released versions of `jj`).
You can use the version switcher in the top-left of the website to do so.

If you are willing to make a PR fixing a documentation problem, even better!

The documentation website sources are Markdown files located in the [`docs/`
directory](https://github.com/jj-vcs/jj/tree/main/docs). You do not need to
know Rust to work with them. See below for [instructions on how to preview the
HTML docs](#previewing-the-html-documentation) as you edit the Markdown files.
Doing so is optional, but recommended.

The `jj help` docs are sourced from the "docstring" comments inside the Rust
sources, currently from the [`cli/src/commands`
directory](https://github.com/jj-vcs/jj/tree/main/cli/src/commands). Working
on them requires setting up a Rust development environment, as described
below, and may occasionally require adjusting a test.

## Learning Rust

In addition to the [Rust Book](https://doc.rust-lang.org/book/) and the other
excellent resources at <https://www.rust-lang.org/learn>, we recommend the
["Comprehensive Rust" mini-course](https://google.github.io/comprehensive-rust/)
for an overview, especially if you are familiar with C++.

## Style guide

See [here](style_guide.md).

## Setting up a development environment

To develop `jj`, the mandatory steps are simply
to [install Rust](https://www.rust-lang.org/tools/install) (the default
installer options are fine), clone the repository, and use `cargo build`
, `cargo fmt`,
`cargo clippy --workspace --all-targets`, and
`cargo test --workspace`. If you are preparing a PR, there are some additional
recommended steps.

### Summary

One-time setup:

    rustup toolchain add nightly  # wanted for 'rustfmt'
    rustup toolchain add 1.97.1   # also specified in Cargo.toml
    cargo install --locked bacon
    cargo install --locked cargo-insta
    cargo install --locked cargo-nextest

During development (adapt according to your preference):

    bacon clippy-all
    cargo +nightly fmt # Occasionally
    cargo nextest run --workspace # Occasionally
    cargo insta test --workspace --test-runner nextest # Occasionally

!!! warning

    Build artifacts from debug builds and especially from repeated
    invocations of `cargo test` can quickly take up 10s of GB of disk space.
    Cargo will happily use up your entire hard drive. If this happens, run
    `cargo clean`.

### Explanation

These are listed roughly in order of decreasing importance.

1. Nearly any change to `jj`'s CLI will require writing or updating snapshot
   tests that use the [`insta`](https://insta.rs/) crate. To make this
   convenient, install the `cargo-insta` binary.
   Use `cargo insta test --workspace` to run tests,
   and `cargo insta review --workspace` to update the snapshot tests.
   The `--workspace` flag is needed to run the tests on all crates; by default,
   only the crate in the current directory is tested.

2. GitHub CI checks require that the code is formatted with the *nightly*
   version of `rustfmt`. To do this on your computer, install the nightly
   toolchain and use `cargo +nightly fmt`.

3. Your code will be rejected if it cannot be compiled with the minimal
   supported version of Rust ("MSRV"). Currently, `jj` follows a rather
   casual MSRV policy: "The current `rustc` stable version, minus one."
   As of this writing, that version is **1.97.1**.

4. Your code needs to pass `cargo clippy`. You can also
   use `cargo +nightly clippy` if you wish to see more warnings.

5. You may also want to install and use [`bacon`](https://dystroy.org/bacon/),
   to automatically build, check, and / or run tests.

6. To run tests more quickly, use `cargo nextest run --workspace`. To
   use `nextest` with `insta`, use `cargo insta test --workspace
   --test-runner nextest`.

   On Linux, you may be able to speed up `nextest` even further by using
   the `mold` linker, as explained below.

### Configuring `jj fix` to run `rustfmt`

Run this in the jj repo:

```shell
jj config set --repo fix.tools.rustfmt '{ command = ["rustfmt", "+nightly"], patterns = ["glob:**/*.rs"] }'
```

> Note: users of Nix and `direnv` should drop the `"+nightly"` argument above
> since the devShell is already configured to pull in a nightly version of
> rustfmt:
>
> ```shell
> jj config set --repo fix.tools.rustfmt '{ command = ["rustfmt"], patterns = ["glob:**/*.rs"] }'
> ```

### Using `mold` for faster tests on Linux

On a machine with a multi-core CPU, one way to speed up
`cargo nextest` on Linux is to use the multi-threaded [`mold`
linker](https://github.com/rui314/mold). This linker may help
if, currently, your CPU is underused while Rust is linking test
binaries. Before proceeding with `mold`, you can check whether this is
an issue worth solving using a system monitoring tool such as `htop`.

`mold` is packaged for many distributions. On Debian, for example,
`sudo apt install mold` should just work.

A simple way to use `mold` is via the `-run` option, e.g.:

```shell
mold -run cargo insta test --workspace --test-runner nextest
```

There will be no indication that a different linker is used, except for
higher CPU usage while linking and, hopefully, faster completion. You
can verify that `mold` was indeed used by running
`readelf -p .comment target/debug/jj`.

There are also ways of having Rust use `mold` by default, see the ["How
to use" instructions](https://github.com/rui314/mold#how-to-use).

On recent versions of MacOS, the default linker Rust uses is already
multi-threaded. It should use all the CPU cores without any configuration.

### Set up a RAM disk for faster tests on macOS

`jj` tests can be sped up significantly on macOS by using a RAM disk instead of
the usual `/tmp` directory (on one Mac, the tests sped up ~3 times from 180
seconds to 55 seconds). You can set this up as follows:

```sh
sudo mkdir -p /Volumes/RAMDisk
sudo chmod a+wx /Volumes/RAMDisk
sudo mount_tmpfs /Volumes/RAMDisk  # Add `-e` to make it case-sensitive
                      # Add `-o nobrowse` to hide the tmpfs from Finder
```

You will need to re-run this after a reboot. Consider making it a script you can
run with `sudo`. (The first two commands don't need sudo if you change the dir
to somewhere in your HOME, the last one always does.)

Then, you can rust tests as, for example,

```sh
TMPDIR=/Volumes/RAMDisk cargo insta test --workspace --test-runner nextest
```

You can double-check whether or not the RAM disk is mounted with

```sh
mount | grep tmpfs
```

<details> <summary> Some details and speculation </summary>



Hard drive speed is not the issue here. Experimentally, when <code>/tmp</code>
is physical, Ilya experienced the tests doing writes at 60MB/s (as reported by
the Activity Monitor) on an SSD that should be many times as fast.

Instead, it seems to have something to do with file locking and
<code>fdatasync</code>. A likely curlprit is the presence of <a
href="https://gregoryszorc.com/blog/2018/10/29/global-kernel-locks-in-apfs/">
global kernel locks in APFS</a>. There are some benchmarks in
<a href="https://github.com/jj-vcs/jj/pull/7493#issuecomment-3289020874">this
PR</a>.

It might be possible to add an entry to `/etc/fstab` to mount tmpfs
automatically. If you succeed with this, let us (and Ilya in particular) know!

</details>

### Editor setup

#### Visual Studio Code

We recommend at least these settings:

```js
{
    "files.insertFinalNewline": true,
    "files.trimTrailingWhitespace": true,
    "[rust]": {
        "files.trimTrailingWhitespace": false
    }
}
```

#### Zed

```js
// .zed/settings.json
{
  "ensure_final_newline_on_save": true,
  "remove_trailing_whitespace_on_save": true,

  "languages": {
    // We don't use a formatter for Markdown files, so format_on_save would just
    // mess with others' docs
    "Markdown": { "format_on_save": "off" },
    "Rust": {
      "format_on_save": "on",
      // Avoid removing trailing spaces within multi-line string literals
      "remove_trailing_whitespace_on_save": false
    }
  },

  "lsp": {
    "rust-analyzer": {
      "initialization_options": {
        // If you are working on docs and don't need `cargo check`, uncomment
        // this option:
        //
        //   "checkOnSave": false,

        // Use nightly `rustfmt`, equivalent to `cargo +nightly fmt`
        "rustfmt": { "extraArgs": ["+nightly"] }
      }
    }
  }
}
```

#### Helix

```toml
# .helix/languages.toml
[language-server.rust-analyzer.config.rustfmt]
extraArgs = ["+nightly"]
```

### Alternative development setup with Nix

If you have [Nix](https://nixos.org/) installed, you can use the provided
`flake.nix` to get a development environment with all necessary tools
pre-configured:

```shell
nix develop
```

Once inside the shell, you can run `cargo build`, `cargo test` etc. as usual.
Other tools like `uv`, `cargo-insta`, `cargo-nextest` and more are installed,
and so you should be able to follow any other recommendations in this guide as
needed.

!!! tip "Editor integration"

    Launch your editor from within the `nix develop` shell so that
    rust-analyzer and other LSP tools can find the correct toolchain. For
    example:

    ```shell
    nix develop
    code .  # or your editor of choice
    ```

!!! note "direnv users"

    If you use [direnv](https://direnv.net/) with
    [nix-direnv](https://github.com/nix-community/nix-direnv), you can `echo
    "source_env .envrc.recommended" >> .envrc` to automatically activate the
    development environment when entering the directory.

### Alternative development setup with `mise`

An experimental development setup is available using
[`mise`](https://mise.jdx.dev/). If you try it, file bugs,
PRs, or tell us on Discord/IRC/discussions if you experience problems
or if this config is too inflexible. If we can make it work for most
platforms and most people's needs, including people previously unfamiliar
with `mise`, we may make `mise` the recommended way to set up a development
environment.

This tool manages the necessary dependencies for you, eliminating the need for
a separate setup process. `mise` automatically installs the required tools when
they are needed.

Here are some of the commands you may find yourself using frequently during
development:

* `mise test`: Runs all tests.
* `mise test <string>`: Runs tests that contain a specific string. For
  example, `mise test squash` would run tests with "squash" in their name.
* `mise build`: Compiles `jj`.
* `mise build:release`: Compiles `jj` in release mode.
* `mise build:docs`: Builds the documentation for `jj`.

For a complete list of all available tasks, you can run `mise tasks` or review
the configuration file at `.config/mise.toml`.

You can customize this configuration using
[a `mise.local.toml` file](https://mise.jdx.dev/configuration.html#mise-toml).

## Previewing the HTML documentation

The documentation for `jj` is automatically published online at
<https://docs.jj-vcs.dev/>.

When editing documentation, you should check your changes locally — especially
if you are adding a new page, or doing a major rewrite.

### Install `uv`

The only thing you need is [`uv`][uv] (version 0.5.1 or newer).

`uv` is a Python project manager written in Rust. It will fetch the right Python
version and the dependencies needed to build the docs. Install it like so:

[uv]: https://docs.astral.sh/uv/

=== "macOS/Linux"

    ```shell
    curl -LsSf https://astral.sh/uv/install.sh | sh
    ```

    !!! note
        If you don't have `~/.local/bin` in your `PATH`, the installer will
        modify your shell profile. To avoid it:

        ```shell
        curl -LsSf https://astral.sh/uv/install.sh | env INSTALLER_NO_MODIFY_PATH=1 sh
        ```

=== "Windows"

    ```shell
    powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"
    ```

=== "Homebrew"

    ```shell
    brew install uv
    ```

=== "Cargo"

    ```shell
    # This might take a while
    cargo install --git https://github.com/astral-sh/uv uv
    ```

=== "Other options"

    * Directly download the binaries from GitHub: [uv releases](https://github.com/astral-sh/uv/releases).
    * Even more options: [Installing uv](https://docs.astral.sh/uv/getting-started/installation/).

### Build the docs

To build the docs, run while inside the `jj` repository:

```shell
uv run mkdocs serve
```

Open <http://127.0.0.1:8000> in your browser to see the docs.

As you edit the `.md` files in `docs/`, the website should be rebuilt and
reloaded in your browser automatically.

!!! note "If the docs are not updating"
    Check the terminal from which you ran `uv run mkdocs serve` for any build
    errors or warnings. Warnings about `"GET /versions.json HTTP/1.1" code 404`
    are expected and harmless.

#### Offline distribution

To build the rendered docs for offline distribution or to view them from your file
system, run while inside the `jj` repository:

```shell
MKDOCS_OFFLINE=true uv run mkdocs build
```

## Building the entire website

!!! tip
    Building the entire website is not usually necessary. If you are editing
    documentation, the previous section is enough.

    These instructions are relevant if you are working on the versioning of the
    documentation that we currently do with `mike`.

The full `jj` website includes the documentation for several `jj` versions
(`prerelease`, latest release, and the older releases). The top-level
URL <https://docs.jj-vcs.dev> redirects to
<https://docs.jj-vcs.dev/latest>, which in turn redirects to
the docs for the last stable version.

The different versions of documentation are managed and deployed with
[`mike`](https://github.com/jimporter/mike), which can be run with
`uv run mike`.

On a POSIX system or WSL, one way to build the entire website is as follows (on
Windows, you'll need to understand and adapt the shell script):

1. Check out `jj` (`jj git clone`), cloned from your fork of `jj` (e.g.
  `github.com/jjfan/jj`). You can also use a pure Git repo if you prefer.

2. Make sure `github.com/jjfan/jj` includes the `gh-pages` bookmark of the jj repo
and run `git fetch origin gh-pages`.

3. Go to the GitHub repository settings, enable GitHub Pages, and configure them
to use the `gh-pages` bookmark (this is usually the default).

4. Install `uv` as explained in [Previewing the HTML
documentation](#previewing-the-html-documentation), and run the same `sh` script
that is used in GitHub CI (details below):

    ```shell
    .github/scripts/docs-build-deploy prerelease main --push
    ```

    This should build the version of the docs from the current commit,
    deploy it as a new commit to the `gh-pages` bookmark,
    and push the `gh-pages` bookmark to the origin.

5. Now, you should be able to see the full website, including your latest changes
to the `prerelease` version, at `https://jjfan.github.io/jj/prerelease/`.

6. (Optional) The previous steps actually only rebuild
`https://jjfan.github.io/jj/prerelease/` and its alias
`https://jjfan.github.io/jj/main/`. If you'd like to test out version switching
back and forth, you can also rebuild the docs for the latest release as follows.

    ```shell
    jj new v1.33.1  # Let's say `jj 1.33.1` is the latest release
    .github/scripts/docs-build-deploy v1.33.1 latest --push
    ```

7. (Optional) When you are done, you may want to reset the `gh-pages` bookmark to the
same spot as it is upstream. If you configured the "upstream" remote,
this can be done with:

    ```shell
    # This will LOSE any changes you made to `gh-pages`
    jj git fetch --remote upstream
    jj bookmark set gh-pages -r gh-pages@upstream
    jj git push --remote origin --bookmark gh-pages
    ```

    If you want to preserve some of the changes you made, you can do `jj bookmark
    set my-changes -r gh-pages` BEFORE running the above commands.

### Explanation of the `docs-build-deploy` script

The script sets up a few environment variables and invokes `uv run mike deploy`
with some default arguments and whatever arguments were passed to
`docs-build-deploy`. Run `uv run mike help deploy` to find out what the
arguments do.

If you need to do something more complicated, you can use `uv run mike
...` commands. You can also edit the `gh-pages` bookmark directly, but take care
to avoid files that will be overwritten by future invocations of `mike`. Then,
you can submit a PR based on the `gh-pages` bookmark of
<https://docs.jj-vcs.dev/> (instead of the usual `main` bookmark).

Previously, the version switcher would not work unless the value of the
`site_url` config in `mkdocs.yml` matched the actual URL the site is being
served from. This bug should now be fixed, but if you are not serving the site
from <https://docs.jj-vcs.dev/> and something fails weirdly, you might want
to adjust the `site_url` to something like `https://jjfan.github.io/jj`.

## Modifying protobuffers (this is not common)

 Occasionally, you may need to change the `.proto` files that define jj's data
 storage format. In this case, you will need to add a few steps to the above
 workflow.

 - Install the `protoc` compiler. This usually means either `apt-get install
   protobuf-compiler` or downloading [an official release]. The
   `prost` [library docs] have additional advice.
 - Run `cargo run -p gen-protos` regularly (or after every edit to a `.proto`
   file). This is the same as running `cargo run` from `lib/gen-protos`. The
   `gen-protos` binary will use the `prost-build` library to compile the
   `.proto` files into `.rs` files.
 - If you are adding a new `.proto` file, you will need to edit the list of
   these files in `lib/gen-protos/src/main.rs`.

[an official release]: https://github.com/protocolbuffers/protobuf/releases
[library docs]: https://docs.rs/prost-build/latest/prost_build/#sourcing-protoc

 The `.rs` files generated from `.proto` files are included in the repository,
 and there is a GitHub CI check that will complain if they do not match.

## Logging

You can print internal jj logs using `JJ_LOG`. It acts like the `RUST_LOG`
environment variable, frequent in Rust codebases, and accepts one or more
[directives]. You can also run `JJ_LOG=debug jj` to get `debug` level logs
enabled for all targets. You can also use the `--debug` global option, which
turns on `debug` log level for `jj-lib` and `jj-cli` only.

[directives]: https://docs.rs/tracing-subscriber/latest/tracing_subscriber/filter/struct.EnvFilter.html#directives

## Profiling

### Introduction

jj provides two main approaches to profiling:

1. **Tracing-based profiling** - Uses the Rust `tracing` crate to record spans
   and events. Good for understanding control flow and finding where time is spent
   in instrumented code.
2. **Sampling-based profiling** - Uses system profilers (perf, dtrace) to sample
   the call stack. Good for finding hot spots including in uninstrumented code and
   external libraries.

### Tracing-based profiling

jj uses the `tracing` crate with Chrome Trace Format output.

Set the `JJ_TRACE` environment variable to generate a trace file:

```shell
# Generate trace with auto-generated filename (jj-trace-{timestamp}.json)
JJ_TRACE= jj st

# Generate trace with specific filename
JJ_TRACE=/tmp/trace.json jj st
```

To include function argument values in the trace (useful for detailed
debugging):

```shell
JJ_TRACE_INCLUDE_ARGS=1 JJ_TRACE=/tmp/trace.json jj log -r 'ancestors(@, 10)'
```

The produced profiles can be imported into chrome://tracing (on Google Chrome)
or https://ui.perfetto.dev/ (all browsers).

Only functions annotated with `#[tracing::instrument]` or explicit `trace_span!`
macros appear in traces. If you need to profile a specific area that isn't
instrumented, you can add `#[instrument]` attributes to the relevant functions.

### Sampling-based profiling

Sampling profilers periodically capture the call stack to identify hot spots. This approach:

- Has lower overhead than tracing
- Captures all code, including uninstrumented functions and external libraries (like gitoxide)
- May miss short-lived function calls since it samples rather than traces every
  call

#### Using samply

One easy-to-use sampling profiler
is [samply]. For example:

```shell
cargo install samply
samply record jj diff
```

Then just open the link it prints.

[samply]: https://github.com/mstange/samply

#### Using cargo-flamegraph

[cargo-flamegraph] generates flamegraph visualizations using system profilers:

```shell
cargo install flamegraph
cargo flamegraph --bin jj -- diff
```

You may need to use `sudo` or install `perf` on your system, see flamegraph's
documentation for more.

[cargo-flamegraph]: https://github.com/flamegraph-rs/flamegraph

---

### Core Tenets (`docs/core_tenets.md`)

## Jujutsu's Core Tenets

Jujutsu's core tenets are:

 * Separation of logic and UI: It should be easy to create new UIs (CLIs, GUIs,
   TUIs, servers) without having to duplicate logic.
 * Easy-to-use APIs: It should be easy to create new commands. For example,
   each command should not have to worry about concurrency, working-copy state,
   and rebasing descendants of rewritten commits.
 * User-friendliness: Making the working copy a commit is simpler. This is
   how the project started.
 * The repo is the source of truth: Most commands should operate on the commit
   graph. The working copy is just one way of editing commits.
 * Pluggable storage: Must be easy to integrate with different commit storage,
   virtual file systems and more.
 * Git-interoperability: Git is everywhere. We need to have good
   interoperability to be adopted.
 * All operations must be able to scale to Google-scale repos (lots of commits,
   lots of files): Laziness is important, must avoid accessing data
   unnecessarily.
 * Strive to be modeless: If at all possible commands should work consistently
   and as the user expects in every situation.
 * Make it incredibly hard to lose work in your repo.
 * Concurrent modifications to the repo should be safe.
 * Allow concurrent edits on any commit, pending or finished.
 * Make a "stacked diffs" workflow as easy as possible.

---

### Design Doc Blueprint (`docs/design_doc_blueprint.md`)

# Title

> A cool name for your Project

Author: [Your-Name](mailto:your-name@reachable.com)

> If there are multiple authors, just list them all

## Summary

A short summary of your project/re-design/component and what problems it
addresses in about 3-10 sentences.

## State of the Feature as of `$VERSION` (optional)

The state of the feature you want to improve and where it currently falls
short. If there's nothing to compare to, leave it out.

## Prior work (optional)

Does this feature exist somewhere else and which tradeoffs it made.

> If there's no prior work, then use the related work section below.

## Goals and non-goals

Direct goals of the project and features deemed not worth pursuing.

## Overview

A detailed overview of the project and the improvements it brings.

### Detailed Design

The place to describe all new interfaces and interactions and how it plays into
the existing code and behavior. This is the place for all nitty-gritty details
which interact with the system.

## Alternatives considered (optional)

Other alternatives to your suggested approach, and why they fall short.

## Issues addressed (optional)

A list of issues which are addressed by this design.

## Related Work (optional)

If there's a feature in another VCS which shares some similarities to your
proposed work, it belongs here. An example would be Jujutsu sparse workspaces
and Perforce client workspaces.

## Future Possibilities

The section for things which could be added to it or deemed out of scope during
the discussion.

---

### Design Docs (`docs/design_docs.md`)

# Jujutsu Design Docs

Jujutsu uses [Design Docs] to drive technical decisions on large projects and it
is the place to discuss your proposed design or new component. It is a very
thorough process, in which the design doc must be approved before PRs for the
feature will be accepted. It shares some similarities with [Rust RFCs] but
mostly addresses _technical_ problems and gauges the technical and social
concerns of all stakeholders.

So if you want to start building a native backend or the server component for
Jujutsu, you'll need to go through this process.

[Design Docs]: https://blog.ceejbot.com/posts/design-docs/

## Process

1. Add a new markdown document to `docs/design`, named after your improvement
   or project.
1. Describe the current state of the world and the things you want to improve.
1. Wait for the Maintainers and Stakeholders to show up.
1. Iterate until everyone accepts the change in normal codereview fashion.

[Rust RFCs]: https://github.com/rust-lang/rfcs

## Blueprint (Template)

You can find the base template of a new Design Doc
[here](design_doc_blueprint.md).

---

### Filesets (`docs/filesets.md`)

# Filesets

Jujutsu supports a functional language for selecting a set of files.
Expressions in this language are called "filesets" (the idea comes from
[Mercurial](https://repo.mercurial-scm.org/hg/help/filesets)). The language
consists of file patterns, operators, and functions.

## Quoting file names

Many `jj` commands accept fileset expressions as positional arguments. File
names passed to these commands [must be quoted][string-literals] if they contain
whitespace or meta characters. However, as a special case, quotes can be omitted
if the expression has no operators nor function calls. For example:

* `jj diff 'Foo Bar'` (shell quotes are required, but inner quotes are optional)
* `jj diff '~"Foo Bar"'` (both shell and inner quotes are required)
* `jj diff '"Foo(1)"'` (both shell and inner quotes are required)

Glob characters aren't considered meta characters, but shell quotes are still
required:

* `jj diff '~glob:**/*.rs'`

[string-literals]: templates.md#stringliteral-type

## File patterns

The following patterns are supported. In all cases, we do not mention any shell
quoting that might be necessary, and the quotes around `"path"` are optional if
the path [has no special characters](#quoting-file-names).

By default, `"path"` is parsed as a `prefix-glob:` pattern, which matches
cwd-relative path prefix.

* `cwd:"path"`: Matches cwd-relative path prefix (file or files under directory
  recursively.)
* `file:"path"` or `cwd-file:"path"`: Matches cwd-relative file (or exact) path.
* `glob:"pattern"` or `cwd-glob:"pattern"`: Matches file paths with cwd-relative
  Unix-style shell [wildcard `pattern`][glob]. For example, `glob:"*.c"` will
  match all `.c` files in the current working directory non-recursively.
* `prefix-glob:"pattern"` or `cwd-prefix-glob:"pattern"`: Like `glob:`, but also
  matches path prefix (file or files under directory recursively.) For example,
  `prefix-glob:"*.d"` is equivalent to `glob:"*.d" | glob:"*.d/**"`.
* `root:"path"`: Matches workspace-relative path prefix (file or files under
  directory recursively.)
* `root-file:"path"`: Matches workspace-relative file (or exact) path.
* `root-glob:"pattern"`: Matches file paths with workspace-relative Unix-style
  shell [wildcard `pattern`][glob].
* `root-prefix-glob:"pattern"`: Like `root-glob:`, but also matches path prefix
  (file or files under directory recursively.)

Glob patterns support case-insensitive matching by appending `-i` to the pattern
name. For example, `glob-i:"*.TXT"` will match both `file.txt` and `FILE.TXT`.

[glob]: https://docs.rs/globset/latest/globset/#syntax

## Operators

The following operators are supported. `x` and `y` below can be any fileset
expressions.

Operators are listed in order of binding power from strongest to weakest, e.g.
`x | y & z` is interpreted as `x | (y & z)` since `&` has stronger binding power
than `|`. Infix operators of the same binding power are parsed from left to
right, e.g. `x ~ y & z` is interpreted as `(x ~ y) & z` rather than `x ~ (y &
z)`.

As seen above, parentheses can be used to control evaluation order, e.g. `(x &
y) | z` or `x & (y | z)`.

1. * `f(x)`: Function call.

2. * `p:x`: File pattern or pattern alias named `p`.

3. * `~x`: Matches everything but `x`.

4. * `x & y`: Matches both `x` and `y`.
   * `x ~ y`: Matches `x` but not `y`.

5. * `x | y`: Matches either `x` or `y` (or both).

## Functions

You can also specify patterns by using functions.

* `all()`: Matches everything.
* `none()`: Matches nothing.

## Aliases

New symbols, functions, and `<name>:<value>` patterns can be defined in the
config file, by using any combination of the predefined symbols / functions and
other aliases.

Alias functions can be overloaded by the number of parameters. However, builtin
function will be shadowed by name, and can't co-exist with aliases.

For example:

```toml
[fileset-aliases]
LOCK = '**/Cargo.lock | **/package-lock.json | **/uv.lock'
'not:x' = '~x'
```

### Alias descriptions

Alias descriptions can be surfaced in shell completions by defining the alias
as a table with `.doc` and `.definition` properties. For example:

```toml
[fileset-aliases]
LOCK = {
    definition = '**/Cargo.lock | **/package-lock.json | **/uv.lock',
    doc = 'Lockfiles'
}
```

You can also use the dotted key syntax:

```toml
[fileset-aliases]
LOCK.definition = '**/Cargo.lock | **/package-lock.json | **/uv.lock'
LOCK.doc = 'Lockfiles'
```

## Examples

Show diff excluding `Cargo.lock`.

```shell
jj diff '~Cargo.lock'
```

List files in `src` excluding Rust sources.

```shell
jj file list 'src ~ glob:"**/*.rs"'
```

Split a revision in two, putting `foo` into the second commit.

```shell
jj split '~foo'
```

---

### Gerrit (`docs/gerrit.md`)

# Using Jujutsu with Gerrit Code Review

JJ and Gerrit share the same mental model, which makes Gerrit feel like a
natural collaboration tool for JJ. JJ tracks a "change identity" across
rewrites, and Gerrit's `Change-Id` tracks the same logical change across patch
sets. JJ and Gerrit's `Change-Id`s aren't natively compatible yet, but they're
philosophically aligned. `jj gerrit upload` bridges the gap today by adding a
Gerrit-style `Change-Id` trailer while JJ keeps its own notion of change
identity on the client. In practice, that means small, clean commits that evolve
over time, exactly how Gerrit wants you to work.

This guide assumes a basic understanding of Git, Gerrit, and Jujutsu.

## Set up a Gerrit remote

Jujutsu communicates with Gerrit by pushing commits to a Git remote. If you're
starting from an existing Git repository with Gerrit remotes already configured,
you can use `jj git init` to start using JJ in that repo. Otherwise, set up your
Gerrit remote:

```shell
# Option 1: Start JJ in an existing Git repo with Gerrit remotes
$ jj git init

# Option 2: Add a Gerrit remote to a JJ repo
$ jj git remote add gerrit https://review.gerrithub.io/yourname/yourproject

# Option 3: Clone the repo via jj
$ jj git clone https://review.gerrithub.io/your/project
```

If you used option 2, you can configure default values in your repository config
by appending the following lines to your config file, like so (to do this for
a specific repo, run `jj config edit --repo`):

```toml
[gerrit]
default-remote = "gerrit"       # name of the Git remote to push to
default-remote-branch = "main"  # target branch in Gerrit
```

## Basic workflow

`jj gerrit upload` takes one or more revsets and uploads the stack of commits
and their ancestors to Gerrit. Each JJ change will map to a single Gerrit change
by generating a `Change-Id` based on the JJ change ID (or using the existing
`Change-Id` trailer if already present). This should be what you want most of
the time, but if you want to associate a JJ change with a specific change
already uploaded to Gerrit, you can copy the `Change-Id` trailer from Gerrit to
the bottom of the commit description in JJ.

> Note: Gerrit identifies and updates changes by the `Change-Id` trailer. When
> you re-upload a commit with the same `Change-Id`, Gerrit creates a new patch
> set.

### Upload a single change

```shell
# Uploads `@` if it has a description, otherwise uploads `@-`.
$ jj gerrit upload

# Or explicitly specify a revision to upload.
$ jj gerrit upload -r @-
```

## Selecting revisions (revsets)

`jj gerrit upload` accepts one or more `-r`/`--revisions` arguments. Each
argument may expand to multiple commits. Common patterns:

- `-r @-`: the commit previous to the one you're currently working on
- `-r A..B`: commits that are ancestors of B but not of A

See the [revsets](revsets.md) guide for more information.

### Preview without pushing

Use `--dry-run` to see which commits would be modified and pushed, and where,
without changing anything or contacting the remote.

```shell
$ jj gerrit upload -r '@-' --remote-branch main --dry-run
```

## Target branch and remote selection

There are a few ways of specifying the target remote for your projects:

- Run `jj config set --user gerrit.default-remote <remote name>` to set your
  default remote across all repos.
- Run `jj config set --repo gerrit.default-remote <remote name>` to set your
  default remote for this specific repo.
- Use `--remote <remote name>` to use a specific remote in one invocation of
  `jj gerrit upload`.
- Otherwise, a remote named `gerrit` will be used. If that doesn't exist, the
  command will fail, and you will need to use one of the above methods to
  specify the remote to upload to.

Additionally, you can specify the target remote branch:

- Run `jj config set --user gerrit.default-remote-branch <branch name>` to set
  your default branch across all repos.
- Run `jj config set --repo gerrit.default-remote-branch <branch name>` to set
  your default branch for this specific repo.
- Use `--remote-branch <branch name>` to use a specific branch in one invocation
  of `jj gerrit upload`.

## Updating changes after review

To address review feedback, update your revisions, then run `jj gerrit
upload` again with the same revsets. Gerrit will add new patch sets to the
existing changes instead of creating new ones.

Examples:

```shell
# Edit an earlier commit in the stack
$ jj edit xyz  # position on the stack to edit
# Apply needed edits, then upload the updates
$ jj gerrit upload -r xyz
```

## `Change-Id` management

If you do not provide an explicit `Change-Id` trailer in your commits,
`jj gerrit upload` will generate a transient one for you based on your JJ
change ID. This means that as long as the JJ change ID remains the same (and
you don't add an explicit `Change-Id` trailer), it will upload as a new patch
set on the existing change.

Keep this association in mind when splitting or squashing changes. For example,
when splitting a change, the portion that you want associated with the
original Gerrit change should remain in the original JJ change (the first half
of the split). Similarly, when squashing new changes, you typically want to
squash into the change that was previously uploaded to Gerrit.

If your JJ changes no longer align with the desired mapping to Gerrit changes,
you can manually copy a Gerrit `Change-Id` trailer into your JJ change
description to directly assign a JJ change to an exist Gerrit change.

As an alternative to `jj gerrit upload`'s automatic `Change-Id` mapping, you
can configure JJ to automatically add `Change-Id` trailer to all change
descriptions:

```toml
[templates]
commit_trailers = '''
if(
  !trailers.contains_key("Change-Id"),
  format_gerrit_change_id_trailer(self)
)
'''
```

In this case, the Gerrit change mapping is defined entirely by the `Change-Id`
trailer. When splitting or squashing changes, be sure to keep the `Change-Id`
trailer associated with the desired changes. Be sure not to duplicate the same
`Change-Id` across different changes. Gerrit will reject pushes that contain
duplicate `Change-Id`s, but if the uploads are done separately, you may
unintentionally overwrite an existing change.

Note that when `jj gerrit upload` automatically adds a `Change-Id` trailer
before pushing to Gerrit, this addition to the commit description will only be
reflected on the uploaded commit, not your local commit. This means the server
will have a different commit ID than you do. As a result, you may encounter
divergence when you fetch a merged change into your local repo. To address this,
you can abandon your local change or rebase it on top of trunk with
`jj rebase --skip-emptied ...`, which will resolve the divergence. Alternatively,
add the above config so that the `Change-Id` trailer is automatically added to
your local commits before `jj gerrit upload` does.

## Alternative `Link` trailer

Since version 3.3.1 Gerrit supports an alternative to the `Change-Id` trailer,
using a `Link` trailer in the format of `<review-url>/id/I<change-id>`. It is
only documented in the [commit-msg hook documentation]. Jujutsu's
`jj gerrit upload` will do the same if you set
`jj config set --repo gerrit.review-url <review-url>`.

[commit-msg hook documentation]: https://gerrit-documentation.storage.googleapis.com/Documentation/3.3.1/cmd-hook-commit-msg.html

---

### Git Command Table (`docs/git-command-table.md`)

# Git command table

Note that all `jj` commands can be run on any commit (not just the working-copy
commit), but that's left out of the table to keep it simple. For example,
`jj squash -r <revision>` will move the diff from that revision into its
parent.

{{ read_yaml("git-command-table.yml") }}

---

### Git Command Table.Yml (`docs/git-command-table.yml`)

- Use case: Create a new repo
  Git command: >
    `git init`
  Jujutsu command: >
    `jj git init [--no-colocate]`
  Notes:

- Use case: Clone an existing repo
  Git command: >
    `git clone <source> <destination> [--origin <remote name>]`
  Jujutsu command: >
    `jj git clone <source> <destination> [--remote <remote name>]`
  Notes: There is no support for cloning non-Git repos yet.

# TODO: Mention that you might need to do a `jj bookmark track branch` to see
# the bookmark in `jj log`.
- Use case: Update the local repo with all bookmarks/branches from a remote
  Git command: >
    `git fetch [<remote>]`
  Jujutsu command: >
    `jj git fetch [--remote <remote>]`
  Notes: There is no support for fetching into non-Git repos yet.

# TODO: This only affects tracked branches now.
- Use case: Update a remote repo with all bookmarks/branches from the local repo
  Git command: >
    `git push --all [<remote>]`
  Jujutsu command: >
    `jj git push --all [--remote <remote>]`
  Notes: There is no support for pushing from non-Git repos yet.

- Use case: Update a remote repo with a single bookmark from the local repo
  Git command: >
    `git push <remote> <bookmark name>`
  Jujutsu command: >
    `jj git push --bookmark <bookmark name> [--remote <remote>]`
  Notes: There is no support for pushing from non-Git repos yet.

- Use case: Add a remote target to the repo
  Git command: >
    `git remote add <remote> <url>`
  Jujutsu command: >
    `jj git remote add <remote> <url>`
  Notes:

- Use case: Show summary of current work and repo status
  Git command: >
    `git status`
  Jujutsu command: >
    `jj st`
  Notes:

- Use case: Show diff of the current change
  Git command: >
    `git diff HEAD`
  Jujutsu command: >
    `jj diff`
  Notes:

- Use case: Show diff of another change
  Git command: >
    `git diff <revision>^ <revision>`
  Jujutsu command: >
    `jj diff -r <revision>`
  Notes:

- Use case: Show diff from another change to the current change
  Git command: >
    `git diff <revision>`
  Jujutsu command: >
    `jj diff --from <revision>`
  Notes:

- Use case: Show diff from change A to change B
  Git command: >
    `git diff A B`
  Jujutsu command: >
    `jj diff --from A --to B`
  Notes:

- Use case: Show all the changes in A..B
  Git command: >
    `git diff A...B`
  Jujutsu command: >
    `jj diff -r A..B`
  Notes:

- Use case: Show description and diff of a change
  Git command: >
    `git show <revision>`
  Jujutsu command: >
    `jj show <revision>`
  Notes:

- Use case: Add a file to the current change
  Git command: >
    `touch filename; git add filename`
  Jujutsu command: >
    `touch filename`
  Notes:

- Use case: Remove a file from the current change
  Git command: >
    `git rm filename`
  Jujutsu command: >
    `rm filename`
  Notes:

- Use case: >
    Remove a previously tracked file from the current change, but keep it in the working
    copy
  Git command: >
    `git rm --cached filename`
  Jujutsu command: >
    `jj file untrack filename`
  Notes: >
    File name must match an ignore pattern to remain untracked. See
    [the documentation for working copies](working-copy.md#introduction) for
    more.

- Use case: Modify a file in the current change
  Git command: >
    `echo stuff >> filename`
  Jujutsu command: >
    `echo stuff >> filename`
  Notes:

- Use case: Finish work on the current change and start a new change
  Git command: >
    `git commit -a`
  Jujutsu command: >
    `jj commit`
  Notes:

- Use case: See compact log graph of ancestors of the current commit
  Git command: >
    `git log --oneline --graph`
  Jujutsu command: >
    `jj log -r ::@`
  Notes:

- Use case: See compact log graph of all reachable commits
  Git command: >
    `git log --oneline --graph --all`
  Jujutsu command: >
    `jj log -r 'all()'`
    or `jj log -r ::`
  Notes: >
    In a Git-backed Jujutsu repository the Git command will also show all commits preserved by Jujutsu,
    including hidden commits. To exclude all commits only preserved by Jujutsu, replace `--all` by `--exclude refs/jj/* --all`.
    This will also exclude the Jujutsu-reachable commits though, if they are not Git-reachable.

- Use case: Show compact log graph of commits not on the main branch
  Git command: >
    `git log --oneline --graph --branches --not upstream/main`
  Jujutsu command: >
    `jj log`
  Notes:

- Use case: >
    Show log of commits that have the string "stuff" in the changed lines,
    also known as the "pickaxe" in Git
  Git command: >
    `git log -G stuff`
  Jujutsu command: >
    `jj log -r 'diff_lines(regex:stuff)'`
  Notes:

- Use case: List versioned files in the working copy
  Git command: >
    `git ls-files --cached`
  Jujutsu command: >
    `jj file list`
  Notes:

- Use case: Search among files versioned in the repository
  Git command: >
    `git grep foo`
  Jujutsu command: >
    `jj file search --pattern=foo`
    or `rg --no-require-git foo`
  Notes:

- Use case: Abandon the current change and start a new change
  Git command: >
    `git reset --hard`
    (cannot be undone)
  Jujutsu command: >
    `jj abandon`
  Notes:

- Use case: Make the current change empty
  Git command: >
    `git reset --hard`
    (same as abandoning a change since Git has no concept of a "change")
  Jujutsu command: >
    `jj restore`
  Notes:

- Use case: Abandon the parent of the working copy, but keep its diff in the working copy
  Git command: >
    `git reset --soft HEAD~`
  Jujutsu command: >
    `jj squash --from @-`
  Notes:

- Use case: Discard working copy changes in some files
  Git command: >
    `git restore <paths>...`
    or `git checkout HEAD -- <paths>...`
  Jujutsu command: >
    `jj restore <paths>...`
  Notes:

- Use case: Edit description (commit message) of the current change
  Git command: Not supported
  Jujutsu command: >
    `jj describe`
  Notes:

- Use case: Edit description (commit message) of the previous change
  Git command: >
    `git commit --amend --only`
  Jujutsu command: >
    `jj describe @-`
  Notes:

- Use case: Edit description (commit message) of any change
  Git command: >
    `git commit --fixup=reword:X; git rebase --autosquash X^`
  Jujutsu command: >
    `jj describe X`
  Notes:

- Use case: Temporarily put away the current change
  Git command: >
    `git stash`
  Jujutsu command: >
    `jj new @-`
  Notes: >
    The old working-copy commit remains as a sibling commit.
    The old working-copy commit X can be restored with `jj edit X`.

- Use case: Start working on a new change based on the <main> bookmark/branch
  Git command: >
    `git switch -c topic main`
    or `git checkout -b topic main`
    (may need to stash or commit first)
  Jujutsu command: >
    `jj new main`
  Notes:

- Use case: Merge branch A into the current change
  Git command: >
    `git merge A`
  Jujutsu command: >
    `jj new @ A`
  Notes:

- Use case: Check out a named revision (or branch) to examine source
  Git command: >
    `git checkout v1.0.1`
  Jujutsu command: >
    `jj new v1.0.1`
  Notes: Creates new empty change on top (see `jj new main`)

- Use case: Move bookmark/branch A onto bookmark/branch B
  Git command: >
    `git rebase B A`
    (may need to rebase other descendant branches separately)
  Jujutsu command: >
    `jj rebase -b A -o B`
  Notes:

- Use case: Move change A and its descendants onto change B
  Git command: >
    `git rebase --onto B A^ <some descendant bookmark>`
    (may need to rebase other descendant bookmarks separately)
  Jujutsu command: >
    `jj rebase -s A -o B`
  Notes:

- Use case: Reorder changes from A-B-C-D to A-C-B-D
  Git command: >
    `git rebase -i A`
  Jujutsu command: >
    `jj rebase -r C --before B`
  Notes:

- Use case: Reorder multiple commits at once
  Git command: >
    `git rebase -i`
  Jujutsu command: >
    `jj arrange`
  Notes:

- Use case: Move the diff in the current change into the parent change
  Git command: >
    `git commit --amend -a`
  Jujutsu command: >
    `jj squash`
  Notes:

- Use case: Interactively move part of the diff in the current change into the parent change
  Git command: >
    `git add -p; git commit --amend`
  Jujutsu command: >
    `jj squash -i`
  Notes:

- Use case: Move the diff in the working copy into an ancestor
  Git command: >
    `git commit --fixup=X; git rebase --autosquash X^`
  Jujutsu command: >
    `jj squash --into X`
  Notes:

- Use case:
    Interactively move part of the diff in an arbitrary change to another arbitrary
    change
  Git command: Not supported
  Jujutsu command: >
    `jj squash -i --from X --into Y`
  Notes:

- Use case: Interactively split the changes in the working copy in two
  Git command: >
    `git commit -p`
  Jujutsu command: >
    `jj split`
  Notes:

- Use case: Interactively split an arbitrary change in two
  Git command: >
    Not supported
    (can be emulated with the "edit" action in `git rebase -i`)
  Jujutsu command: >
    `jj split -r <revision>`
  Notes:

- Use case: Interactively edit the diff in a given change
  Git command: >
    Not supported
    (can be emulated with the "edit" action in `git rebase -i`)
  Jujutsu command: >
    `jj diffedit -r <revision>`
  Notes:

- Use case: Resolve conflicts and continue interrupted operation
  Git command: >
    `echo resolved > filename; git add filename; git rebase/merge/cherry-pick
    --continue`
  Jujutsu command: >
    `echo resolved > filename; jj squash`
  Notes: Operations don't get interrupted, so no need to continue.

- Use case: Create a copy of a commit on top of another commit
  Git command: >
    `git co <destination>; git cherry-pick <source>`
  Jujutsu command: >
    `jj duplicate <source> -o <destination>`
  Notes:

- Use case: Find the root of the working copy (or check if in a repo)
  Git command: >
    `git rev-parse --show-toplevel`
  Jujutsu command: >
    `jj workspace root`
  Notes:

- Use case: List bookmarks/branches
  Git command: >
    `git branch`
  Jujutsu command: >
    `jj bookmark list`
    or `jj b l` for short
  Notes:

- Use case: Create a bookmark/branch
  Git command: >
    `git branch <name> <revision>`
  Jujutsu command: >
    `jj bookmark create <name> -r <revision>`
  Notes:

- Use case: Move a bookmark/branch forward
  Git command: >
    `git branch -f <name> <revision>`
  Jujutsu command: >
    `jj bookmark move <name> --to <revision>`
    or `jj b m <name> -t <revision>` for short
  Notes:

- Use case: Move a bookmark/branch backward or sideways
  Git command: >
    `git branch -f <name> <revision>`
  Jujutsu command: >
    `jj bookmark move <name> --to <revision> --allow-backwards`
  Notes:

- Use case: Delete a bookmark/branch
  Git command: >
    `git branch --delete <name>`
  Jujutsu command: >
    `jj bookmark delete <name>`
  Notes:

- Use case: List tags
  Git command: >
    `git tag -l`
  Jujutsu command: >
    `jj tag list`
  Notes:

- Use case: Create a tag
  Git command: >
    `git tag <name> <revision>`
  Jujutsu command: >
    `jj tag set <name> -r <revision>`
  Notes:

- Use case: Delete a tag
  Git command: >
    `git tag -d <name>`
  Jujutsu command: >
    `jj tag delete <name>`
  Notes:

- Use case: List tags that contain a revision (targets are descendants of the revision)
  Git command: >
    `git tag --contains <revision>`
  Jujutsu command: >
    `jj tag list -r '<revision>::'`
  Notes: >
    Uses jj's revset language. The `::` postfix operator selects all
    descendants of the given revision.

- Use case: List tags merged into a revision (targets are ancestors of the revision)
  Git command: >
    `git tag --merged <revision>`
  Jujutsu command: >
    `jj tag list -r '::<revision>'`
  Notes: >
    Uses jj's revset language. The `::` prefix operator selects all
    ancestors of the given revision.

- Use case: See log of operations performed on the repo
  Git command: Not supported
  Jujutsu command: >
    `jj op log`
  Notes:

- Use case: Undo the last operation
  Git command: Not supported
  Jujutsu command: >
    `jj undo`
  Notes: >
    There is also a matching `jj redo` command to redo the most recently undone
    operation.

- Use case: Revert an earlier operation
  Git command: Not supported
  Jujutsu command: >
    `jj op revert`
  Notes:

- Use case: Create a commit that cancels out a previous commit
  Git command: >
    `git revert <revision>`
  Jujutsu command: >
    `jj revert -r <revision> -B @`
  Notes:

- Use case: Show what revision and author last modified each line of a file
  Git command: >
    `git blame <file>`
  Jujutsu command: >
    `jj file annotate <path>`
  Notes:

---

### Git Comparison (`docs/git-comparison.md`)

# Comparison with Git

## Introduction

This document attempts to describe how Jujutsu is different from Git. See
[the Git-compatibility doc](git-compatibility.md) for information about how
the `jj` command interoperates with Git repos. See
[the Git command table](git-command-table.md) for a table of similar commands.

## Overview

Here is a list of conceptual differences between Jujutsu and Git, along with
links to more details where applicable and available. There's a
[table](git-command-table.md) explaining how to achieve various use cases.

* **The working copy is automatically committed.** That results in a simpler and
  more consistent CLI because the working copy is now treated like any other
  commit. [Details](working-copy.md).
* **There's no index (staging area).** Because the working copy is automatically
  committed, an index-like concept doesn't make sense. The index is very similar
  to an intermediate commit between `HEAD` and the working copy, so workflows
  that depend on it can be modeled using proper commits instead. Jujutsu has
  excellent support for moving changes between commits. [Details](#the-index).
* **No need for branch names (but they are supported as
  [bookmarks](glossary.md#bookmark)).** Git lets you check out a commit without
  attaching a branch to it. It calls this state "detached HEAD". This is the
  normal state in Jujutsu (there's actually no way -- yet, at least -- to have
  an active branch/bookmark). However, Jujutsu keeps track of all visible heads
  (leaves) of the commit graph, so the commits won't get lost or
  garbage-collected.
* **No current branch.** Git lets you check out a branch, making it the 'current
  branch', and new commits will automatically update the branch. This is
  necessary in Git because Git might otherwise lose track of the new commits.

    Jujutsu does not have a corresponding concept of a 'current bookmark';
  instead, you update bookmarks manually. For example, if you start work on top
  of a commit with a bookmark, new commits are created on top of the bookmark,
  then you issue a later command to update the bookmark.
* **Conflicts can be committed.** No commands fail because of merge conflicts.
  The conflicts are instead recorded in commits and you can resolve them later.
  [Details](conflicts.md). This lets us rebase conflict and conflict
  resolutions, and thereby addressing most `git rerere` use cases.
* **Descendant commits are automatically rebased.** Whenever you rewrite a
  commit (e.g. by running `jj rebase`), all its descendants commits will
  automatically be rebased on top. Branches pointing to it will also get
  updated, and so will the working copy if it points to any of the rebased
  commits.
* **No "evil merges".** In Git, a merge commit that contains changes that are
  not in any parent are called [evil merges]. We believe the reason for this
  opinionated term is that Git has historically not been very good at working
  with such commits. For example, `git show` doesn't show them well (without
  `--remerge-diff` from 2022), and `git rebase` drops the changes (without
  `--rebase-merges` from 2018). Jujutsu defines the changes in a commit as
  being relative to the auto-merged parents in all contexts, so you can safely
  include change in merge commits.
* **Bookmarks/branches are identified by their names (across remotes).** For
  example, if you pull from a remote that has a `main` branch, you'll get a
  bookmark by that name in your local repo. If you then move it and push back to
  the remote, the `main` branch on the remote will be updated.
  [Details](bookmarks.md).
* **The operation log replaces reflogs.** The operation log is similar to
  reflogs, but is much more powerful. It keeps track of atomic updates to all
  refs at once (Jujutsu thus improves on Git's per-ref history much in the same
  way that Subversion improved on RCS's per-file history). The operation log
  powers e.g. the undo functionality. [Details](operation-log.md)
* **There's a single, virtual root commit.** Like Mercurial, Jujutsu has a
  virtual commit (with a hash consisting of only zeros) called the "root commit"
  (called the "null revision" in Mercurial). This commit is a common ancestor of
  all commits. That removes the awkward state Git calls the "unborn branch"
  state (which is the state a newly initialized Git repo is in), and related
  command-line flags (e.g. `git rebase --root`, `git checkout --orphan`).

## The index

Git's ["index"](https://git-scm.com/book/en/v2/Git-Tools-Reset-Demystified) has
multiple roles. One role is as a cache of file system information. Jujutsu has
something similar. Unfortunately, Git exposes the index to the user, which makes
the CLI unnecessarily complicated (learning what the different flavors of
`git reset` do, especially when combined with commits and/or paths, usually
takes a while). Jujutsu, like Mercurial, doesn't make that mistake.

As a Git power-user, you may think that you need the power of the index to
commit only part of the working copy. However, Jujutsu provides commands for
more directly achieving most use cases you're used to using Git's index for. For
example, to create a commit from part of the changes in the working copy, you
might be used to using `git add -p; git commit`. With Jujutsu, you'd instead
use `jj split` to split the working-copy commit into two commits. To add more
changes into the parent commit, which you might normally use
`git add -p; git commit --amend` for, you can instead use `jj squash -i` to
choose which changes to move into the parent commit, or `jj squash <file>` to
move a specific file.

[evil merges]: https://git-scm.com/docs/gitglossary#Documentation/gitglossary.txt-evilmerge

---

### Git Compatibility (`docs/git-compatibility.md`)

# Git compatibility

Jujutsu has two backends for storing commits. One of them uses a regular Git
repo, which means that you can collaborate with Git users without them even
knowing that you're not using the `git` CLI.

See `jj help git` for help about the `jj git` family of commands, and e.g.
`jj help git push` for help about a specific command (use `jj git push -h` for
briefer help).

## Supported features

The following list describes which Git features Jujutsu is compatible with. For
a comparison with Git, including how workflows are different, see the
[Git-comparison doc](git-comparison.md).

* **Configuration: Partial.** The only configuration from Git (e.g. in
  `~/.gitconfig`) that's respected is the following. Feel free to file a bug if
  you miss any particular configuration options.
  * The configuration of remotes (`[remote "<name>"]`). Simple fetch refspecs
    are respected when branches are not explicitly specified on the CLI.
    (`git` is used for remote operations)
  * `core.excludesFile`
* **Authentication: Yes.** `git` is used for remote operations under the hood.
* **Branches: Yes.** You can read more about
  [how branches work in Jujutsu](bookmarks.md)
  and [how they interoperate with Git](#branches).
* **Tags: Partial.** You can check out tagged commits by name (pointed to by
  either annotated or lightweight tags). You can also create lightweight tags,
  but you cannot create annotated tags.
* **.gitignore: Yes.** Patterns in `.gitignore` files are supported. So are
  ignores in `.git/info/exclude` or configured via Git's `core.excludesFile`
  config. Since working-copy files are snapshotted by almost every `jj` command,
  you might need to run `jj file untrack` to exclude newly ignored files from the
  working-copy commit. It's recommended to set up the ignore patterns earlier.
  The `.gitignore` support uses a native implementation, so please report a bug
  if you notice any difference compared to `git`.
* **.gitattributes: No.** There's [#53](https://github.com/jj-vcs/jj/issues/53)
  about adding support for at least the `eol` attribute.
* **Hooks: No.** There's [#405](https://github.com/jj-vcs/jj/issues/405)
  specifically for providing the checks from <https://pre-commit.com>.
* **Merge commits: Yes.** Octopus merges (i.e. with more than 2 parents) are
  also supported.
* **Detached HEAD: Yes.** Jujutsu supports anonymous branches, so this is a
  natural state.
* **Orphan branch: Yes.** Jujutsu has a virtual root commit that appears as
  parent of all commits Git would call "root commits".
* **Staging area: Kind of.** The staging area will be ignored. For example,
  `jj diff` will show a diff from the Git HEAD to the working copy. There are
  [ways of fulfilling your use cases without a staging
  area](git-comparison.md#the-index).
* **Garbage collection: Yes.** It should be safe to run `git gc` in the Git
  repo, but it's not tested, so it's probably a good idea to make a backup of
  the whole workspace first. There's [no garbage collection and repacking of
  Jujutsu's own data structures yet](https://github.com/jj-vcs/jj/issues/12),
  however.
* **Bare repositories: Yes.** You can use `jj git init --git-repo=<path>` to
  create a repo backed by a bare Git repo.
* **Submodules: No.** They will not show up in the working copy, but they will
  not be lost either.
* **Partial clones: No.**
* **Shallow clones: Kind of.** Shallow commits all have the virtual root commit
  as their parent. However, deepening or fully unshallowing a repository is
  currently not yet supported and will cause issues.
* **git-worktree: No.** However, there's native support for multiple working
  copies backed by a single repo. See the `jj workspace` family of commands.
* **Sparse checkouts: No.** However, there's native support for sparse
  checkouts. See the `jj sparse` command.
* **Signed commits: Yes.**
  You can sign commits automatically [by configuration](config.md#commit-signing),
  or use the `jj sign` command.
* **Git LFS: No.** ([#80](https://github.com/jj-vcs/jj/issues/80))

## Creating an empty repo

To create an empty repo using the Git backend, use `jj git init <name>`. This
creates a [colocated](#colocated-jujutsugit-workspaces) Jujutsu workspace,
there will be a `.jj` directory and a `.git` directory.

## Creating a repo backed by an existing Git repo

There are two common ways to start using Jujutsu with a Git repository you
already have on disk.

### In the same working copy (colocated)

To create a Jujutsu repo *in the Git repository itself* (a repo with a
[colocated](#colocated-jujutsugit-workspaces) workspace), change into that
directory and run:

```shell
$ cd path/to/git-repo
$ jj git init
```

Jujutsu reuses the existing Git repository as its backing store, so both `.jj/`
and `.git/` live at the top level of the same working copy and you can use both
`jj` and `git` commands there. This is the usual way to start using Jujutsu on
an existing local Git checkout.

### In a separate working copy

To create a Jujutsu repo that shares commits with an existing Git repo but has
its own working-copy files (similar to a
[Git worktree](https://git-scm.com/docs/git-worktree)), create it in a directory
that is not already a Git checkout and point it at the Git repo:

```shell
$ cd path/to/new/jj-repo
$ jj git init --git-repo=path/to/git-repo
```

(`path/to/git-repo` may be absolute, or relative to the current directory.) See
`jj git init --help` for other arguments.

The working-copy files and the record of the working-copy commit will be
separate, but the commits will be accessible in both repos. Use `jj git import`
to update the Jujutsu repo with changes made in the Git repo. Use
`jj git export` to update the Git repo with changes made in the Jujutsu repo.

## Creating a repo by cloning a Git repo

To create a Jujutsu repo from a remote Git URL, use `jj git clone <URL>
[<destination>]`. For example, `jj git clone
https://github.com/octocat/Hello-World` will clone GitHub's "Hello-World" repo
into a directory by the same name.

By default, the remote repository will be named `origin`. You can use a name of
your choice by adding `--remote <remote name>` to the `jj git clone` command.

## <a name="colocated-jujutsugit-repos"></a>Colocated Jujutsu/Git workspaces

A colocated Jujutsu workspace is a hybrid Jujutsu/Git workspace. This is the
default for Git-backed workspace created with `jj git init` or `jj git clone`.
The Git repo and the Jujutsu workspace then share the same working copy. Jujutsu
will import and export from and to the Git repo on every `jj` command
automatically.

This mode is very convenient when tools (e.g. build tools) expect a Git repo to
be present.

It is allowed to mix `jj` and `git` commands in such a workspace in any order.
However, it may be easier to keep track of what is going on if you mostly use
read-only `git` commands and use `jj` to make changes to the repo. One reason
for this (see below for more) is that `jj` commands will usually put the Git
repo in a "detached HEAD" state, since in `jj` there is not concept of a
"currently tracked branch". Before doing mutating Git commands, you may need to
tell Git what the current branch should be with a `git switch` command.

You can undo the results of mutating `git` commands using `jj undo` and `jj op
restore`. Inside `jj op log`, changes by `git` will be represented as an "import
git refs" operation.

You can disable colocation with the `--no-colocate` flag on the commands `jj git
init` and `jj git clone` or by setting the configuration `git.colocate = false`.
Much of the repo data will still be stored in the Git repository format, but the
Git repository will be hidden inside a sub-directory of the `.jj` directory.
Moreover, unless you explicitly use the `jj git import` and `jj git export`
commands, that Git repository will either have no branches at all (not even a
main branch) or will have branches that are out of date with jj's bookmarks.

Colocation can be disabled because it does have some disadvantages:

* Interleaving `jj` and `git` commands increases the chance of confusing branch
  conflicts or [conflicted (AKA divergent) change
  ids](glossary.md#divergent-change). These never lose data, but can be
  annoying.

    Such interleaving can happen unknowingly. For example, some IDEs can cause
  it because they automatically run `git fetch` in the background from time to
  time.

* In colocated workspaces with a very large number of branches or other refs,
  `jj` commands can get noticeably slower because of the automatic
  `jj git import` executed on each command. This can be mitigated by
  occasionally running `jj util gc` to speed up the import (that command
  includes packing the Git refs).

* Git tools will have trouble with revisions that contain conflicted files.
  While `jj` renders these files with conflict markers in the working copy, they
  are stored in a non-human-readable fashion inside the repo. Git tools will
  often see this non-human-readable representation.

* When a `jj` branch is conflicted, the position of the branch in the Git repo
  will disagree with one or more of the conflicted positions. The state of that
  branch in git will be labeled as though it belongs to a remote named "git",
  e.g. `branch@git`.

* Jujutsu will ignore Git's staging area. It will not understand merge conflicts
  as Git represents them, unfinished `git rebase` states, as well as other less
  common states a Git repository can be in.

* Colocated workspaces are less resilient to
  [concurrency](technical/concurrency.md#syncing-with-rsync-nfs-dropbox-etc)
  issues if you share the repo using an NFS filesystem or Dropbox. In general,
  such use of Jujutsu is not currently thoroughly tested.

* There may still be bugs when interleaving mutating `jj` and `git` commands,
  usually having to do with a branch pointer ending up in the wrong place. We
  are working on the known ones, and are not aware of any major ones. Please
  report any new ones you find, or if any of the known bugs are less minor than
  they appear.

### Converting a workspace into a colocated workspace

A Jujutsu workspace backed by a Git repo has a full Git repo inside. Such a
workspace can be converted into a colocated workspace using the
`jj git colocation` command.

To check the current colocation status of your workspace:

```bash
jj git colocation status
```

To convert to a colocated workspace:

```bash
jj git colocation enable
```

To convert to a non-colocated workspace:

```bash
jj git colocation disable
```

The `jj git colocation enable` command automates the following manual process:

```bash
# Ignore the .jj directory in Git
echo '/*' > .jj/.gitignore
# Move the Git repo
mv .jj/repo/store/git .git
# Tell jj where to find it (do not use on Windows! See below.)
echo -n '../../../.git' > .jj/repo/store/git_target
# Make the Git repository non-bare and set HEAD
git config --unset core.bare
# Convince jj to update .git/HEAD to point to the working-copy commit's parent
jj new && jj undo
```

!!! warning

    On Windows, the `echo` command will append line endings and cause `jj`
    to complain about the contents of `git_target`.

    Instead of the `echo -n ...` line, use:
    `Set-Content -Path .jj/repo/store/git_target -Value ../../../.git -NoNewLine`

## Branches

TODO: Describe how branches are mapped

## Format mapping details

Paths are assumed to be UTF-8. I have no current plans to support paths with
other encodings.

Commits created by `jj` have a ref starting with `refs/jj/` to prevent GC.

Commits with conflicts cannot be represented in Git. They appear in the Git
commit as root directories called`.jjconflict-base-*/` and
`.jjconflict-side-*/`. Note that the purpose of this representation is only to
prevent GC of the relevant trees; the authoritative information is in a
non-standard `jj:trees` commit header. As long as you use `jj`
commands to work with them, you won't notice those paths. If, on the other hand,
you use e.g. `git switch` to check one of them out, you will see those
directories in your working copy. If you then run e.g. `jj status`, the
resulting snapshot will contain the contents of the first side of the conflict
as well as the `.jjconflict-*/` directories, making it look like they
were added to your working copy. You will probably want to run
`jj abandon` to get back to the state with the unresolved conflicts.

Change IDs are stored in git commit headers as reverse hex encodings. This is
a non-standard header and is not preserved by all `git` tooling. For example,
the header is preserved by a `git commit --amend`, but is not preserved through
a rebase operation. GitHub and other major forges seem to preserve them for the
most part. The header is written by default in commits created by `jj` (since
version `0.30.0`) and this functionality can be turned off using the
`git.write-change-id-header` setting.

Note that `git show` and `git log` do not print this header. To verify presence
of a `change-id` commit header using `git` itself, use
[`git-cat-file`](https://git-scm.com/docs/git-cat-file) like this:

```shell
git cat-file -p <commit ref>
```

---

### Git Experts (`docs/git-experts.md`)

# Jujutsu for Git experts

People who are proficient with Git often ask what benefit there is to using
Jujutsu. This page explains the practical advantages for Git experts, with
examples showing how common workflows become easier, safer, or faster with
Jujutsu.

## Git can be used side-by-side in the same repository

Jujutsu and Git repositories exist in the same directory, so you can use `jj`
and `git` side-by-side (this is called [colocation]). If you find a situation
that's easier with Git, run the `git` command and return to `jj` when you're
done (and please [make a feature request] if there isn't one yet!).

Colocation makes migration easier because you can adopt Jujutsu for the
workflows it improves without losing access to the Git commands and tools you
already know.

[colocation]: glossary.md#colocated-repos
[make a feature request]: https://github.com/jj-vcs/jj/issues

## The Git index/staging area

Jujutsu does not have an index/staging area as Git does. Because rewriting
commits is quick and easy, it's natural to use commits as a replacement for the
index.

Instead of separate commands just for interacting with the index (`git add`,
`git rm --cached`), the commands `jj split` and `jj squash` can be used to move
work-in-progress as easily as moving finished work.

```sh
# Split the working copy commit into two sequential commits, putting file1 and
# file2 In the first commit
jj split file1 file2

# or interactively select which changes to split
jj split

# Move the changes in file3 into the parent commit
jj squash file3
# or, interactively:
jj squash -i
```

## Automatic and safer history editing

If you frequently amend, reorder, or squash commits, Jujutsu can often perform
the same operations in fewer commands.

Suppose you want to amend an older commit. With Git you might do this in three
steps:

```sh
git add file1 file2
git commit --fixup abc
git rebase -i --autosquash
```

With Jujutsu, you simply squash the changes directly into the commit you want to
amend. All descendants are automatically rebased on top of the amended commit:

```sh
jj squash --into abc file1 file2
```

## Undo is more powerful than using the reflog

Git's reflog is powerful, but it's per-ref and can be awkward to use when
multiple refs and operations are involved.

Jujutsu's operation log records the state of the entire repository: Every change
is an operation you can inspect, and you can restore to any earlier state with
one command.

Common uses of the operation log:

- `jj undo` reverts the last operation in one step, without needing to figure
  out which ref to reset. You can repeat `jj undo` to continue stepping backwards
  in time.

- `jj op log -p` shows operations with diffs so you can inspect what happened.

- `--at-operation ID` lets you run commands as if the repository were in a
  previous state.

## The evolution log shows the history of a single change

The Git reflog shows how refs moved over time, but makes it difficult to see how
a particular commit evolved over time. Jujutsu's evolution log ("evolog") shows
exactly this: Each time a change is rewritten, the update is visible in the
evolog.

You can use the evolog to find a previous version, then `jj restore` to restore
the complete or partial contents to the current version.

## `jj absorb` makes it easier to update a patch stack

When amending several commits in a stack of changes, Git requires you to run
`git commit --fixup <ID>` at least once for each commit before running `git
rebase --autosquash`.

`jj absorb` is useful when you've made small fixes in the working copy and want
them incorporated into recent commits. It automatically moves each change in the
working copy into the previous commit where that line was changed.

Use `jj absorb --interactive` to choose only some hunks to consider for
absorption. Any hunks that are not selected or cannot be absorbed unambiguously
remain in the source commit.

It doesn't solve all cases: If multiple commits in the stack modified the same
line as was changed in the working copy, it will not move that change. But it
does help the trivial cases, leaving you to decide how to squash the remaining
changes.

---

### Github (`docs/github.md`)

# Using Jujutsu with GitHub and GitLab Projects

This guide assumes a basic understanding of either Git or Mercurial.

## Basic workflow

The simplest way to start with Jujutsu is to create a stack of commits first.
You will only need to create a bookmark when you need to push the stack to a
remote. There are two primary workflows: using a generated bookmark name or
naming a bookmark.

### Using a generated bookmark name

In this example we're letting Jujutsu auto-create a bookmark.

```shell
# Start a new commit off of the default bookmark.
$ jj new main
# Refactor some files, then add a description and start a new commit
$ jj commit -m 'refactor(foo): restructure foo()'
# Add a feature, then add a description and start a new commit
$ jj commit -m 'feat(bar): add support for bar'
# Let Jujutsu generate a bookmark name and push that to GitHub. Note that we
# push the working-copy commit's *parent* because the working-copy commit
# itself is empty.
$ jj git push --change @- # or -c for short
```

### Using a named bookmark

In this example, we create a bookmark named `bar` and then push it to the remote.

```shell
# Start a new commit off of the default bookmark.
$ jj new main
# Refactor some files, then add a description and start a new commit
$ jj commit -m 'refactor(foo): restructure foo()'
# Add a feature, then add a description and start a new commit
$ jj commit -m 'feat(bar): add support for bar'
# Create a bookmark so we can push it to GitHub. Note that we created the bookmark
# on the working-copy commit's *parent* because the working copy itself is empty.
$ jj bookmark create bar -r @- # `bar` now contains the previous two commits.
# Set the bookmark to be tracked on the remote.
$ jj bookmark track bar
# Push the bookmark to GitHub (pushes only `bar`)
$ jj git push
```

While it's possible to create a bookmark in advance and commit on top of it in a
Git-like manner, you will then need to move the bookmark manually when you create
a new commits. Unlike Git, Jujutsu will not do it automatically.

## Updating the repository

Jujutsu does not currently have a direct equivalent of `git pull`, though it may
gain [a similar command someday][sync-issue].

In the meantime, updating your branches is a two step process. The first is to
`jj git fetch` to fetch everything that's happened on your remote. The second is
to rebase all of your branches on top of your `main` branch with `jj rebase -o
main`. This command's default is the same as passing `-b @`, so if you have more
than one outstanding branch, you'll need to call `jj rebase` again with the `-b`
flag for each of them, or pass multiple `-b` arguments, one for every branch.

[sync-issue]: https://github.com/jj-vcs/jj/issues/1039

## Working in a Git colocated workspaces

After doing `jj git init`, which
[colocates](git-compatibility.md#colocated-jujutsugit-workspaces) the `.jj` and
`.git` directories, Git will be in a [detached HEAD state][detached], which is
unusual, as Git mainly works with named branches; jj does not.

In a colocated workspace, every `jj` command will automatically synchronize
Jujutsu's view of the repo with Git's view. For example, `jj commit` updates the
HEAD of the Git repository, enabling an incremental migration.

```shell
$ nvim docs/tutorial.md
$ # Do some more work.
$ jj commit -m "Update tutorial"
# Create a bookmark on the working-copy commit's parent
$ jj bookmark create doc-update -r @-
$ jj bookmark track doc-update
$ jj git push
```

## Working in a Jujutsu repository

In a Jujutsu repository, the workflow is simplified. If there's no need for
explicitly named bookmarks, you can just generate one for a change. As Jujutsu is
able to create a bookmark for a revision.

```shell
$ # Do your work
$ jj commit
$ # Push change "mw", letting Jujutsu automatically create a bookmark called
$ # "push-mwmpwkwknuz"
$ jj git push -c mw
```

## Addressing review comments

There are two workflows for addressing review comments, depending on your
project's preference. Many projects prefer that you address comments by adding
commits to your bookmark[^1]. Some projects (such as Jujutsu and LLVM) instead
prefer that you keep your commits clean by rewriting them and then
force-pushing[^2].

### Adding new commits

If your project prefers that you address review comments by adding commits on
top, you can do that by doing something like this:

```shell
$ # Create a new commit on top of the `your-feature` bookmark from above.
$ jj new your-feature
$ # Address the comments by updating the code. Then review the changes.
$ jj diff
$ # Give the fix a description and create a new working-copy on top.
$ jj commit -m 'address pr comments'
$ # Update the bookmark to point to the new commit.
$ jj bookmark move your-feature --to @-
$ # Push it to your remote
$ jj git push
```

Notably, the above workflow creates a new commit for you. The same can be
achieved without creating a new commit.

!!! note

    If the working copy commit becomes immutable (for example, after
    `jj git push`), Jujutsu automatically creates a new working-copy commit on
    top. If you need to make further edits before pushing, run `jj new` to
    avoid amending them to the previous commit.

```shell
$ # Create a new commit on top of the `your-feature` bookmark from above.
$ jj new your-feature
$ # Address the comments by updating the code. Then review the changes.
$ jj diff
$ # Give the fix a description.
$ jj describe -m 'address pr comments'
$ # Update the bookmark to point to the current commit.
$ jj bookmark move your-feature --to @
$ # Push it to your remote
$ jj git push
```

### Rewriting commits

If your project prefers that you keep commits clean, you can do that by doing
something like this:

```shell
$ # Create a new commit on top of the second-to-last commit in `your-feature`,
$ # as reviewers requested a fix there.
$ jj new your-feature- # NOTE: the trailing hyphen is not a typo!
$ # Address the comments by updating the code. Then review the changes.
$ jj diff
$ # Squash the changes into the parent commit
$ jj squash
$ # Push the updated bookmark to the remote. Jujutsu automatically makes it a
$ # force push
$ jj git push --bookmark your-feature
```

The hyphen after `your-feature` comes from the [revset](revsets.md) syntax.

## Working with other people's bookmarks

By default, `jj git clone` imports the default remote bookmark (which is usually
`main` or `master`), but `jj git fetch` doesn't import new remote bookmarks to
local bookmarks. This means that if you want to iterate or test another
contributor's bookmark, you'll need to do `jj new <bookmark>@<remote>` onto it.

If you want to import all remote bookmarks including inactive ones, set
`remotes.<name>.auto-track-bookmarks = "*"` in the config file. Then you can
specify a contributor's bookmark as `jj new <bookmark>` instead of `jj new
<bookmark>@<remote>`.

You can find more information on that setting [here][auto-bookmark].

## Using GitHub CLI

GitHub CLI will have trouble finding the proper Git repository path in jj repos
that aren't [colocated](./git-compatibility.md#colocated-jujutsugit-workspaces)
(see [issue #1008]). You can configure the `$GIT_DIR` environment variable to
point it to the right path:

```shell
$ GIT_DIR=$(jj git root) gh issue list
```

You can make that automatic by installing [direnv](https://direnv.net) and
defining hooks in a `.envrc` file in the repository root to configure `$GIT_DIR`.
Just add this line into `.envrc`:

```shell
export GIT_DIR=$(jj git root)
```

and run `direnv allow` to approve it for direnv to run. Then GitHub CLI will
work automatically even in workspaces that aren't colocated so you can execute
commands like `gh issue list` normally.

[issue #1008]: https://github.com/jj-vcs/jj/issues/1008

## Useful Revsets

Log all revisions across all local bookmarks that aren't on the main bookmark nor
on any remote:

```shell
$ jj log -r 'bookmarks() & ~(main | remote_bookmarks())'
```

Log all revisions that you authored, across all bookmarks that aren't on any
remote:

```shell
$ jj log -r 'mine() & bookmarks() & ~remote_bookmarks()'
```

Log all remote bookmarks that you authored or committed to:

```shell
$ jj log -r 'remote_bookmarks() & (mine() | committer(your@email.com))'
```

Log all ancestors of the current working copy that aren't on any remote:

```shell
$ jj log -r 'remote_bookmarks()..@'
```

## Merge conflicts

For a detailed overview, how Jujutsu handles conflicts, revisit
the [tutorial][tut].

[^1]:
    This is a GitHub-style review, as GitHub currently only is able to compare
    bookmarks.

[^2]:
    If you're wondering why we prefer clean commits in this project, see
    e.g. [this blog post][stacked]

[auto-bookmark]: config.md#automatic-tracking-of-bookmarks
[detached]: https://git-scm.com/docs/git-checkout#_detached_head
[tut]: tutorial.md#conflicts
[stacked]: https://jg.gg/2018/09/29/stacked-diffs-versus-pull-requests/

## Using several remotes

It is common to use several remotes when contributing to a shared repository.
For example, "upstream" can designate the remote where the changes will be
merged through a pull-request while "origin" is your private fork of the
project.

```shell
$ jj git clone --remote upstream https://github.com/upstream-org/repo
$ cd repo
$ jj git remote add origin git@github.com:your-org/your-repo-fork
```

This will automatically setup your repository to track the main
bookmark from the upstream repository, typically `main@upstream`
or `master@upstream`.

You might want to `jj git fetch` from "upstream" and to `jj git push`
to "origin". You can configure the default remotes to fetch from and
push to in your configuration file
(`jj config edit --user|repo|workspace`):

```toml
[git]
fetch = "upstream"
push = "origin"
```

The default for both `git.fetch` and `git.push` is "origin".

If you usually work on a project from several computers, you may
configure `jj` to fetch from both repositories by default, in order to
keep your own bookmarks synchronized through your `origin` repository:

```toml
[git]
fetch = ["upstream", "origin"]
push = "origin"
```

## Git push options

`jj git push` supports passing Git “push options” to the server via
`-o`/`--option`. These are forwarded to the remote and interpreted by the hosting
platform, if it supports them. You can repeat `-o` to send multiple options.

- Syntax: `jj git push -o <push_option>` or `jj git push --option <push_option>`
- Multiple options: `jj git push -o foo -o bar=val`
- Quoting: If the option’s value has spaces, wrap it in double quotes.

Support is server-dependent. GitLab supports push options for CI and merge
requests; other platforms may not. Refer to your host’s documentation for the
full list and behavior.

Examples with GitLab push options (see GitLab docs for details):

- Skip CI for a push:
  ```shell
  jj git push -o ci.skip
  ```
- Pass CI variables to the created pipeline:
  ```shell
  jj git push -o 'ci.variable=MAX_RETRIES=10' -o 'ci.variable=MAX_TIME=600'
  ```
- Create a merge request with metadata on push:
  ```shell
  jj git push \
    -o merge_request.create \
    -o merge_request.target=main \
    -o 'merge_request.title=Add feature X' \
    -o 'merge_request.description=Implements X with tests' \
    -o merge_request.draft
  ```
- Auto-merge when pipeline succeeds and remove the source branch:
  ```shell
  jj git push \
    -o merge_request.merge_when_pipeline_succeeds \
    -o merge_request.remove_source_branch
  ```
- Add and remove labels:
  ```shell
  jj git push \
    -o 'merge_request.label=label1' \
    -o 'merge_request.label=label2' \
    -o 'merge_request.unlabel=label3'
  ```
- Assign and unassign users:
  ```shell
  jj git push \
    -o 'merge_request.assign=user1' \
    -o 'merge_request.assign=user2' \
    -o 'merge_request.unassign=user3'
  ```

---

### Glossary (`docs/glossary.md`)

# Glossary

## Anonymous branch

An anonymous branch is a chain of commits that doesn't necessarily have any
[bookmarks](#bookmark) pointing to it or to any of its descendants. Unlike Git,
Jujutsu keeps commits on anonymous branches around until they are explicitly
abandoned. Visible anonymous branches are tracked by the [view](#view), which
stores a list of [heads](#head) of such branches.

## Author date

The author date of a [commit](#commit) records when the commit's changes were
originally authored. It is set when the commit is first authored and usually
stays the same when the commit is [rewritten](#rewrite). For a newly-created
commit, the author date and [committer date](#committer-date) are usually the
same, but they can differ after history-editing commands such as `jj rebase`,
`jj describe`, or `jj squash`. When using the Git [backend](#backend), this is
stored as Git's native author date field.

## Backend

A backend is an implementation of the storage layer. There is currently only one
production-ready builtin commit backend: the Git backend. The Git backend stores
commits in a Git repository. There are several backends used for testing. Google
also has its own cloud-based backend.

There are also pluggable backends for storing other information than commits,
such as the "operation store backend" for storing
[the operation log](#operation-log).

## Bookmark

A bookmark is a named pointer to a [commit](#commit). They are similar to Git's
[branches](#branch) and even more similar to [Mercurial's
bookmarks](https://wiki.mercurial-scm.org/Bookmarks). See [here](bookmarks.md)
for details.

Unlike in Git, there is no concept of a "current bookmark"; bookmarks *do not*
move when you create a new commit. Bookmarks *do* automatically follow the
commit if it gets [rewritten](#rewrite).

## Branch

In the context of `jj`, the word "branch" usually refers to an [anonymous
branch](#anonymous-branch) or, less formally, a branch of the commit "tree"
(which is itself an informal way to refer to the commit graph, parts of which
might resemble a tree even when it's not mathematically a tree).

We also sometimes discuss Git's branches and branches on Git remotes. Locally,
these correspond to [bookmarks](#bookmark). In a colocated workspace, each
local Git branch corresponds to a `jj` bookmark.

## Change

A change is a commit as it [evolves over time](#rewrite). Changes themselves
don't exist as an object in the data model; only the change ID does. The change
ID is a property of a commit.

## Change ID

A change ID is a unique identifier for a [change](#change). They are typically
16 bytes long and are often randomly generated. By default, `jj log` presents
them as a sequence of 12 letters in the k-z range, at the beginning of a line.
These are actually hexadecimal numbers that use "digits" z-k instead of 0-9a-f.

## Change offset

Sometimes a change ID might not unambiguously identify a commit, such as if the
commit is hidden or if the change ID is [divergent](#divergent-change). In these
cases, you can add an offset after the change ID to make it clear which commit
you are referring to, with the most recent commit having an offset of 0. For
instance, the most recent commit with change ID `xyz` could be referred to as
`xyz/0`, while the one before it would be `xyz/1`, and so on.

## Commit

A snapshot of the files in the repository at a given point in time (technically
a [tree object](#tree)), together with some metadata. The metadata includes the
author, the committer, the [author date](#author-date), the
[committer date](#committer-date), and pointers to the commit's parents. Through
the pointers to the parents, the commits form a
[Directed Acyclic Graph (DAG)](https://en.wikipedia.org/wiki/Directed_acyclic_graph)
.

Note that even though commits are stored as snapshots, they are often treated
as differences between snapshots, namely compared to their parent's snapshot. If
they have more than one parent, then the difference is computed against the
result of merging the parents. For example, `jj diff` will show the differences
introduced by a commit compared to its parent(s), and `jj rebase` will apply
those changes onto another base commit.

The word "revision" is used as a synonym for "commit".

## Commit ID

A commit ID is a unique identifier for a [commit](#commit). They are 20 bytes
long when using the Git backend. They are presented in regular hexadecimal
format at the end of the line in `jj log`, using 12 hexadecimal digits by
default. When using the Git backend, the commit ID is the Git commit ID.

## Committer date

The committer date of a [commit](#commit) records when that particular commit
object was created. It is set for a new commit and updated whenever `jj`
[rewrites](#rewrite) the commit, such as when rebasing, describing, or squashing
it. For a newly-created commit, the committer date and
[author date](#author-date) are usually the same, but they can differ after the
commit has been rewritten. When using the Git [backend](#backend), this is
stored as Git's native committer date field.

## <a name="colocated-repos"></a>Colocated workspaces

When using the Git [backend](#backend) and the backing Git repository's `.git/`
directory is a sibling of `.jj/`, we call the workspace colocated. Most
tools designed for Git can be easily used on such workspaces. `jj` and `git`
commands can be used interchangeably.

See [here](git-compatibility.md#colocated-jujutsugit-workspaces) for details.

## Conflict

Conflicts can occur in many places. The most common type is conflicts in files.
Those are the conflicts that users coming from other VCSs are usually familiar
with. You can see them in `jj status` and in `jj log` (the red "conflict"
label at the end of the line). See [here](conflicts.md) for details.

Conflicts can also occur in [bookmarks](#bookmark). For example, if you moved a
bookmark locally, and it was also moved on the remote, then the bookmark will be
in a conflicted state after you pull from the remote.
See [here](bookmarks.md#conflicts) for details.

Similar to a bookmark conflict, when a [change](#change) is rewritten locally
and remotely, for example, then the change will be in a conflicted state. We
call that a [divergent change](#divergent-change).

## Divergent change

A divergent change is a [change](#change) that has more than one
[visible commit](#visible-commits). These changes are displayed with a label of
"divergent" in the log.

## Head

A head is a commit with no descendants. The context in which it has no
descendants varies. For example, the `heads(X)`
[revset function](revsets.md#functions) returns commits that have no descendants
within the set `X` itself. The [view](#view) records which
anonymous heads (heads without a bookmark pointing to them) are visible at a
given [operation](#operation). Note that this is quite different from Git's
[HEAD](https://git-scm.com/book/en/v2/Git-Internals-Git-References#ref_the_ref).

## Hidden commits, abandoned commits

See [visible commits](#visible-commits).

## Operation

A snapshot of the [visible commits](#visible-commits) and [bookmarks](#bookmark)
at a given point in time (technically a [view object](#view)), together with
some metadata. The metadata includes the username, hostname, timestamps, and
pointers to the operation's parents.

## Operation log

The operation log is the
[DAG](https://en.wikipedia.org/wiki/Directed_acyclic_graph) formed by
[operation](#operation) objects, much in the same way that commits form a DAG,
which is sometimes called the "commit history". When operations happen in
sequence, they form a single line in the graph. Operations that happen
concurrently from jj's perspective result in forks and merges in the DAG.

## Repository

Basically everything under `.jj/`, i.e. the full set of [operations](#operation)
and [commits](#commit).

## Remote

A remote is a reference to a copy of your repository. The most common case is
that the remote is hosted on the internet or another network, but local remotes
are also possible. Remotes are useful when working on a project with multiple
collaborators. As Jujutsu is compatible with Git you can use all popular
providers that are also available for Git. For example
[GitHub](https://github.com), [GitLab](https://gitlab.com) or
[Codeberg](https://codeberg.org/).

## Revision

A synonym for [Commit](#commit).

## Revset

Jujutsu supports a functional language for selecting a set of revisions.
Expressions in this language are called "revsets". See [here](revsets.md) for
details. We also often use the term "revset" for the set of revisions selected
by a revset.

## Rewrite

To "rewrite" a commit means to create a new version of that commit with
different contents, metadata (including parent pointers), or both. Rewriting a
commit results in a new commit, and thus a new [commit ID](#commit-id), but the
[change ID](#change-id) generally remains the same. Some examples of rewriting a
commit would be changing its description or rebasing it. Modifying the working
copy rewrites the working copy commit.

## Root commit

The root commit is a virtual commit at the root of every repository. It has a
commit ID consisting of all '0's (`00000000...`) and a change ID consisting of
all 'z's (`zzzzzzzz...`). It can be referred to in [revsets](#revset) by the
function `root()`. Note that our definition of "root commit" is different from
Git's; Git's "root commits" are the first commit(s) in the repository, i.e. the
commits `jj log -r 'root()+'` will show.

## Tree

A tree object represents a snapshot of a directory in the repository. Tree
objects are defined recursively; each tree object only has the files and
directories contained directly in the directory it represents.

## Tracked bookmarks and tracking bookmarks

A remote bookmark can be made "tracked" with the `jj bookmark track` command. This
results in a "tracking" local bookmark that tracks the remote bookmark.

See [the bookmarks documentation](bookmarks.md#terminology-summary) for a more
detailed definition of these terms.

## Visible commits

Visible commits are the commits you see in `jj log -r 'all()'`. They are the
commits that are reachable from an anonymous head in the [view](#view).
Ancestors of a visible commit are implicitly visible.

Intuitively, a visible commit is the "latest version" of a given
[change](#change). A commit that is abandoned or [rewritten](#rewrite) stops
being visible and is labeled as "hidden". Such a commit is still accessible by
its [commit ID](#commit-id) or the combination of its [change ID](#change-id)
and a [change offset](#change-offset).

## View

A view is a snapshot of bookmarks and their targets, anonymous heads,
and working-copy commits. The anonymous heads define which commits
are [visible](#visible-commits).

A view object is similar to a [tree](#tree) object in that it represents a
snapshot without history, and an [operation](#operation) object is similar to a
[commit](#commit) object in that it adds metadata and history.

## Workspace

A workspace is a [working copy](#working-copy) and an
associated [repository](#repository). There can be multiple workspaces for a
single repository. Each workspace has a `.jj/` directory, but the
[commits](#commit) and [operations](#operation) will be stored in the initial
workspace; the other workspaces will have pointers to the initial workspace. See
[here](working-copy.md#workspaces) for details.

This is what Git calls a "worktree".

## Working copy

The working copy contains the files you're currently working on. It is
automatically snapshot at the beginning of almost every `jj` command, thus
creating a new [working-copy commit](#working-copy-commit) if any changes had
been made in the working copy. Conversely, the working copy is automatically
updated to the state of the working-copy commit at the end of almost every `jj`
command. See [here](working-copy.md) for details.

This is what Git calls a "working tree".

## Working-copy commit

A commit that corresponds to the current state of the working copy. There is
one working-copy commit per [workspace](#workspace). The current working-copy
commits are tracked in the [operation log](#operation-log).

---

### Install And Setup (`docs/install-and-setup.md`)

# Installation and setup

## Installation

### Download pre-built binaries for a release

There are [pre-built binaries] of the last released version of `jj` for
Windows, Mac, or Linux (the "musl" version should work on all distributions).

#### Cargo Binstall

If you use [`cargo-binstall`][cargo-binstall], you
can install binaries of the latest `jj` release from GitHub as follows:

```shell
# Will put the jj binary for the latest release in ~/.cargo/bin by default
cargo binstall --strategies crate-meta-data jj-cli
```

Without the `--strategies` option, you may get equivalent binaries that should
be compiled from the same source code.

!!! note

    If you'd like to install a prerelease version, you'll need to use one of the
    options below.

### Linux

#### From Source

First make sure that you have a Rust version >= 1.97.1 and that the
`build-essential` package is installed by running something like this:

```shell
sudo apt-get install build-essential
```

Now run either:

```shell
# To install the *prerelease* version from the main branch
cargo install --git https://github.com/jj-vcs/jj.git --locked --bin jj jj-cli
```

or:

```shell
# To install the latest release
cargo install --locked --bin jj jj-cli
```

#### Arch Linux
You can install the `jujutsu` package from the [official extra repository](https://archlinux.org/packages/extra/x86_64/jujutsu/):

```shell
pacman -S jujutsu
```

Or install from the [AUR repository](https://aur.archlinux.org/packages/jujutsu-git) with an [AUR Helper](https://wiki.archlinux.org/title/AUR_helpers):

```shell
yay -S jujutsu-git
```

#### NixOS

If you're on NixOS you can install a **released** version of `jj` using the
[nixpkgs `jujutsu` package](https://search.nixos.org/packages?channel=unstable&show=jujutsu).

To install a **prerelease** version, you can use the flake for this repository.
For example, if you want to run `jj` loaded from the flake, use:

```shell
nix run 'github:jj-vcs/jj'
```

You can also add this flake url to your system input flakes. Or you can
install the flake to your user profile:

```shell
# Installs the prerelease version from the main branch
nix profile install 'github:jj-vcs/jj'
```

#### Homebrew

If you use Homebrew, you can run:

```shell
# Installs the latest release
brew install jj
```

#### Gentoo Linux

`dev-vcs/jj` is available in the [GURU](https://wiki.gentoo.org/wiki/Project:GURU) repository.
Details on how to enable the GURU repository can be found [here](https://wiki.gentoo.org/wiki/Project:GURU/Information_for_End_Users).

Once you have synced the GURU repository, you can install `dev-vcs/jj` via Portage:

```shell
emerge -av dev-vcs/jj
```

#### openSUSE Tumbleweed

`jujutsu` can be installed from the official [openSUSE-Tumbleweed-Oss](http://download.opensuse.org/tumbleweed/repo/oss/) repository:

```shell
zypper install jujutsu
```

#### Fedora Linux

`jujutsu` has an unofficial [COPR
repo](https://copr.fedorainfracloud.org/coprs/aldantanneo/jj-vcs) for Fedora:

```shell
sudo dnf copr enable aldantanneo/jj-vcs
sudo dnf install jj-cli
```

### Mac

#### From Source

First make sure that you have a Rust version >= 1.97.1. You may also need to run:

```shell
xcode-select --install
```

Now run either:

```shell
# To install the *prerelease* version from the main branch
cargo install --git https://github.com/jj-vcs/jj.git \
     --locked --bin jj jj-cli
```

or:

```shell
# To install the latest release
cargo install --locked --bin jj jj-cli
```

#### Homebrew

If you use Homebrew, you can run:

```shell
# Installs the latest release
brew install jj
```

#### MacPorts

You can also install `jj` via [the MacPorts `jujutsu`
port][macports]:

```shell
# Installs the latest release
sudo port install jujutsu
```

### Windows

First make sure that you have a Rust version >= 1.97.1. Now run either:

```shell
# To install the *prerelease* version from the main branch
cargo install --git https://github.com/jj-vcs/jj.git --locked --bin jj jj-cli
```

or:

```shell
# To install the latest release
cargo install --locked --bin jj jj-cli
```

via winget:

```shell
# To install the latest release via winget
winget install jj-vcs.jj
```

via scoop:

```shell
# To install the latest release via scoop
scoop install main/jj
```

### FreeBSD

`devel/jujutsu` is [available](https://www.freshports.org/devel/jujutsu) in the ports.

Install pre-built pkg:

```shell
# To install the latest release via pkg
pkg install jujutsu
```

Install from building the port:

```shell
# To install the latest release via ports
cd /usr/ports/devel/jujutsu/ && make install clean
```

## Runtime Requirements

You will need git 2.42.0 or above. On older systems (e.g. Debian 11, Ubuntu
22.04) you will need to [upgrade Git](https://git-scm.com/install/).

## Initial configuration

You may want to configure your name and email so commits are made in your name.

```shell
$ jj config set --user user.name "Martin von Zweigbergk"
$ jj config set --user user.email "martinvonz@google.com"
```

## Command-line completion

Jujutsu provides 2 different command-line completion scripts.

### Standard completions

The standard completion script provides completions for `jj` subcommands and
options.

### Dynamic completions

The dynamic completion script provides completions for `jj` subcommands and
options, as well as additional completions, including bookmarks,
aliases, revisions, operations and files. Dynamic completions can be context
aware, for example they respect the global flags `--repository` and
`--at-operation` as well as some command-specific ones like `--revision`,
`--from`, and `--to`.

Dynamic completions are not the default/only option since the underlying engine
[is still labeled unstable][clap]. We expect to transition to them as the
default once the engine is stabilized. Please let us know if you encounter any
issues with dynamic completions.

!!! info "Which completion script should I use?"

    Generally, dynamic completions provide a much better completion experience.
    Although the underlying engine is deemed unstable, there have not been many
    issues in practice. Dynamic completions are the preferred option for many
    contributors and users.

    We recommend using the dynamic completion script, and falling back to the
    standard completion script if there are any issues.



### Bash

=== "Standard"

    ```shell
    source <(jj util completion bash)
    ```

=== "Dynamic"

    ```shell
    source <(COMPLETE=bash jj)
    ```

### Zsh

=== "Standard"

    ```shell
    autoload -U compinit
    compinit
    source <(jj util completion zsh)
    ```

=== "Dynamic"

    ```shell
    source <(COMPLETE=zsh jj)
    ```

### Fish

=== "Standard"

    ```shell
    jj util completion fish | source
    ```

=== "Dynamic"

    !!! note

        No configuration is required with fish >= 4.0.2 which loads dynamic completions by default.

    ```shell
    COMPLETE=fish jj | source
    ```

### Nushell

=== "Standard"

    ```nu
    jj util completion nushell | save -f completions-jj.nu
    use completions-jj.nu *  # Or `source completions-jj.nu`
    ```

### Powershell

=== "Standard"

    ```powershell
    jj util completion power-shell | Out-String | Invoke-Expression
    ```

=== "Dynamic"

    ```powershell
    $env:COMPLETE = "powershell"
    jj | Out-String | Invoke-Expression
    Remove-Item Env:\COMPLETE
    ```

Insert the above into your `$PROFILE` file
(usually `$HOME\Documents\PowerShell\Microsoft.PowerShell_profile.ps1`).

!!! note

    Note that to execute scripts in PowerShell on Windows, including `$PROFILE`,
    the [execution policy][ExecutionPolicies] needs to be set to `RemoteSigned`
    at minimum.

[cargo-binstall]: https://github.com/cargo-bins/cargo-binstall
[clap]: https://github.com/clap-rs/clap/issues/3166
[Homebrew]: https://brew.sh/
[macports]: https://ports.macports.org/port/jujutsu/
[pre-built binaries]: https://github.com/jj-vcs/jj/releases/latest
[ExecutionPolicies]: https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_execution_policies

---

### Operation Log (`docs/operation-log.md`)

# Operation log

## Introduction

Jujutsu records each operation that modifies the repo in the "operation log".
You can see the log with `jj op log`. Each operation object contains a snapshot
of how the repo looked at the end of the operation. We call this snapshot a
"view" object. The view contains information about where each bookmark, tag, and
Git ref (in Git-backed repos) pointed, as well as the set of heads in the repo,
and the current working-copy commit in each workspace. The operation object also
(in addition to the view) contains pointers to the operation(s) immediately
before it, as well as metadata about the operation, such as timestamps,
username, hostname, description.

The operation log allows you to undo operations one-by-one (`jj undo`) or even
revert a specific one which isn't the most recent operation (`jj op revert`). It
also lets you restore the entire repo to the way it looked at an earlier point
(`jj op restore`).

When referring to operations, you can use `@` to represent the current
operation.

The following operators are supported:

* `x-`: Parents of `x` (e.g. `@-`)
* `x+`: Children of `x`

## Divergent operations

One benefit of the operation log (and the reason for its creation) is that it
allows lock-free concurrency -- you can run concurrent `jj` commands without
corrupting the repo, even if you run the commands on different machines that
access the repo via a distributed file system (as long as the file system
guarantees that a write is only visible once previous writes are visible). When
you run a `jj` command, it will start by loading the repo at the latest
operation. It will not see any changes written by concurrent commands. If there
are conflicts, you will be informed of them by subsequent `jj st` and/or
`jj log` commands.

As an example, let's say you had started editing the description of a change and
then also update the contents of the change (maybe because you had forgotten the
editor). When you eventually close your editor, the command will succeed and
e.g. `jj log` will indicate that the change has diverged.

## Loading an old version of the repo

The top-level `--at-op`/`--at-operation` option allows you to load the repo at a
specific operation. This can be useful for understanding how your repo got into
the current state. It can be even more useful for understanding why someone
else's repo got into its current state.

When you use `--at-op`, the automatic snapshotting of the working copy will not
take place. When referring to a revision with the `@` symbol (as many commands
do by default), that will resolve to the working-copy commit recorded in the
operation's view (which is actually how it always works -- it's just the
snapshotting that's skipped with `--at-op`).

As a top-level option, `--at-op` can be passed to any command. However, you
will typically only want to run read-only commands. For example, `jj log`,
`jj st`, and `jj diff` all make sense. It's still possible to run e.g.
`jj --at-op=<some operation ID> describe`. That's equivalent to having started
`jj describe` back when the specified operation was the most recent operation
and then let it run until now (which can be done for that particular command by
not closing the editor). There's practically no good reason to do that other
than to simulate concurrent commands.

---

### Paid Contributors (`docs/paid_contributors.md`)

# Paying employers and their contributing employees

This is the list of companies paying for contributions to Jujutsu. For each
company, all employees who have contributed are listed, whether it's part of
their job to contribute or not.

The purpose of this list is to make it easy to spot conflicts of interest, such
as when an employee approves changes from another employee of the same company.
See [contribution docs](contributing.md#code-reviews) for details on this policy.

## East River Source Control

* benbrittain
* bts
* ConnerPetzold
* davidbarsky
* ilyagr
* martinvonz
* steveklabnik
* thoughtpolice

## Alphabet/Google

* 06393993
* 2079884FDavid
* aabmass
* ajaspers
* algmyr
* AM5800
* aspotashev
* brychanrobot
* daehyeok
* drieber
* durin42
* edre
* emesterhazy
* essiene
* ffyuanda
* finque
* honglooker
* hooper
* incognito124
* jonathantanmy
* josephlou5
* kevincliao
* kiranani
* lukegb
* matts1
* matttproud
* michaelchirico
* mlcui-corp
* orthros
* pmandloi28
* prattmic
* qfel
* Ralith
* rdamazio
* sbarfurth
* solson
* spectral54
* steadmon
* tbodt
* vaghinak-vardanyan
* zygoloid

## IMC Trading

* freundTech
* HoelzelJon

---

### Related Work (`docs/related-work.md`)

# Related work

Similar tools:

* [git-branchless](https://github.com/arxanas/git-branchless): Helps you use a
  branchless workflow in your Git repo. Supports anonymous branching, undo,
  and faster rebase (`git move`). Under heavy development and quickly gaining
  new features.
* [Sapling](https://sapling-scm.com/): A heavily modified fork of
  [Mercurial](https://www.mercurial-scm.org/) developed and used at Meta. It
  is compatible with Git, has undo functionality, and
  [a graphical interface](https://sapling-scm.com/docs/addons/isl).
  See [how it is different from Jujutsu](sapling-comparison.md).
* [GitUp](https://gitup.co/): A Mac-only GUI for Git. Like Jujutsu, supports
  undo and restoring the repo to an earlier snapshot. Backed by its
  [GitUpKit library](https://github.com/git-up/GitUp#gitupkit).
* [Gitless](https://gitless.com/): Another attempt at providing a simpler
  interface for Git. Like Jujutsu, does not have an "index"/"staging area"
  concept. Also doesn't move the working-copy changes between branches (which
  we do simply as a consequence of making the working copy a commit).
* [Breezy](https://www.breezy-vcs.org/): Another VCS that's similar in that it
  has multiple storage backends, including its own format as well as .git
  support.
* [GitButler](https://gitbutler.com/): A Git client that works with multiple
  virtual branches simultaneously, first-class conflicts, and operations history.

---

### Releasing (`docs/releasing.md`)

# How to do a release

## Update changelog and Cargo versions

Send a PR similar to <https://github.com/jj-vcs/jj/pull/7954>. Feel free to
copy-edit the changelog in order to:

* Populate "Release highlights" if relevant
* Put more important items first so the reader doesn't miss them
* Make items consistent when it comes to language and formatting
* Catch any misplaced changelog items by looking at the CHANGELOG diff.

To get the CHANGELOG diff, you can run

```shell
jj log -r 'heads(tags())'  # Check that this shows the previous version
jj diff --from 'heads(tags())' --to main CHANGELOG.md
```

Make sure to add a corresponding reference link at the bottom of the
CHANGELOG for the new version's tag. It should be the GitHub URL comparing
the previous version tag with the new version tag
(e.g. `https://github.com/jj-vcs/jj/compare/v0.32.0...v0.33.0`). Also update the
`[unreleased]` link accordingly, comparing the newest version against `HEAD`.

Producing the list of contributors is a bit annoying. The current suggestion is
to run something like this:

```shell
root=$(jj log -G -r 'heads(tags(glob:"v*.*.*") & ::trunk())' -T commit_id)
filter='
map(.commits[] | select(.author.login | (. != null and endswith("[bot]") | not)))
  | unique_by(if .author.login != null then .author.login else .author.email end)
  | map(if .author.login != null
        then "* \(.commit.author.name) (@\(.author.login))"
        else "* \(.commit.author.name)"
        end)
  | .[]
'
gh api "/repos/jj-vcs/jj/compare/$root...main" --paginate | jq -sr "$filter" | sort -f
```

<https://docs.github.com/en/rest/commits/commits?apiVersion=2022-11-28#compare-two-commits>

Alternatively, the list can be produced locally:

```shell
jj log -G -r 'heads(tags())..main' -T '"* " ++ author ++ "\n"' | sort -fu
```

Then try to find the right GitHub username for each person and copy their name
and username from the GitHub page for the person
(e.g. <https://github.com/martinvonz>).

Get the PR through review and get it merged as usual.

## Create a tag and a GitHub release

1. Go to <https://github.com/jj-vcs/jj/releases> and click "Draft a new release"
2. Click "Choose a tag" and enter "v0.\<number\>.0" (e.g. "v0.26.0") to create a
   new tag
3. Click "Target", then "Recent commits", and select the commit from your merged
   PR
4. Use the name (e.g. "v0.26.0") as "Release title". Paste the changelog entries
   into the message body
5. Check "Create a discussion for this release"
6. Click "Publish release"

## Publish the crates to crates.io

Go to a terminal and create a new clone of the repo [^1]:

```shell
cd $(mktemp -d)
jj git clone https://github.com/jj-vcs/jj
cd jj
jj new v0.<number>.0
```

Publish each crate:

```shell
cargo publish --workspace
```

[^1]: We recommend publishing from a new clone because `cargo publish` will
      archive ignored files if they match the patterns in `[include]`
      ([example](https://github.com/jj-vcs/jj/blob/b95628c398c6c3d11f41bdf53d0aef11f92ee96d/lib/Cargo.toml#L15-L22)),
      so it's a security risk to run it in an existing clone where you may have
      left sensitive content in an ignored file.

---

### Revsets (`docs/revsets.md`)

# Revsets

Jujutsu supports a functional language for selecting a set of revisions.
Expressions in this language are called "revsets" (the idea comes from
[Mercurial](https://www.mercurial-scm.org/repo/hg/help/revsets)). The language
consists of symbols, operators, and functions.

Most `jj` commands accept a revset (or multiple). Many commands, such as
`jj edit <revset>` expect the revset to resolve to a single commit; it is an
error to pass a revset that resolves to more than one commit (or zero commits)
to such commands.

The words "revisions" and "commits" are used interchangeably in this document.

## Hidden revisions

Most revsets search only the [visible commits](glossary.md#visible-commits).
Other commits are only included if you explicitly mention them (e.g. by commit
ID, `<name>@<remote>` symbol, or `at_operation()` function).

If hidden commits are specified, their ancestors also become available to the
search space. They are included in `all()`, `x..`, `~x`, etc., but not in
`..visible_heads()`, etc. For example, `hidden_id | all()` is equivalent to
`hidden_id | ::(hidden_id | visible_heads())`.

## Symbols

The `@` expression refers to the working copy commit in the current workspace.
Use `<workspace name>@` to refer to the working-copy commit in another
workspace. Use `<name>@<remote>` to refer to a remote-tracking tag / bookmark.

A full commit ID refers to a single commit. A unique prefix of the full commit
ID can also be used. It is an error to use a non-unique prefix.

A full change ID refers to a visible commit with that change ID. A unique prefix
of the full change ID can also be used. It is an error to use a non-unique
prefix or [a divergent change ID][divergent-change]. To refer to a
[hidden](#hidden-revisions) commit or [divergent change][divergent-change], a
[change offset][change-offset] can be added using `<change ID>/<offset>` syntax.

Use [single or double quotes][string-literals] to prevent a symbol from being
interpreted as an expression. For example, `"x-"` is the symbol `x-`, not the
parents of symbol `x`. Taking shell quoting into account, you may need to use
something like `jj log -r '"x-"'`.

[divergent-change]: glossary.md#divergent-change
[change-offset]: glossary.md#change-offset
[string-literals]: templates.md#stringliteral-type

### Priority

Jujutsu attempts to resolve a symbol in the following order:

1. Tag name
2. Bookmark name
3. Commit ID or change ID

To override the priority, use the appropriate [revset function](#functions). For
example, to resolve `abc` as a commit ID even if there happens to be a bookmark
by the same name, use `commit_id(abc)`. This is particularly useful in scripts.

## Operators

The following operators are supported. `x` and `y` below can be any revset, not
only symbols.

Operators are listed in order of binding power from strongest to weakest, e.g.
`x | y & z` is interpreted as `x | (y & z)` since `&` has stronger binding power
than `|`. Infix operators of the same binding power are parsed from left to
right, e.g. `x ~ y & z` is interpreted as `(x ~ y) & z` rather than `x ~ (y &
z)`.

As seen above, parentheses can be used to control evaluation order, e.g. `(x &
y) | z` or `x & (y | z)`.



1. * `f(x)`: Function call.

2. * `x-`: Parents of `x`, can be empty.
   * `x+`: Children of `x`, can be empty.

3. * `p:x`: String/date pattern or pattern alias named `p`.

4. * `x::`: Descendants of `x`, including the commits in `x` itself. Equivalent to
     `x::visible_heads()` if no hidden revisions are mentioned.
   * `x..`: Revisions that are not ancestors of `x`. Equivalent to `~::x`, and
     `x..visible_heads()` if no hidden revisions are mentioned.
   * `::x`: Ancestors of `x`, including the commits in `x` itself. Shorthand for
     `root()::x`.
   * `..x`: Ancestors of `x`, including the commits in `x` itself, but excluding
     the root commit. Shorthand for `root()..x`. Equivalent to `::x ~ root()`.
   * `x::y`: Descendants of `x` that are also ancestors of `y`. Equivalent
      to `x:: & ::y`. This is what `git log` calls `--ancestry-path x..y`.
   * `x..y`: Ancestors of `y` that are not also ancestors of `x`. Equivalent to
     `::y ~ ::x`. This is what `git log` calls `x..y` (i.e. the same as we call it).
     Note that this is *not* a "path" between `x` and `y` in the commit graph -- `x`
     and `y` do not need to be related by ancestry.
   * `::`: All visible commits in the repo. Equivalent to `all()`, and
     `root()::visible_heads()` if no hidden revisions are mentioned.
   * `..`: All visible commits in the repo, but excluding the root commit.
     Equivalent to `~root()`, and `root()..visible_heads()` if no hidden revisions
     are mentioned.

5. * `~x`: Revisions that are not in `x`.

6. * `x & y`: Revisions that are in both `x` and `y`.
   * `x ~ y`: Revisions that are in `x` but not in `y`.

7. * `x | y`: Revisions that are in either `x` or `y` (or both).

**Note:** The `..` operator does not distribute over union (`|`) on its left
side. For example, `(A | B)..` is **not** equivalent to `A.. | B..`. The
expression `(A | B)..` means "commits that are not ancestors of A *and* not
ancestors of B", while `A.. | B..` means "commits that are not ancestors of A
*or* not ancestors of B". In fact, `(A | B).. = A.. & B..`. See the examples
below for concrete illustrations.



??? examples

    Given this history:
    ```
    D
    |\
    B C
    |/
    A
    |
    root()
    ```

    **Operator** `x-`

    * `D-` ⇒ `{C,B}`
    * `B-` ⇒ `{A}`
    * `A-` ⇒ `{root()}`
    * `root()-` ⇒ `{}` (empty set)
    * `none()-` ⇒ `{}` (empty set)
    * `(D|A)-` ⇒ `{C,B,root()}`
    * `(C|B)-` ⇒ `{A}`

    **Operator** `x+`

    * `D+` ⇒ `{}` (empty set)
    * `B+` ⇒ `{D}`
    * `A+` ⇒ `{B,C}`
    * `root()+` ⇒ `{A}`
    * `none()+` ⇒ `{}` (empty set)
    * `(C|B)+` ⇒ `{D}`
    * `(B|root())+` ⇒ `{D,A}`

    **Operator** `x::`

    * `D::` ⇒ `{D}`
    * `B::` ⇒ `{D,B}`
    * `A::` ⇒ `{D,C,B,A}`
    * `root()::` ⇒ `{D,C,B,A,root()}`
    * `none()::` ⇒ `{}` (empty set)
    * `(C|B)::` ⇒ `{D,C,B}`

    **Operator** `x..`

    * `D..` ⇒ `{}` (empty set)
    * `B..` ⇒ `{D,C}` (note that, unlike `B::`, this includes `C`)
    * `A..` ⇒ `{D,C,B}`
    * `root()..` ⇒ `{D,C,B,A}`
    * `none()..` ⇒ `{D,C,B,A,root()}`
    * `(C|B)..` ⇒ `{D}`

    **Operator** `::x`

    * `::D` ⇒ `{D,C,B,A,root()}`
    * `::B` ⇒ `{B,A,root()}`
    * `::A` ⇒ `{A,root()}`
    * `::root()` ⇒ `{root()}`
    * `::none()` ⇒ `{}` (empty set)
    * `::(C|B)` ⇒ `{C,B,A,root()}`

    **Operator** `..x`

    * `..D` ⇒ `{D,C,B,A}`
    * `..B` ⇒ `{B,A}`
    * `..A` ⇒ `{A}`
    * `..root()` ⇒ `{}` (empty set)
    * `..none()` ⇒ `{}` (empty set)
    * `..(C|B)` ⇒ `{C,B,A}`

    **Operator** `x::y`

    * `D::D` ⇒ `{D}`
    * `B::D` ⇒ `{D,B}` (note that, unlike `B..D`, this includes `B` and excludes `C`)
    * `B::C` ⇒ `{}` (empty set) (note that, unlike `B..C`, this excludes `C`)
    * `A::D` ⇒ `{D,C,B,A}`
    * `root()::D` ⇒ `{D,C,B,A,root()}`
    * `none()::D` ⇒ `{}` (empty set)
    * `D::B` ⇒ `{}` (empty set)
    * `(C|B)::(C|B)` ⇒ `{C,B}`

    **Operator** `x..y`

    * `D..D` ⇒ `{}` (empty set)
    * `B..D` ⇒ `{D,C}` (note that, unlike `B::D`, this includes `C` and excludes `B`)
    * `B..C` ⇒ `{C}` (note that, unlike `B::C`, this includes `C`)
    * `A..D` ⇒ `{D,C,B}`
    * `root()..D` ⇒ `{D,C,B,A}`
    * `none()..D` ⇒ `{D,C,B,A,root()}`
    * `D..B` ⇒ `{}` (empty set)
    * `(C|B)..(C|B)` ⇒ `{}` (empty set)

    **Non-distributivity of `..` over union (left side)**

    Using the same graph, observe that `(C|B)..` ⇒ `{D}`, but:
    * `C..` ⇒ `{D,B}`
    * `B..` ⇒ `{D,C}`
    * `C.. | B..` ⇒ `{D,C,B}`
    * `C.. & B..` ⇒ `{D}`

    So `(C|B)..` ≠ `C.. | B..`, but `(C|B).. = C.. & B..`. The `..` operator
    converts union to intersection on its left side.

## Functions

You can also specify revisions by using functions. Some functions take other
revsets (expressions) as arguments.

??? note "Function argument syntax"

    In this documentation, optional arguments are indicated with square
    brackets like `[arg]`. Some arguments also have an optional label which can
    be used to specify that argument without specifying all previous arguments.

    For instance, `remote_bookmarks([name_pattern], [[remote=]remote_pattern])`
    indicates that all of the following usages are valid:

    * `remote_bookmarks()`
    * `remote_bookmarks("main")`
    * `remote_bookmarks("main", "origin")`
    * `remote_bookmarks("main", remote="origin")`
    * `remote_bookmarks(remote="origin")`

* `parents(x, [depth])`: `parents(x)` is the same as `x-`.
  `parents(x, depth)` returns the parents of `x` at the given `depth`. For
  instance, `parents(x, 3)` is equivalent to `x---`.

* `children(x, [depth])`: `children(x)` is the same as `x+`.
  `children(x, depth)` returns the children of `x` at the given `depth`. For
  instance, `children(x, 3)` is equivalent to `x+++`.

* `ancestors(x, [depth])`: `ancestors(x)` is the same as `::x`.
  `ancestors(x, depth)` returns the ancestors of `x` limited to the given
  `depth`.

* `descendants(x, [depth])`: `descendants(x)` is the same as `x::`.
  `descendants(x, depth)` returns the descendants of `x` limited to the given
  `depth`.

* `first_parent(x, [depth])`: `first_parent(x)` is similar to `parents(x)`, but
  for merges, it only returns the first parent instead of returning all parents.
  The `depth` argument also works similarly, so `first_parent(x, 2)` is
  equivalent to `first_parent(first_parent(x))`.

* `first_ancestors(x, [depth])`: Similar to `ancestors(x, [depth])`, but only
  traverses the first parent of each commit. In Git, the first parent of a merge
  commit is conventionally the branch into which changes are being merged, so
  `first_ancestors()` can be used to exclude changes made on other branches.

* `reachable(srcs, domain)`: All commits reachable from `srcs`, traversing all
  parent and child edges, such that the entire path is within `domain`. This is
  useful for finding all related commits in a branch or feature without
  traversing outside a defined scope. For example, `reachable(@, mutable())`
  returns the stack of commits you are working on.

* `connected(x)`: Same as `x::x`. Useful when `x` includes several commits.

* `all()`: All visible commits and ancestors of commits explicitly mentioned.

* `none()`: No commits. This function is rarely useful; it is provided for
  completeness.

* `change_id(prefix)`: Commits with the given change ID prefix. If the specified
  change is divergent, this resolves to multiple commits. It is an error to use a
  non-unique prefix. Unmatched prefix isn't an error.

* `commit_id(prefix)`: Commits with the given commit ID prefix. It is an error
  to use a non-unique prefix. Unmatched prefix isn't an error.

* `bookmarks([pattern])`: All local bookmark targets. If `pattern` is specified,
  this selects the bookmarks whose name match the given [string
  pattern](#string-patterns). For example, `bookmarks(*push*)` would match the
  bookmarks `push-123` and `repushed` but not the bookmark `main`. If a bookmark
  is in a conflicted state, all its possible targets are included.

* `remote_bookmarks([name_pattern], [[remote=]remote_pattern])`: All remote
  bookmarks targets across all remotes. If just the `name_pattern` is specified,
  the bookmarks whose names match the given [string pattern](#string-patterns)
  across all remotes are selected. If both `name_pattern` and `remote_pattern`
  are specified, the selection is further restricted to just the remotes whose
  names match `remote_pattern`.

  For example, `remote_bookmarks(*push*, *ri*)` would match the bookmarks
  `push-123@origin` and `repushed@private` but not `push-123@upstream` or
  `main@origin` or `main@upstream`. If a bookmark is in a conflicted state, all
  its possible targets are included.

  Git-tracking bookmarks are excluded by default. Use `remote="git"` or
  `remote="*"` to select bookmarks including `@git` ones.

* `tracked_remote_bookmarks([name_pattern], [[remote=]remote_pattern])`: All
  targets of tracked remote bookmarks. Supports the same optional arguments as
  `remote_bookmarks()`.

* `untracked_remote_bookmarks([name_pattern], [[remote=]remote_pattern])`: All
  targets of untracked remote bookmarks. Supports the same optional arguments as
  `remote_bookmarks()`.

* `tags([pattern])`: All tag targets. If `pattern` is specified, this selects
  the tags whose name match the given [string pattern](#string-patterns). For
  example, `tags(*v1*)` would match the tags `v123` and `rev1` but not the tag
  `v2`. If a tag is in a conflicted state, all its possible targets are
  included.

* `remote_tags([name_pattern], [[remote=]remote_pattern])`: All remote tags
  targets across all remotes. See `remote_bookmarks()` for arguments.

* `tracked_remote_tags([name_pattern], [[remote=]remote_pattern])`: All targets
  of tracked remote tags. Supports the same optional arguments as
  `remote_tags()`.

* `untracked_remote_tags([name_pattern], [[remote=]remote_pattern])`: All
  targets of untracked remote tags. Supports the same optional arguments as
  `remote_tags()`.

* `visible_heads()`: All visible heads (same as `heads(all())` if no hidden
  revisions are mentioned).

* `root()`: The virtual commit that is the oldest ancestor of all other commits.

* `heads(x)`: Commits in `x` that are not ancestors of other commits in `x`.
  Equivalent to `x ~ ::x-`. Note that this is different from
  [Mercurial's](https://repo.mercurial-scm.org/hg/help/revsets) `heads(x)`
  function, which is equivalent to `x ~ x-`.

* `roots(x)`: Commits in `x` that are not descendants of other commits in `x`.
  Equivalent to `x ~ x+::`. Note that this is different from
  [Mercurial's](https://repo.mercurial-scm.org/hg/help/revsets) `roots(x)`
  function, which is equivalent to `x ~ x+`.

* `latest(x, [count])`: Latest `count` commits in `x`, based on committer
  timestamp. The default `count` is 1.

* `fork_point(x)`: The fork point of all commits in `x`. The fork point is the
  common ancestor(s) of all commits in `x` which do not have any descendants
  that are also common ancestors of all commits in `x`. It is equivalent to
  the revset `heads(::x_1 & ::x_2 & ... & ::x_N)`, where `x_{1..N}` are commits
  in `x`. If `x` resolves to a single commit, `fork_point(x)` resolves to `x`.

  `fork_point(x | y)` is what `git merge-base x y` calculates (but unlike `git
  merge-base --fork-point`, the `fork_point()` function does *not* take a
  bookmark's history into account).

* `merge_point(x)`: The merge point of all commits in `x`. Similar to the fork
  point, the merge point is the common descendant(s) of all commits in `x` which
  do not have any ancestors that are also common descendants of all commits in
  `x`. It is equivalent to the revset `roots(x_1:: & x_2:: & ... & x_N::)`,
  where `x_{1..N}` are commits in `x`. If `x` resolves to a single commit,
  `merge_point(x)` resolves to `x`.

* `bisect(x)`: Finds commits in the input set for which about half of the input
  set are descendants. The current implementation deals somewhat poorly with
  non-linear history.

* `exactly(x, count)`: Evaluates `x`, and errors if it is not of exactly size
  `count`. Otherwise, returns `x`. This is useful in particular with `count=1`
  when you want to ensure that some revset expression has exactly one target.

* `merges()`: Merge commits.

* `forks()`: Fork commits, i.e. those with more than 1 child.

* `description(pattern)`: Commits that have a description matching the given
  [string pattern](#string-patterns).

  A non-empty description is usually terminated with newline character. For
  example, `description("")` matches commits without description, and
  `description("foo\n")` matches commits with description `"foo\n"`.

* `subject(pattern)`: Commits that have a subject matching the given [string
  pattern](#string-patterns). A subject is the first line of the description
  (without newline character.)

* `author(pattern)`: Commits with the author's name or email matching the given
  [string pattern](#string-patterns). Equivalent to `author_name(pattern) |
  author_email(pattern)`.

* `author_name(pattern)`: Commits with the author's name matching the given
  [string pattern](#string-patterns).

* `author_email(pattern)`: Commits with the author's email matching the given
  [string pattern](#string-patterns).

* `author_date(pattern)`: Commits with [author dates](glossary.md#author-date)
  matching the specified [date pattern](#date-patterns).

* `mine()`: Commits where the author's email matches the email of the current
  user. Equivalent to `author_email(exact-i:<user-email>)`

* `committer(pattern)`: Commits with the committer's name or email matching the
  given [string pattern](#string-patterns). Equivalent to
  `committer_name(pattern) | committer_email(pattern)`.

* `committer_name(pattern)`: Commits with the committer's name matching the
  given [string pattern](#string-patterns).

* `committer_email(pattern)`: Commits with the committer's email matching the
  given [string pattern](#string-patterns).

* `committer_date(pattern)`: Commits with
  [committer dates](glossary.md#committer-date) matching the specified [date
  pattern](#date-patterns).

* `signed()`: Commits that are cryptographically signed.

* `empty()`: Commits modifying no files. This also includes `merges()` without
  user modifications and `root()`.

* `files(expression)`: Commits modifying paths matching the given [fileset
  expression](filesets.md).

  Paths are relative to the directory `jj` was invoked from. A directory name
  will match all files in that directory and its subdirectories.

  For example, `files(foo)` will match files `foo`, `foo/bar`, `foo/bar/baz`.
  It will *not* match `foobar` or `bar/foo`.

  Some file patterns might need quoting because the `expression` must also be
  parsable as a revset. For example, `.` has to be quoted in `files(".")`.

* `diff_lines(text, [files])`: Commits containing diffs matching the given
  `text` pattern line by line.

  The search paths can be narrowed by the `files` expression. All modified files
  are scanned by default, but it is likely to change in future version to
  respect the command line path arguments.

  For example, `diff_lines("*TODO*", "src")` will search revisions where "TODO"
  is added to or removed from files under "src".

* `diff_lines_added(text, [files])`: like `diff_lines()` above, but matches only
  the "added" side of the diff.

* `diff_lines_removed(text, [files])`: like `diff_lines()` above, but matches
  only the "removed" side of the diff.

* `conflicts()`: Commits that have files in a conflicted state.

* `divergent()`: Commits that are [divergent](glossary.md#divergent-change).

* `present(x)`: Same as `x`, but evaluated to `none()` if any of the commits
  in `x` doesn't exist (e.g. is an unknown bookmark name.)

* `coalesce(revsets...)`: Commits in the first revset in the list of `revsets`
  which does not evaluate to `none()`. If all revsets evaluate to `none()`, then
  the result of `coalesce` will also be `none()`.

* `working_copies()`: The working copy commits across all the workspaces.

* `at_operation(op, x)`: Evaluates `x` at the specified [operation][]. For
  example, `at_operation(@-, visible_heads())` will return all heads which were
  visible at the previous operation.

  Since `at_operation(op, x)` brings all commits that were visible at the
  operation to the search space, `at_operation(op, x) | all()` is equivalent to
  `at_operation(op, x) | ::(at_operation(op, x | visible_heads()) |
  visible_heads())`.

[operation]: glossary.md#operation

??? examples

    Given this history:
    ```
    E
    | D
    |/|
    B C
    |/
    A
    |
    root()
    ```

    **function** `reachable()`

    `reachable(srcs, domain)` finds all commits reachable from `srcs` by
    following parent or child edges, but limited to commits within `domain`.

    * `reachable(E, A..)` ⇒ `{E,D,C,B}` — All commits after A are reachable
    * `reachable(E, B..)` ⇒ `{E}` — E is isolated: its only edge (to B) leaves the domain
    * `reachable(C, B..)` ⇒ `{D,C}` — C and D are connected; the C→A edge leaves the domain
    * `reachable(D, B..)` ⇒ `{D,C}` — Same result: D reaches C, but D→B leaves the domain
    * `reachable(A, A..)` ⇒ `{}` (empty set) — A is not in domain `A..`, so it's ignored

    **function** `connected()`

    * `connected(E|A)` ⇒ `{E,B,A}`
    * `connected(D|A)` ⇒ `{D,C,B,A}`
    * `connected(A)` ⇒ `{A}`

    **function** `heads()`

    * `heads(E|D)` ⇒ `{E,D}`
    * `heads(E|C)` ⇒ `{E,C}`
    * `heads(E|B)` ⇒ `{E}`
    * `heads(E|A)` ⇒ `{E}`
    * `heads(A)` ⇒ `{A}`

    **function** `roots()`

    * `roots(E|D)` ⇒ `{E,D}`
    * `roots(E|C)` ⇒ `{E,C}`
    * `roots(E|B)` ⇒ `{B}`
    * `roots(E|A)` ⇒ `{A}`
    * `roots(A)` ⇒ `{A}`

    **function** `fork_point()`

    * `fork_point(E|D)` ⇒ `{B}`
    * `fork_point(E|C)` ⇒ `{A}`
    * `fork_point(E|B)` ⇒ `{B}`
    * `fork_point(E|A)` ⇒ `{A}`
    * `fork_point(D|C)` ⇒ `{C}`
    * `fork_point(D|B)` ⇒ `{B}`
    * `fork_point(B|C)` ⇒ `{A}`
    * `fork_point(A)` ⇒ `{A}`
    * `fork_point(none())` ⇒ `{}`

## String patterns

Functions that perform string matching support the following pattern syntax (the
quotes are optional).

By default, `"string"` is parsed as a `glob:` pattern.

* `exact:"string"`: Matches strings exactly equal to `string`.
* `glob:"pattern"`: Matches strings with Unix-style shell [wildcard
  `pattern`](https://docs.rs/globset/latest/globset/#syntax).
* `regex:"pattern"`: Matches substrings with [regular
  expression `pattern`](https://docs.rs/regex/latest/regex/#syntax).
* `substring:"string"`: Matches strings that contain `string`.

You can append `-i` after the kind to match case‐insensitively (e.g.
`glob-i:"fix*jpeg*"`).

String patterns can be combined by using logical operators
(e.g. `bookmarks(~glob:"ci/*")`):

* `~x`: Matches everything but `x`.
* `x & y`: Matches both `x` and `y`.
* `x ~ y`: Matches `x` but not `y`.
* `x | y`: Matches either `x` or `y` (or both).

## Date patterns

Functions that perform date matching support the following pattern syntax:

* `after:"string"`: Matches dates exactly at or after the given date.
* `before:"string"`: Matches dates before, but not including, the given date.

Date strings can be specified in several forms, including:

* 2024-02-01
* 2024-02-01T12:00:00
* 2024-02-01T12:00:00-08:00
* 2024-02-01 12:00:00
* 2 days ago
* 5 minutes ago
* yesterday
* yesterday 5pm
* yesterday 10:30
* yesterday 15:30

## Aliases

New symbols, functions, and `<name>:<value>` patterns can be defined in the
config file, by using any combination of the predefined symbols/functions and
other aliases.

Alias functions can be overloaded by the number of parameters. However, builtin
function will be shadowed by name, and can't co-exist with aliases.

For example:

```toml
[revset-aliases]
HEAD = '@-'
'user()' = 'user("me@example.org")'
'user(x)' = 'author(x) | committer(x)'
'grep:x' = 'description(regex:x)'
```

### Alias descriptions

Alias descriptions can be surfaced in shell completions by defining the alias
as a table with `.doc` and `.definition` properties. For example:

```toml
[revset-aliases]
HEAD = { definition = '@-', doc = 'The parent of the working-copy commit' }
```

You can also use the dotted key syntax:

```toml
[revset-aliases]
HEAD.definition = '@-'
HEAD.doc = 'The parent of the working-copy commit'
```

### Built-in Aliases

The following aliases are built-in and used for certain operations. These functions
are defined as aliases in order to allow you to overwrite them as needed.
See [revsets.toml](https://github.com/jj-vcs/jj/blob/main/cli/src/config/revsets.toml)
for a comprehensive list.

* `trunk()`: Resolves to the head commit for the default bookmark of the default
  remote, or the remote named `upstream` or `origin`. This is set at the
  repository level upon initialization of a Jujutsu repository.

  If the default bookmark cannot be resolved during initialization, the default
  global configuration tries the bookmarks `main`, `master`, and `trunk` on the
  `upstream` and `origin` remotes. If more than one potential trunk commit
  exists, the newest one is chosen. If none of the bookmarks exist, the revset
  evaluates to `root()`.

  You can [override](./config.md) this as appropriate. If you do, make sure it
  always resolves to exactly one commit. For example:

  ```toml
  [revset-aliases]
  'trunk()' = 'your-bookmark@your-remote'
  ```

* `builtin_log()`: Resolves to `present(@) |
  ancestors(immutable_heads().., 2) | trunk()`. It is used as the default value
  for `revsets.log`, which is the set of revisions shown by `jj log` if no
  revisions or paths are specified.

* `builtin_immutable_heads()`: Resolves to `trunk() | tags() |
  untracked_remote_bookmarks() | untracked_remote_tags()`. It is used as the
  default definition for `immutable_heads()` below. It is not recommended to
  redefine this alias. Prefer to redefine `immutable_heads()` instead.

* `immutable_heads()`: The heads of the set of immutable commits (not "heads
  that are immutable"). Resolves to `trunk() | tags() |
  untracked_remote_bookmarks() | untracked_remote_tags()` by default. It is
  actually defined as `builtin_immutable_heads()`, and can be overridden as
  required. The full set of immutable commits is `::immutable_heads()` (i.e.,
  `immutable()`). See [here](config.md#set-of-immutable-commits) for details.

* `immutable()`: The set of commits that `jj` treats as immutable. This is
  equivalent to `::(immutable_heads() | root())`. It is not recommended to redefine
  this alias. Note that modifying this will *not* change whether a commit is immutable.
  To do that, edit `immutable_heads()`.

* `mutable()`: The set of commits that `jj` treats as mutable. This is
  equivalent to `~immutable()`. It is not recommended to redefine this alias.
  Note that modifying this will *not* change whether a commit is immutable.
  To do that, edit `immutable_heads()`.

* `visible()`: The set of visible commits. Resolves to `::visible_heads()`.
  This is equal to `all()` unless your revset includes
  [hidden revisions](#hidden-revisions).

* `hidden()`: The set of hidden commits. Resolves to `~visible()`. This is empty
  unless your revset includes [hidden revisions](#hidden-revisions). Note that
  this is *not* [the set of all previously visible
  commits](https://github.com/jj-vcs/jj/issues/2623).

## Examples

Show the parent(s) of the working-copy commit (like `git log -1 HEAD`):

```shell
jj log -r @-
```

Show all ancestors of the working copy (like plain `git log`)

```shell
jj log -r ::@
```

Show commits not on any remote bookmark:

```shell
jj log -r 'remote_bookmarks()..'
```

Show commits not on `origin` (if you have other remotes like `fork`):

```shell
jj log -r 'remote_bookmarks(remote=origin)..'
```

Show the initial commits in the repo (the ones Git calls "root commits"):

```shell
jj log -r 'root()+'
```

Show some important commits (like `git log --simplify-by-decoration`):

```shell
jj log -r 'tags() | bookmarks()'
```

Show local commits leading up to the working copy, as well as descendants of
those commits:

```shell
jj log -r '(remote_bookmarks()..@)::'
```

Show commits authored by "martinvonz" and containing the word "reset" in the
description:

```shell
jj log -r 'author(*martinvonz*) & description(*reset*)'
```

---

### Roadmap (`docs/roadmap.md`)

# Roadmap

This documents some of the goals we have. Many of them are quite independent.

> **Note:** Most people contributing to Jujutsu do so in their spare time, which
>  means that we cannot attach any target dates to any of the goals below.

## Support for copies and renames

We want to support copy tracing in a way that leaves it up to the commit backend
to either record or detect copies. That should let us work with existing Git
repos (Git does not record copies, it detects them on the fly) as well as with
very large repos where detection would be too slow. See
[design doc][copy-design-doc].

## Forge integrations

We would like to make it easier to work with various popular forges by providing
something like `jj github submit` and `jj gitlab submit`. For popular forges, we
might include that support by default in the standard `jj` binary.

## Submodule support

Git submodules are used frequently enough in large Git repos that we will
probably need to [support them][submodules]. There are still big open
questions around UX.

## Better Rust API for UIs

UIs like [gg] currently have to duplicate quite a bit of logic from `jj-cli`. We
need to make this code not specific to the CLI (e.g. return status objects
instead of printing messages) and move it into `jj-lib`.

## RPC API

One problem with writing tools using the Rust API is that they will only work
with the backends they were compiled with. For example, a regular [gg] build
will not work on Google repos because it doesn't have the backends necessary to
load them. We want to provide an RPC API for tools that want to work with an
unknown build of `jj` by having the tool run something like `jj api` to give it
an address to talk to.

In addition to helping with the problem of unknown backends, having an RPC API
should make it easier for tools like VS Code that are not written in Rust. The
RPC API will probably be at a higher abstraction level than the Rust API.

See [design doc][api-design-doc].

## Open-source cloud-based repos (server and daemon process)

Google has an internal Jujutsu server backed by a database. This server allows
commits and repos (operation logs) to be stored in the cloud (i.e. the database).
Working copies can still be stored locally.

In order to reduce latency, there is a local daemon process that caches reads
and writes. It also prefetches objects it thinks the client might ask for
next. It also helps with write latency by optimistically answering write
requests (it therefore needs to know the server's hashing scheme so it can
return the right IDs).

We (the project, not necessarily Google) want to provide a similar experience
for all users. We would therefore like to create a similar server and daemon.
The daemon might be the same process as for the RPC API mentioned above.

## Virtual file system (VFS)

For very large projects and/or large files, it can be expensive to update the
working copy. We want to provide a VFS to help with that. Updating the working
copy to another commit can then be done simply by telling the VFS to use the
other commit as base, without needing to download any large files in the target
commit until the user asks for them via the file system. A VFS can also make it
cheap to snapshot the working copy by keeping track of all changes compared to
the base commit.

Having a VFS can also be very benefial for [`jj run`][jj-run], since we can then
cheaply create temporary working copies for the commands to run in.

## Better support for large files

We have talked about somehow using content-defined chunking (CDC) to reduce
storage and transfer costs for large files. Maybe we will store files in our
future cloud-based server using the same model as [XetHub][xet-storage].

[api-design-doc]: https://docs.google.com/document/d/1rOKvutee5TVYpFhh_UDNZDxfUKyrJ8rjCNpFaNHOHwU/edit?usp=sharing&resourcekey=0-922ApyoAjuXN_uTKqmCqjg
[copy-design-doc]: design/copy-tracking.md
[gg]: https://github.com/gulbanana/gg
[jj-run]: https://github.com/jj-vcs/jj/issues/1869
[submodules]: https://github.com/jj-vcs/jj/issues/494
[xet-storage]: https://web.archive.org/web/20240914200921/https://xethub.com/assets/docs/concepts/xet-storage

---

### Sapling Comparison (`docs/sapling-comparison.md`)

# Comparison with Sapling

## Introduction

This document attempts to describe how jj is different
from [Sapling](https://sapling-scm.com). Sapling is a VCS developed by Meta. It
was announced about 3 years after development started on jj. It is a heavily
modified fork of [Mercurial](https://www.mercurial-scm.org/). Because jj has
copied many ideas from Mercurial, there are many similarities between the two
tools, such as:

* A user-friendly CLI
* A "[revset](revsets.md)" language for selecting revisions
* Good support for working with stacked commits, including tracking "anonymous
  heads" (no "detached HEAD" state like in Git) and `split` commands, and
  automatically rebasing descendant commits when you amend a commit.
* Flexible customization of output using [templates](templates.md)

## Differences

Here is a list of some differences between jj and Sapling.

* **Working copy:** When using Sapling (like most VCSs), the
  user explicitly tells the tool when to create a commit and which files to
  include. When using jj, the working copy
  is [automatically snapshotted by every command](working-copy.md). New files
  are automatically tracked and deleted files are automatically untracked. This
  has several advantages:

  * The working copy is effectively backed up every time you run a command.
  * No commands fail because you have changes in the working copy ("abort: 1
    conflicting file changes: ..."). No need for `sl shelve`.
  * Simpler and more consistent CLI because the working copy is treated like any
    other commit.

* **Conflicts:** Like most VCSs, Sapling requires the user to
  resolve conflicts before committing. jj lets
  you [commit conflicts](conflicts.md). Note that it's a representation of the
  conflict that's committed, not conflict markers (`<<<<<<<` etc.). This also
  has several advantages:

  * Merge conflicts won't prevent you from checking out another commit.
  * You can resolve the conflicts when you feel like it.
  * Rebasing descendants always succeeds. Like jj, Sapling automatically
    rebases, but it will fail if there are conflicts.
  * Merge commits can be rebased correctly (Sapling sometimes fails).
  * You can rebase conflicts and conflict resolutions.

* **Undo:** jj's undo is powered by [the operation log](operation-log.md), which
  records how the repo has changed over time. Sapling has a similar feature
  with its [MetaLog](https://sapling-scm.com/docs/internals/metalog).
  They seem to provide similar functionality, but jj also exposes the log to the
  user via `jj op log`, so you can tell how far back you want to go back.
  Sapling has `sl debugmetalog`, but that seems to show the history of a single
  commit, not the whole repo's history. Thanks to jj snapshotting the working
  copy, it's possible to undo changes to the working copy. For example, if
  you `jj undo` a ` jj commit`, `jj diff` will show the same changes as
  before `jj commit`, but if you `sl undo` a `sl commit`, the working copy will
  be clean.
* **Git interop:** Sapling supports cloning, pushing, and pulling from a remote
  Git repo. jj also does, and it also supports sharing a working copy with a Git
  repo, so you can use `jj` and `git` interchangeably in the same repo.
* **Polish:** Sapling is more polished and feature-complete. Sapling has very
  nice built-in web UI called
  [Interactive Smartlog](https://sapling-scm.com/docs/addons/isl), which lets
  you drag and drop commits to rebase them, among other things.
* **Forge workflow:** Sapling has `sl pr submit --stack`, which lets you
  push a stack of commits as separate GitHub PRs, including setting the base
  branch. It only supports GitHub. jj doesn't have any direct integration with
  GitHub or any other forge. However, it has `jj git push --change` for
  automatically creating branches for specified commits. You have to specify
  each commit you want to create a branch for by using
  `jj git push --change X --change Y ...`, and you have to manually set up any
  base branches in GitHub's UI (or GitLab's or ...). On subsequent pushes, you
  can update all at once by specifying something like `jj git push -r main..@`
  (to push all branches on the current stack of commits from where it forked
  from `main`).

---

### Style Guide (`docs/style_guide.md`)

# Style guide

## Panics

Panics are not allowed, especially in code that may run on a server. Calling
`.unwrap()` is okay if it's guaranteed to be safe by previous checks or
documented invariants. For example, if a function is documented as requiring a
non-empty slice as input, it's fine to call `slice[0]` and panic.

## Markdown

Try to wrap at 80 columns. We don't have a formatter yet.

## Prefer lower-level tests to end-to-end tests

When possible, prefer lower-level tests that don't use the `jj` binary.
End-to-end tests are much slower than similar tests that create a repo using
`jj-lib` (roughly 100x slower). It's also often easier to test edge cases in
lower-level tests.

It can still be useful to add a test case or two to check that the lower-level
functionality is correctly hooked up in the CLI. For example, the end-to-end
tests for `jj log` don't need to test that all kinds of revsets are evaluated
correctly (we have tests in `jj-lib` for that), but they should check that the
`-r` flag is respected.

Use end-to-end tests for testing the CLI commands themselves.

## Logging messages

When messages are logged, such as with `writeln!(ui.status(), "Message...")?;`,
prefer to end the message with a period as if it were a full sentence. For
example:

```rust
// CORRECT:
writeln!(ui.status(), "Rebased {num_rebased} descendant commits.")?;
// INCORRECT:
writeln!(ui.status(), "Rebased {num_rebased} descendant commits")?;
```

There are of course exceptions to this, such as messages that are printing
interpolated values at the end, or need to print some templated value:

```rust
// These are fine:
writeln!(
    ui.status(),
    "Operation left uncommitted because --no-integrate-operation was requested: {}",
    short_operation_hash(self.repo().op_id())
)?;
writeln!(ui.warning_default(), "No matching entries for paths: {paths}")?;
```

Also try not to put periods right after any printed IDs or symbols (such as
commit IDs), since users may double click to copy the value, which may include
the period.

### Plural forms

Do not special-case singular vs. plural forms in messages with a "number
object(s)" format; always use the plural form, regardless of the number being
used:

```rust
// CORRECT:
writeln!(ui.status(), "Rebased {num_rebased} descendant commits.")?;
// INCORRECT:
writeln!(ui.status(), "Rebased {num_rebased} descendant commit(s).")?;
writeln!(
  ui.status(),
  "Rebased {num_rebased} descendant commit{}.",
  if num_rebased == 1 { "" } else { "s" }
)?;
```

If a message is only referring to a single specific object without any numbers
involved, use the singular form.

## Rust

- Prefer Itertools `collect_vec()` and `try_collect()` over annotated
  `.collect::<Vec<...>>()` calls.

    ```rust
    // CORRECT:

    let commits = workspace_helper.parse_union_revsets(&revs)
      .ids()
      .collect_vec();

    let fallible_commits = commits.iter().map(|c| store.get(c))
      .try_collect()?;
    ```

    ```rust
    // INCORRECT: The annotation is used

    let commits: Vec<_> = workspace_helper.parse_union_revsets(&revs)
      .ids()
      .collect();

    let fallible_commits = commits.iter().map(|c| store.get(c))
      .collect::<Vec<_, CommandError>>()?;
    ```

## Documentation comments

### General

In general, try to follow [`rust-lang/rfcs` 1574][rust-lang/rfcs-1574]. The
important points are:

- Use documentation comments (`///`) on types, functions, and fields.

- Use Markdown where appropriate, such as for symbols, code blocks, headings,
  etc.

- The first line of a doc comment should be a single-line short sentence that
  summarizes the element being described. There is no hard rule for "short", but
  try to make it both clear and succinct.
  - Since doc comments are interpreted as Markdown, there must be a blank
    comment line between this first line and the rest of the comment:

    ```rust
    // CORRECT:

    /// Foos the given `bar` and `baz`.
    ///
    /// More description about this very important function.
    fn foo(bar: &Bar, baz: &Baz) { ... }
    ```

    ```rust
    // INCORRECT: This entire comment will be interpreted as the "first line".

    /// Foos the given `bar` and `baz`.
    /// More description about this very important function.
    fn foo(bar: &Bar, baz: &Baz) { ... }
    ```

  - For functions, this summary line should be written in third-person singular
    present indicative form ("Returns ..." instead of "Return ..."). You can
    imagine an implicit "This function..." or the function's name before the
    summary line.

- The rest of the comment after the first line should generally be written in
  complete sentences. But use your own judgment too; for example a sentence that
  says "Returns blah blah." would be fine.

[rust-lang/rfcs-1574]:
  https://github.com/rust-lang/rfcs/blob/master/text/1574-more-api-documentation-conventions.md

### Subcommand comments

Specifically for `jj` subcommands:

- Doc comments for subcommands should describe its corresponding `*Args` struct.
  For example, documentation for `jj new` goes on `struct NewArgs`.

- As above, write an appropriate short first line for the command, but do not
  include a trailing period / full stop. See `jj -h` for examples (`--help` will
  show the full doc comment for each command, while the short form `-h` will
  only show the first line).
  - Use imperative mood, as if you are telling the command what to do (giving it
    a command, if you will).

  - If the subcommand is actually a "collection" of subcommands (such as
    `jj config` or `jj bookmark`), prefer to use the format "Manage ...", such
    as "Manage config options" or "Manage bookmarks". But use your own judgment;
    if it sounds awkward, "Commands for ..." or whatever you come up with would
    probably be fine.

  ```
  CORRECT:
  $ jj new -h
  Create a new, empty change and (by default) edit it in the working copy

  $ jj bookmark create -h
  Create a new bookmark
  ```

  ```
  INCORRECT:
  $ jj new -h
  Creates a new, empty change. The default behavior is to immediately edit this new change, but pass `--no-edit` to avoid this.

  $ jj bookmark create -h
  Creates new bookmarks with the given names and assigns them to the given target revision, which defaults to `@`.
  ```

- The rest of the command documentation should be written in complete sentences.

- If the command requires complex explanation, such as `jj rebase`, prefer to
  fully explain it in the command's doc comment rather than in some other online
  documentation page so that all the relevant info can still be accessed via the
  CLI.
  - For example, `jj rebase --help`.
  - Markdown can be used, but make sure to use at least level 3 headings (`###`
    or more). The CLI reference page uses level 2 headings for each command
    (e.g., `## jj root`), so the Table of Contents might break if any command
    documentation uses level 1 or level 2 headings.

- Default aliases (i.e., those defined in [`misc.toml`][misc.toml]) are not
  automatically shown by `clap`, since it doesn't know about them. Mention these
  at the end of the first line for the command in question in the format
  `[default alias: <alias>]`. For example, the first line of `jj describe` is:

  ```
  Update the change description or other metadata [default alias: desc]
  ```

- Very loose guidance, but if you are adding a new command, prefer single
  "action" words (e.g., verbs) over compound words or portmanteaus. It can be a
  good idea to add a new subcommand instead. Please ask for feedback on names if
  you are struggling to come up with something or are unsure about a name.

- If you are unsure how your comment will look after being processed, run the
  `test_generate_markdown_docs_in_docs_dir` test to generate
  `cli-reference@.md.snap`.

[misc.toml]: https://github.com/jj-vcs/jj/blob/main/cli/src/config/misc.toml

### Command argument / option comments

Specifically for `jj` command arguments / options:

- As above, write an appropriate short first line for the arg, but do not
  include a trailing period / full stop. See `jj <command> -h` for examples
  (`--help` will show the full doc comment for each arg, while the short form
  `-h` will only show the first line).
  - Since arguments can be things or actions or whatever, there is no hard rule
    for the form of the first line. If it is a noun, such as `-R`/`--repository`
    or `--config`, a good first line could be the answer to the question "What
    is `<arg>`?" If it is a verb, such as `--quiet`, try to stick with
    imperative mood (as if you're telling the command how to behave if this flag
    is present). Use your own judgment for what might be clearest for users.

  - If the argument can be repeated, append "(can be repeated)" to the end of
    the first line.

  - If the argument can be repeated, prefer to make the argument name singular
    instead of plural, but speak about the "things" in the plural in the
    documentation. This makes the experience of specifying the values on the
    command line simpler and more consistent.
    - Note that the name of the struct field can be different from the name of
      the argument according to `clap`:
      ```rust
      #[arg(long = "flag")]
      my_values: MyType,
      ```
      This section is mostly discussing the user-facing argument / option names;
      how you name variables is up to you.

  ```
  CORRECT / PREFERRED:
  --ignore-working-copy  Don't snapshot the working copy, and don't update it
  --quiet                Silence non-primary command output
  --no-pager             Disable the pager
  --config <NAME=VALUE>  Additional configuration options (can be repeated)
  ```

  ```
  INCORRECT / LESS PREFERRED:
  --ignore-working-copy   No snapshotting (so working copy is ignored).
  --quiet                 Provide this option to skip non-primary command output.
  --no-pager              Disables the pager.
  --configs <NAME=VALUE>  Override some configuration options.
  ```

- Short options and visible aliases are automatically shown by `clap`, so no
  need to mention them explicitly (for simple cases). However, it may be good to
  mention conflicting arguments / options or how options interact with each
  other if applicable.

- If an argument has a default value, mention what it is, probably in the full
  description (i.e., not the first line).
  - In cases where this default comes from a configurable setting, such as
    `revsets.log` for `--revisions` in `jj log`, mention the setting. Prefer not
    to also spell out the default value of the setting, since that introduces
    toil and potential inconsistency if the default ever changes in the future.

- If you are unsure how your comment will look after being processed, run the
  `test_generate_markdown_docs_in_docs_dir` test to generate
  `cli-reference@.md.snap`.

### Fileset / revset / templating language functions

(Note: In the templating language, this guidance also applies to all type
methods.)

#### Function signatures

- Function signatures are written similar to Rust:

  ```
  function_name(arg1: Type1, arg2: Type2) -> ReturnType
  ```

  (Note that types are only applicable to the templating language and can be
  omitted from the fileset and revset language documentation.)

- For optional parameters, put square brackets around the `arg: Type` part;
  importantly, do not put the brackets around any surrounding commas.

  ```
  CORRECT:
  parents(x, [depth])
  if(condition: Boolean, then: Template, [else: Template]) -> Template
  ```

  ```
  INCORRECT:
  parents(x[, depth])
  if(condition: Boolean, then: Template[, else]) -> Template
  ```

- For named parameters (rare), put the parameter name in the format `[name=]`
  before the `arg: Type` part, with no spaces. Note that this should also be
  included in the square brackets indicating that the parameter is optional.
  `name` and `arg` may be the same, but they don't have to be.

  (Note that currently, named parameters must be optional, since they are
  intended as a way for users to skip previous optional parameters and provide a
  later one instead. However, this may change in the future, especially if named
  parameters make their way to the templating language.)

  ```
  CORRECT:
  remote_bookmarks([name_pattern], [[remote=]remote_pattern])
  ```

  ```
  INCORRECT:
  remote_bookmarks([name_pattern], [remote] = [remote_pattern])
  remote_bookmarks([name_pattern], [remote=remote_pattern])
  ```

- For variadic parameters, add an ellipsis (`...`) after the `arg: Type` part.

  ```
  CORRECT:
  coalesce(revsets...)
  concat(content: Template...) -> Template
  ```

  ```
  INCORRECT:
  coalesce(...revsets)
  concat(*content: Template) -> Template
  ```

#### Descriptions

- For the first sentence:
  - In the fileset language, "functions" act as "filters for files". Follow the
    general Rust style of writing the first sentence in third-person singular
    present indicative form.

  - In the revset language, "functions" evaluate to "a set of revisions".
    Writing a "Returns" prefix for every function is redundant and unnecessary;
    instead, write the first sentence as if there is an implicit "The return
    value is..." or "The return values are..." in front of the first sentence.

  - In the templating language, "functions" will "accept inputs, do things, and
    return an output". Follow the general Rust style of writing the first
    sentence in third-person singular present indicative form.

  ```
  CORRECT:
  all(): Matches all files.

  root(): The virtual commit that is the oldest ancestor of all other commits.
  none(): No commits.
  none(): Nothing.

  json(value: Serialize) -> String: Serializes `value` in JSON format.
  ```

  ```
  INCORRECT:
  all(): All files.

  root(): Returns the virtual commit that is the oldest ancestor of all other commits.
  none(): Returns nothing.

  json(value: Serialize) -> String: Serialize `value` in JSON format.
  json(value: Serialize) -> String: The `value` in JSON format.
  ```

- All following sentences should be written as complete sentences.

- We tend to write the entire description as a single paragraph following the
  function signature, but add line breaks for any examples of usages.

- Use `backticks` to reference arguments of the function instead of simply
  saying "the content" or "the width".

- If a parameter's purpose is obvious, it does not need to be explicitly
  mentioned in the description, but try to make sure it is clear what all
  parameters do.

- Mention default values for optional parameters, or what happens if not given.

- Call out edge cases if applicable.

- For type methods in the templating language, try to document every method,
  even if obvious. This will give you the chance to mention any edge cases,
  default behaviors, or differences from other methods.

## TOML

Try to wrap lines and comments to 80 columns. We don't have a formatter yet.

For TOML that gets compiled into the binary (in `cli/src/config`):

- For `[revsets]` and `[templates]`, prefer to delegate to a function alias
  (named `builtin_...`) if the definition is a little longer or more complex.
  This allows users to reference the existing default in their own aliases; this
  is not possible with the values from the `[revsets]` or `[templates]` tables
  directly.

- For `[revset-aliases]`, do not define any non-function aliases, since they may
  conflict with user symbols such as bookmarks or tags. Historically we have not
  added many default revset aliases, so there isn't much else to say.

- For `[template-aliases]`, all non-function aliases (that are not 'constants')
  must be prefixed with `builtin_`, and they should preferably have a
  corresponding function overload that the non-function alias calls with `self`.
  Non-function aliases are provided for CLI convenience since they don't require
  shell quoting, so only add one if it makes sense to (for example, if it is a
  complete value for a `--template` flag).

- For `[template-aliases]`, prefer to name functions with the prefix `format_`.

- For `[colors]`, prefer to define colors for labels rather than for the color
  or style. That is, do not add a `colors.red = "red"` entry, since this makes
  it harder for users to customize a specific label in a specific output.
  Instead, make sure labels reflect their context or purpose, such as `prefix`,
  `author`, `conflict`, etc.

- For `[colors]`, avoid the `"bright yellow"` color as a default; it is
  difficult to read on xterm's default theme. See
  [#528](https://github.com/jj-vcs/jj/issues/528).

---

### Templates (`docs/templates.md`)

# Templates

Jujutsu supports a functional language to customize output of commands.
The language consists of literals, keywords, operators, functions, and
methods.

A couple of `jj` commands accept a template via `-T`/`--template` option.

## Keywords

Keywords represent objects of different types; the types are described in
the following sections. The top-level object for a template can always be
referenced as `self`. Its concrete type depends on the command (and is
documented on each command's `--template` help).

In any template, all 0-argument methods of that top-level `self` type are
available as keywords. For example:

* In `jj log`, `self` is [the `Commit` type](#commit-type), so `commit_id` is
  equivalent to `self.commit_id()`.
* In `jj op log`, `self` is [the `Operation` type](#operation-type), so
  `current_operation` is equivalent to `self.current_operation()`.
* In `jj file list`, `self` is [the `TreeEntry` type](#treeentry-type), so
  `path` is equivalent to `self.path()`.

## Operators

The following operators are supported. `x` and `y` below can be any expression.

Operators are listed in order of binding power from strongest to weakest, e.g.
`x ++ y * z` is interpreted as `x ++ (y * z)` since `*` has stronger binding
power than `++`. Infix operators of the same binding power are parsed from left
to right, e.g. `x * y / z` is interpreted as `(x * y) / z`.

As seen above, parentheses can be used to control evaluation order, e.g. `(x +
y) * z`.

1. * `x.f()`: Method call.
   * `f(x)`: Function call.

2. * `-x`: Negate integer value.
   * `!x`: Logical not.

3. * `p:x`: String pattern or pattern alias named `p`.

4. * `x * y`, `x / y`, `x % y`: Multiplication / division / remainder. Operands must
     be `Integer`s.

5. * `x + y`, `x - y`: Addition / subtraction. Operands must be `Integer`s.

6. * `x >= y`, `x > y`, `x <= y`, `x < y`: Greater than or equal / greater than /
     lesser than or equal / lesser than. Operands must be `Integer`s.

7. * `x == y`, `x != y`: Equal / not equal. Operands must be either `Boolean`,
     `ByteString`, `Integer`, or `String`.

8. * `x && y`: Logical and, short-circuiting.

9. * `x || y`: Logical or, short-circuiting.

10. * `x ++ y`: Concatenate `x` and `y` templates.

## Global functions

The following functions are defined.

* `fill(width: Integer, content: Template, [break_words: bool]) -> Template`:
  Fill lines at the given `width`. If `break_words` is `true`, any words longer
  than `width` will be split to fit in `width` characters.
* `indent(prefix: Template, content: Template) -> Template`: Indent
  non-empty lines by the given `prefix`.
* `pad_start(width: Integer, content: Template, [fill_char: Template])`: Pad (or
  right-justify) content by adding leading fill characters. The `content`
  shouldn't have newline character.
* `pad_end(width: Integer, content: Template, [fill_char: Template])`: Pad (or
  left-justify) content by adding trailing fill characters. The `content`
  shouldn't have newline character.
* `pad_centered(width: Integer, content: Template, [fill_char: Template])`: Pad
  content by adding both leading and trailing fill characters. If an odd number
  of fill characters are needed, the trailing fill will be one longer than the
  leading fill. The `content` shouldn't have newline characters.
* `truncate_start(width: Integer, content: Template, [ellipsis: Template])`:
  Truncate `content` by removing leading characters. The `content` shouldn't
  have newline character. If `ellipsis` is provided and `content` was truncated,
  prepend the `ellipsis` to the result.
* `truncate_end(width: Integer, content: Template, [ellipsis: Template])`:
  Truncate `content` by removing trailing characters. The `content` shouldn't
  have newline character. If `ellipsis` is provided and `content` was truncated,
  append the `ellipsis` to the result.
* `hash(content: Stringify) -> String`:
  Hash the input and return a hexadecimal string representation of the digest.
* `label(label: Stringify, content: Template) -> Template`: Apply a custom
  [color label](#color-labels) to the content. The `label` is evaluated as a
  space-separated string.
* `hyperlink(url: Stringify, text: Template, [fallback: Template]) -> Template`:
  Render `text` as a hyperlink to `url` using [OSC 8 escape sequences](https://github.com/Alhadis/OSC8-Adoption)
  when outputting with color enabled. Otherwise, renders `fallback` instead,
  which defaults to `text`. Use `--color=always` to force hyperlinks when piping
  output to a terminal emulator that supports OSC 8.
* `raw_escape_sequence(content: Template) -> Template`: Preserves any escape
  sequences in `content` (i.e., bypasses sanitization) and strips labels.
  Note: This function is intended for escape sequences and as such, its output
  is expected to be invisible / of no display width. Outputting content with
  nonzero display width may break wrapping, indentation etc.
* `replace(pattern: StringPattern, content: Template, replacement: |RegexCaptures| -> Template) -> Template`:
  Replace every match of `pattern` in `content` by formatting the `replacement`
  lambda for each match.

  This is similar to [`String.replace()`](#string-type), but operates on
  `Template` content and preserves formatting of the parts of `content` that are
  not replaced. The replacement itself is newly formatted template content.

  The lambda argument is a [`RegexCaptures`](#regexcaptures-type) value for the
  current match. Use `.get(index)` to access capture groups by index, `.name(name)`
  to access named capture groups, and `.len()` to get the number of captures
  including capture group 0 for the full match.
* `stringify(content: Stringify) -> String`: Format `content` to string. This
  effectively removes color labels.
* `json(value: Serialize) -> String`: Serialize `value` in JSON format. To
  serialize multiple values as a JSON object, use `json({key => value, ..})`.
  For example, `json({"commit_id" => commit_id, "tags" => tags})`.
* `if(condition: Boolean, then: Any, [else: Any]) -> Any`:
  Conditionally evaluates to `then` / `else` content.
* `try(expr: Any, fallback: Any...) -> Any`: Evaluates each expression in order
  and returns the first successful result. Suppresses preceding runtime errors,
  but lets parsing errors (such as type errors) pass through.
* `coalesce(content: Template...) -> Template`: Returns the first **non-empty**
  content.
* `concat(content: Template...) -> Template`:
  Same as `content_1 ++ ... ++ content_n`.
* `join(separator: Template, content: Template...) -> Template`: Insert
  `separator` between `content`s.
* `separate(separator: Template, content: Template...) -> Template`: Insert
  `separator` between **non-empty** `content`s.
* `surround(prefix: Template, suffix: Template, content: Template) -> Template`:
  Surround **non-empty** content with texts such as parentheses.
* `config(name: Stringify) -> Option<ConfigValue>`: Look up configuration
   value by `name`.
* `revset(revset: Stringify) -> List<Commit>`: Evaluates [a revset](revsets.md).

  The revset is evaluated once during template parsing if its value evaluates to
  a constant string. Queries built from values that aren't yet known during
  parsing, for example `self.change_id()`, are evaluated per call at runtime.
* `git_web_url([remote: String]) -> String`: Best-effort conversion of a git
  remote URL to an HTTPS web URL. Defaults to the "origin" remote. Returns an
  empty string on failure. SSH host alias resolution is currently unsupported.

## Types

### `AnnotationLine` type

_Conversion: `Boolean`: no, `Serialize`: no, `Template`: no_

The following methods are defined.

* `.commit() -> Commit`: Commit responsible for changing the relevant line.
* `.content() -> ByteString`: Line content including newline character.
* `.line_number() -> Integer`: 1-based line number.
* `.original_line_number() -> Integer`: 1-based line number in the original commit.
* `.first_line_in_hunk() -> Boolean`: False when the directly preceding line
  references the same commit.

### `Any` type

_Conversion: `Boolean`: no, `Serialize`: maybe, `Template`: maybe_

All types can be implicitly converted to `Any`. No methods are defined.

### `AnyList` type

_Conversion: `Boolean`: no, `Serialize`: maybe, `Template`: maybe_

The following methods are defined.

* `.join(separator: Template) -> Template`: Concatenate elements with
  the given `separator`.

### `Boolean` type

_Conversion: `Boolean`: yes, `Serialize`: yes, `Template`: yes_

No methods are defined. Can be constructed with `false` or `true` literal.

### `ByteString` type

_Conversion: `Boolean`: yes, `Serialize`: yes, `Template`: yes_

A byte string, whose encoding is considered ASCII-compatible (e.g. UTF-8), but
isn't guaranteed. This can be implicitly converted to `Boolean`. The following
methods are defined.

* `.len() -> Integer`: Length in bytes.
* `.contains(needle: ByteStringify) -> Boolean`: Whether the string contains the
  provided stringifiable value as a substring.
* `.match(needle: StringPattern) -> ByteString`: Extract the first matching part
  of the string for the given pattern.

  An empty string is returned if there is no match.
* `.starts_with(needle: ByteStringify) -> Boolean`: Whether `needle` is a
  prefix of the string.
* `.ends_with(needle: ByteStringify) -> Boolean`: Whether `needle` is a suffix
  of the string.
* `.remove_prefix(needle: ByteStringify) -> ByteString`: Remove the passed
  prefix, if present.
* `.remove_suffix(needle: ByteStringify) -> ByteString`: Remove the passed
  suffix, if present.
* `.trim() -> ByteString`: Remove leading and trailing ASCII whitespace.
* `.trim_start() -> ByteString`: Remove leading ASCII whitespace.
* `.trim_end() -> ByteString`: Remove trailing ASCII whitespace.
* `.substr(start: Integer, [end: Integer]) -> ByteString`: Extract substring.
  Indices are 0-based and `end` is exclusive. Negative values count from the end
  of the string, with `-1` being the last byte. If `end` is not given, returns
  from `start` to the end of the string.
* `.first_line() -> ByteString`
* `.lines() -> List<ByteString>`: Split into lines excluding newline characters.
* `.split(separator: StringPattern, [limit: Integer]) -> List<ByteString>`:
  Split into substrings by the given `separator` pattern. If `limit` is
  specified, it determines the maximum number of elements in the result, with
  the remainder of the string returned as the final element. A `limit` of 0
  returns an empty list.
* `.replace(pattern: StringPattern, replacement: ByteStringify, [limit:
  Integer]) -> ByteString`: Replace occurrences of the given `pattern` with the
  `replacement` string.

  By default, all occurrences are replaced. If `limit` is specified, at most
  that many occurrences are replaced.

  Supports capture groups in patterns using `$0` (entire match), `$1`, `$2` etc.
* `.upper() -> ByteString`: Map each ASCII character to upper case.
* `.lower() -> ByteString`: Map each ASCII character to lower case.

### `ByteStringify` type

An expression that can be converted to a `ByteString`.

A `String` can be converted to a `ByteString` losslessly. Any types that can be
converted to `Template` can also be `ByteStringify`. Unlike `Template`, color
labels are stripped.

### `ChangeId` type

_Conversion: `Boolean`: no, `Serialize`: yes, `Template`: yes_

The following methods are defined.

* `.normal_hex() -> String`: Normal hex representation (0-9a-f) instead of the
  canonical "reversed" (z-k) representation.
* `.short([len: Integer]) -> String`
* `.shortest([min_len: Integer]) -> ShortestIdPrefix`: Shortest unique prefix.

### `Commit` type

_Conversion: `Boolean`: no, `Serialize`: yes, `Template`: no_

This type cannot be printed. The following methods are defined.

* `.description() -> String`: Usually ends with a trailing `\n`, if non-blank.
* `.trailers() -> List<Trailer>`: The trailers at the end of the commit
  description that are formatted as `<key>: <value>`. These are returned in the
  same order as they appear in the description, and there may be multiple
  `Trailer`s with the same key.
* `.change_id() -> ChangeId`
* `.commit_id() -> CommitId`
* `.parents() -> List<Commit>`
* `.author() -> Signature`
* `.committer() -> Signature`
* `.signature() -> Option<CryptographicSignature>`: Cryptographic signature if
  the commit was signed.
* `.mine() -> Boolean`: Commits where the author's email matches the email of
  the current user.
* `.working_copies() -> List<WorkspaceRef>`: For multi-workspace repositories,
  returns a list of workspace references for each workspace whose working-copy
  commit matches the current commit.
* `.current_working_copy() -> Boolean`: True for the working-copy commit of the
  current workspace.
* `.bookmarks() -> List<CommitRef>`: Local and remote bookmarks pointing to the
  commit. A tracked remote bookmark will be included only if its target is
  different from the local one.
* `.local_bookmarks() -> List<CommitRef>`: All local bookmarks pointing to the
  commit.
* `.remote_bookmarks() -> List<CommitRef>`: All remote bookmarks pointing to the
  commit.
* `.tags() -> List<CommitRef>`: Local and remote tags pointing to the commit. A
  tracked remote tag will be included only if its target is different from the
  local one.
* `.local_tags() -> List<CommitRef>`: All local tags pointing to the commit.
* `.remote_tags() -> List<CommitRef>`: All remote tags pointing to the commit.
* `.divergent() -> Boolean`: True if the commit's change ID corresponds to multiple
  visible commits.
* `.hidden() -> Boolean`: True if the commit is not visible (a.k.a. abandoned).
* `.change_offset() -> Option<Integer>`: The [change offset](glossary.md#change-offset)
  of this commit. May not be available for some commits.
* `.immutable() -> Boolean`: True if the commit is included in [the set of
  immutable commits](config.md#set-of-immutable-commits).
* `.contained_in(revset: Stringify) -> Boolean`: True if the commit is included in
  [the provided revset](revsets.md).
* `.conflict() -> Boolean`: True if the commit contains merge conflicts.
* `.empty() -> Boolean`: True if the commit modifies no files.
* `.diff([files: StringLiteral]) -> TreeDiff`: Changes from the parents within [the
  `files` expression](filesets.md). All files are compared by default, but it is
  likely to change in future version to respect the command line path arguments.
* `.files([files: StringLiteral]) -> List<TreeEntry>`: Files that exist in this commit,
  matching [the `files` expression](filesets.md). Use `.diff().files()` to list
  changed files.
* `.conflicted_files() -> List<TreeEntry>`: Conflicted files in this commit.
* `.root() -> Boolean`: True if the commit is the root commit.

### `CommitEvolutionEntry` type

_Conversion: `Boolean`: no, `Serialize`: yes, `Template`: no_

This type cannot be printed. The following methods are defined.

* `.commit() -> Commit`: New commit.
* `.operation() -> Operation`: Operation where the commit was created or
  rewritten.
* `.predecessors() -> List<Commit>`: Predecessor commits of this entry.
* `.inter_diff([files: StringLiteral]) -> TreeDiff`: Changes between this commit and its
  predecessor version(s), rebased onto the parents of this commit to avoid unrelated
  changes (similar to `jj evolog -p`).

### `CommitId` type

_Conversion: `Boolean`: no, `Serialize`: yes, `Template`: yes_

The following methods are defined.

* `.short([len: Integer]) -> String`
* `.shortest([min_len: Integer]) -> ShortestIdPrefix`: Shortest unique prefix.

### `CommitRef` type

_Conversion: `Boolean`: no, `Serialize`: yes, `Template`: yes_

The following methods are defined.

* `.name() -> RefSymbol`: Local bookmark or tag name.
* `.remote() -> Option<RefSymbol>`: Remote name if this is a remote ref.
* `.present() -> Boolean`: True if the ref points to any commit.
* `.conflict() -> Boolean`: True if [the bookmark or tag is
  conflicted](bookmarks.md#conflicts).
* `.normal_target() -> Option<Commit>`: Target commit if the ref is not
  conflicted and points to a commit.
* `.removed_targets() -> List<Commit>`: Old target commits if conflicted.
* `.added_targets() -> List<Commit>`: New target commits. The list usually
  contains one "normal" target.
* `.tracked() -> Boolean`: True if the ref is tracked by a local ref. The local
  ref might have been deleted (but not pushed yet.)
* `.tracking_present() -> Boolean`: True if the ref is tracked by a local ref,
    and if the local ref points to any commit.
* `.tracking_ahead_count() -> SizeHint`: Number of commits ahead of the tracking
  local ref.
* `.tracking_behind_count() -> SizeHint`: Number of commits behind of the
  tracking local ref.
* `.synced() -> Boolean`: For a local bookmark, true if synced with all tracked
  remotes. For a remote bookmark, true if synced with the tracking local
  bookmark.

### `ConfigValue` type

_Conversion: `Boolean`: no, `Serialize`: yes, `Template`: yes_

This type can be printed in TOML syntax. The following methods are defined.

* `.as_boolean() -> Boolean`: Extract boolean.
* `.as_integer() -> Integer`: Extract integer.
* `.as_string() -> String`: Extract string. This does not convert non-string
  value (e.g. integer) to string.
* `.as_string_list() -> List<String>`: Extract list of strings.

### `CryptographicSignature` type

_Conversion: `Boolean`: no, `Serialize`: no, `Template`: no_

The following methods are defined.

* `.status() -> String`: The signature's status (`"good"`, `"bad"`, `"unknown"`,
  `"invalid"`).
* `.key() -> String`: The signature's key id representation (for GPG and SSH,
  this is the public key fingerprint).
* `.display() -> String`: The signature's display string (for GPG, this is the
  formatted primary user ID; for SSH, this is the principal).

!!! warning

    Calling any of `.status()`, `.key()`, or `.display()` is slow, as it incurs
    the performance cost of verifying the signature (for example shelling out
    to `gpg` or `ssh-keygen`). Though consecutive calls will be faster, because
    the backend caches the verification result.

!!! info

    As opposed to calling any of `.status()`, `.key()`, or `.display()`,
    checking for signature presence through boolean coercion is fast:
    ```
    if(commit.signature(), "commit has a signature", "commit is unsigned")
    ```

### `DiffStatEntry` type

_Conversion: `Boolean`: no, `Serialize`: no, `Template`: no_

This type holds the diff stats per file. The following methods are defined.

* `.bytes_delta() -> Integer`: The difference in size of the file, in bytes.
* `.lines_added() -> Integer`: Number of lines added.
* `.lines_removed() -> Integer`: Number of lines deleted.
* `.path() -> RepoPath`: Path to the entry. If the entry is a copy / rename, this
  points to the target (or right) entry.
* `.display_diff_path() -> String`: Format path for display, taking into account copy / rename information.
* `.status() -> String`: One of `"modified"`, `"added"`, `"removed"`, `"copied"`, or `"renamed"`.
* `.status_char() -> String`: One of `"M"` (modified), `"A"` (added), `"D"` (removed),
  `"C"` (copied), or `"R"` (renamed).

### `DiffStats` type

_Conversion: `Boolean`: no, `Serialize`: no, `Template`: yes_

This type can be printed as a histogram of the changes. The following methods
are defined.

* `.files() -> List<DiffStatEntry>`: Per-file stats for changed files.
* `.total_added() -> Integer`: Total number of insertions.
* `.total_removed() -> Integer`: Total number of deletions.

### `Email` type

_Conversion: `Boolean`: yes, `Serialize`: yes, `Template`: yes_

The email field of a signature may or may not look like an email address. It may
be empty, may not contain the symbol `@`, and could in principle contain
multiple `@`s.

The following methods are defined.

* `.local() -> String`: the part of the email before the first `@`, usually the
  username.
* `.domain() -> String`: the part of the email after the first `@` or the empty
  string.

### `FsPath` type

_Conversion: `Boolean`: no, `Serialize`: yes, `Template`: yes_

A filesystem path. Paths can contain bytes that are not valid UTF-8. Rendering
and path formatting methods preserve those bytes. Serialization with `json()`
follows Rust's `Path` serialization, which can reject non-UTF-8 paths.

The following methods are defined.

* `.absolute() -> FsPath`: Return an absolute filesystem path.
* `.relative() -> FsPath`: Return the path relative to the current working
  directory.

### `Integer` type

_Conversion: `Boolean`: no, `Serialize`: yes, `Template`: yes_

No methods are defined.

### `List` type

_Conversion: `Boolean`: yes, `Serialize`: maybe, `Template`: maybe_

A list can be implicitly converted to `Boolean`. The following methods are
defined.

* `.len() -> Integer`: Number of elements in the list.
* `.join(separator: Template) -> Template`: Concatenate elements with
  the given `separator`.
* `.filter(|item| expression) -> List`: Filter list elements by predicate
  `expression`. Example: `description.lines().filter(|s| s.contains("#"))`
* `.map(|item| expression) -> AnyList`: Apply template `expression`
  to each element. Example: `parents.map(|c| c.commit_id().short())`
* `.any(|item| expression) -> Boolean`: Returns true if any element satisfies
  the predicate `expression`. Example: `parents.any(|c| c.description().contains("fix"))`
* `.all(|item| expression) -> Boolean`: Returns true if all elements satisfy
  the predicate `expression`. Example: `parents.all(|c| c.mine())`
* `.first() -> Option<T>`: Returns the first element.
* `.last() -> Option<T>`: Returns the last element.
* `.get(index: Integer) -> Option<T>`: Returns the element at `index` (0-based).
* `.reverse() -> List`: Returns the list in reverse order.
* `.skip(count: Integer) -> List`: Skips the first `count` elements and
  returns the rest.
* `.take(count: Integer) -> List`: Returns only the first `count` elements.

### `List<Trailer>` type

The following methods are defined. See also the `List` type.

* `.contains_key(key: Stringify) -> Boolean`: True if the commit description
  contains at least one trailer with the key `key`.

### `Operation` type

_Conversion: `Boolean`: no, `Serialize`: yes, `Template`: no_

This type cannot be printed. The following methods are defined.

* `.current_operation() -> Boolean`
* `.description() -> String`
* `.id() -> OperationId`
* `.attributes() -> String`
* `.time() -> TimestampRange`
* `.user() -> String`
* `.snapshot() -> Boolean`: True if the operation is a snapshot operation.
* `.workspace_name() -> String`: The name of the workspace the operation was
  created from. An empty string if the operation wasn't created from a
  workspace.
* `.root() -> Boolean`: True if the operation is the root operation.
* `.parents() -> List<Operation>`

### `OperationId` type

_Conversion: `Boolean`: no, `Serialize`: yes, `Template`: yes_

The following methods are defined.

* `.short([len: Integer]) -> String`

### `Option` type

_Conversion: `Boolean`: yes, `Serialize`: maybe, `Template`: maybe_

An option can be implicitly converted to `Boolean` denoting whether the
contained value is set. If set, all methods of the contained value can be
invoked. If not set, an error will be reported inline on method call.

On comparison between two optional values or optional and non-optional values,
unset value is not an error. Unset value is considered less than any set values.

Types convertible to `Boolean` (e.g. `String` and `List`) lack support for
`Option` because the truthy conversion of an unset value would conflict with
it. Instead, unset values of these types are converted to their empty
equivalents.

### `RefSymbol` type

_Conversion: `Boolean`: no, `Serialize`: yes, `Template`: yes_

[A `String` type](#string-type), but is formatted as revset symbol by quoting
and escaping if necessary. Unlike strings, this cannot be implicitly converted
to `Boolean`.

### `RegexCaptures` type

_Conversion: `Boolean`: no, `Serialize`: no, `Template`: no_

This type is passed to the replacement lambda of the global
[`replace()`](#global-functions) function.

The following methods are defined.

* `.len() -> Integer`: Number of capture groups, including capture group 0 for
  the full match.
* `.get(index: Integer) -> ByteString`: Returns the capture group at `index`.
  Capture group 0 is the full match. Errors if the index is out of bounds.
* `.name(name: Stringify) -> ByteString`: Returns the named capture group
  `name`. Errors if there is no such named capture group.

### `RepoPath` type

_Conversion: `Boolean`: no, `Serialize`: yes, `Template`: yes_

A slash-separated path relative to the repository root. The following methods
are defined.

* `.absolute() -> FsPath`: Absolute filesystem path.
* `.display() -> String`: Format path for display. The formatted path uses
  platform-native separator, and is relative to the current working directory.
* `.parent() -> Option<RepoPath>`: Parent directory path.

### `Serialize` type

An expression that can be serialized in machine-readable format such as JSON.

!!! note

    Field names and value types in the serialized output are usually stable
    across jj versions, but the backward compatibility isn't guaranteed. If the
    underlying data model is updated, the serialized output may change.

### `ShortestIdPrefix` type

_Conversion: `Boolean`: no, `Serialize`: yes, `Template`: yes_

The following methods are defined.

* `.prefix() -> String`
* `.rest() -> String`
* `.upper() -> ShortestIdPrefix`
* `.lower() -> ShortestIdPrefix`

### `Signature` type

_Conversion: `Boolean`: no, `Serialize`: yes, `Template`: yes_

The following methods are defined.

* `.name() -> String`
* `.email() -> Email`
* `.timestamp() -> Timestamp`

### `SizeHint` type

_Conversion: `Boolean`: no, `Serialize`: yes, `Template`: no_

This type cannot be printed. The following methods are defined.

* `.lower() -> Integer`: Lower bound.
* `.upper() -> Option<Integer>`: Upper bound if known.
* `.exact() -> Option<Integer>`: Exact value if upper bound is known and it
  equals to the lower bound.
* `.zero() -> Boolean`: True if upper bound is known and is `0`. Equivalent to
  `.upper() == 0`.

### `String` type

_Conversion: `Boolean`: yes, `Serialize`: yes, `Template`: yes_

A UTF-8-encoded string. This can be implicitly converted to `Boolean`. The
following methods are defined.

* `.len() -> Integer`: Length in UTF-8 bytes.
* `.contains(needle: Stringify) -> Boolean`: Whether the string contains the
  provided stringifiable value as a substring.
* `.match(needle: StringPattern) -> String`: Extracts
  the first matching part of the string for the given pattern.

  An empty string is returned if there is no match.
* `.starts_with(needle: Stringify) -> Boolean`
* `.ends_with(needle: Stringify) -> Boolean`
* `.remove_prefix(needle: Stringify) -> String`: Removes the passed prefix, if
  present.
* `.remove_suffix(needle: Stringify) -> String`: Removes the passed suffix, if
  present.
* `.trim() -> String`: Removes leading and trailing whitespace
* `.trim_start() -> String`: Removes leading whitespace
* `.trim_end() -> String`: Removes trailing whitespace
* `.substr(start: Integer, [end: Integer]) -> String`: Extract substring. The
  `start` / `end` indices should be specified in units of UTF-8 bytes. Indices are
  0-based and `end` is exclusive. Negative values count from the end of the
  string, with `-1` being the last byte. If the `start` index is in the middle
  of a UTF-8 codepoint, the codepoint is fully part of the result. If the `end`
  index is in the middle of a UTF-8 codepoint, the codepoint is not part of the
  result. If `end` is not given, returns from `start` to the end of the string.
* `.first_line() -> String`: Equivalent to `.lines().first()`.
* `.lines() -> List<String>`: Split into lines excluding newline characters.
* `.split(separator: StringPattern, [limit: Integer]) -> List<String>`: Split
  into substrings by the given `separator` pattern. If `limit` is specified, it
  determines the maximum number of elements in the result, with the remainder of
  the string returned as the final element. A `limit` of 0 returns an empty
  list.
* `.replace(pattern: StringPattern, replacement: Stringify, [limit: Integer]) ->
  String`: Replace occurrences of the given `pattern` with the `replacement`
  string.

  By default, all occurrences are replaced. If `limit` is specified, at most
  that many occurrences are replaced.

  Supports capture groups in patterns using `$0` (entire match), `$1`, `$2` etc.
* `.upper() -> String`
* `.lower() -> String`
* `.escape_json() -> String`: Serializes the string in JSON format. This
  function is useful for making machine-readable templates. For example, you
  can use it in a template like `'{ "foo": ' ++ foo.escape_json() ++ ' }'` to
  return a JSON/JSONL.

### `Stringify` type

An expression that can be converted to a `String`.

Any types that can be converted to `Template` can also be `Stringify`. Unlike
`Template`, color labels are stripped. Invalid UTF-8 sequences are rejected.

### `StringLiteral` type

A string literal known at parse time. Unlike `Stringify`, this cannot be a
dynamic expression - it must be a literal value like `"main"` or `"format"`.

String literals must be surrounded by single or double quotes (`'` or `"`).
A double-quoted string literal supports the following escape sequences:

* `\"`: double quote
* `\\`: backslash
* `\t`: horizontal tab
* `\r`: carriage return
* `\n`: new line
* `\0`: null
* `\e`: escape (i.e., `\x1b`)
* `\xHH`: byte with hex value `HH`

Other escape sequences are not supported. Any UTF-8 characters are allowed
inside a string literal, with two exceptions: unescaped `"`-s and uses of `\`
that don't form a valid escape sequence.

A single-quoted string literal has no escape syntax. `'` can't be expressed
inside a single-quoted string literal.

String literals have their own type so that the value can be validated at parse
time.

### `StringPattern` type

_Conversion: `Boolean`: no, `Serialize`: no, `Template`: no_

These are the exact same as the [String pattern type] in revsets, except that
quotes are mandatory.

Literal strings may be used, which are interpreted as case-sensitive substring
matching.

Currently `StringPattern` values cannot be passed around as values and may
only occur directly in the call site they are used in.

[String pattern type]: revsets.md#string-patterns

### `Template` type

_Conversion: `Boolean`: no, `Serialize`: no, `Template`: yes_

Most types can be implicitly converted to `Template`. No methods are defined.

### `Timestamp` type

_Conversion: `Boolean`: no, `Serialize`: yes, `Template`: yes_

The following methods are defined.

* `.ago() -> String`: Format as relative timestamp.
* `.format(format: Stringify) -> String`: Format with [the specified strftime-like
  format string](https://docs.rs/chrono/latest/chrono/format/strftime/).
* `.utc() -> Timestamp`: Convert timestamp into UTC timezone.
* `.local() -> Timestamp`: Convert timestamp into local timezone.
* `.after(date: StringLiteral) -> Boolean`: True if the timestamp is exactly at or
  after the given date. Supported date formats are the same as the revset
  [Date pattern type].
* `.before(date: StringLiteral) -> Boolean`: True if the timestamp is before, but
  not including, the given date. Supported date formats are the same as the
  revset [Date pattern type].
* `.since(start: Timestamp) -> TimestampRange`: The TimestampRange between `start` and
  `self`. It may be used in conjunction with `TimestampRange::duration` to obtain
  a human-friendly duration between two `Timestamp`s.

[Date pattern type]: revsets.md#date-patterns

### `TimestampRange` type

_Conversion: `Boolean`: no, `Serialize`: yes, `Template`: yes_

The following methods are defined.

* `.start() -> Timestamp`
* `.end() -> Timestamp`
* `.duration() -> String`

### `Trailer` type

_Conversion: `Boolean`: no, `Serialize`: no, `Template`: yes_

The following methods are defined.

* `.key() -> String`
* `.value() -> String`

### `TreeDiff` type

_Conversion: `Boolean`: no, `Serialize`: no, `Template`: no_

This type cannot be printed. The following methods are defined.

* `.files() -> List<TreeDiffEntry>`: Changed files.
* `.color_words([context: Integer]) -> Template`: Format as a word-level diff
  with changes indicated only by color.
* `.git([context: Integer]) -> Template`: Format as a Git diff.
* `.stat([width: Integer], [max_bar_width: Integer]) -> DiffStats`: Calculate
  stats of changed lines. `width` sets the entire width of the diff stat
  including paths, and `max_bar_width` limits the maximum character length of
  the `++--` bar.
* `.summary() -> Template`: Format as a list of status code and path pairs.

### `TreeDiffEntry` type

_Conversion: `Boolean`: no, `Serialize`: no, `Template`: no_

This type cannot be printed. The following methods are defined.

* `.path() -> RepoPath`: Path to the entry. If the entry is a copy / rename, this
  points to the target (or right) entry.
* `.display_diff_path() -> String`: Format path for display, taking into account copy / rename information.
* `.status() -> String`: One of `"modified"`, `"added"`, `"removed"`,
  `"copied"`, or `"renamed"`.
* `.status_char() -> String`: Single-character status indicator: `"M"` for modified,
  `"A"` for added, `"D"` for removed, `"C"` for copied, or `"R"` for renamed.
* `.source() -> TreeEntry`: The source (or left) entry.
* `.target() -> TreeEntry`: The target (or right) entry.

### `TreeEntry` type

_Conversion: `Boolean`: no, `Serialize`: no, `Template`: no_

This type cannot be printed. The following methods are defined.

* `.path() -> RepoPath`: Path to the entry.
* `.normal_value() -> Option<TreeValue>`: The resolved value of the entry, or none
  if the entry is absent or conflicted. Here, "normal" means resolved, not just a
  regular file: symlinks, trees, and Git submodules are also included.
* `.conflict() -> Boolean`: True if the entry is a merge conflict.
* `.conflict_side_count() -> Integer`: Number of sides in the merge conflict (1 if not
  conflicted, 2 or more for multi-way merges).
* `.file_type() -> String`: One of `"file"`, `"symlink"`, `"tree"`,
  `"git-submodule"`, or `"conflict"`.
* `.executable() -> Boolean`: True if the entry is an executable file.

### `TreeValue` type

_Conversion: `Boolean`: no, `Serialize`: no, `Template`: yes_

A file, symlink, tree, or Git submodule value. Its default formatting is
its full hexadecimal object ID. For a Git submodule, this is the recorded commit
ID. No methods are defined.

### `WorkspaceRef` type

_Conversion: `Boolean`: no, `Serialize`: yes, `Template`: yes_

The following methods are defined.

* `.name() -> RefSymbol`: Returns the workspace name as a symbol.
* `.target() -> Commit`: Returns the working-copy commit of this workspace.
* `.root() -> Option<FsPath>`: Returns the workspace root path, if the root path
  is recorded.

  This is optional because workspaces created before jj 0.38.0 did not record
  workspace root paths.

## Color labels

You can [customize the output colors][config-colors] by using color labels. `jj`
adds some labels automatically; they can also be added manually.

Template fragments are usually **automatically** labeled with the command name,
the context (or the top-level object), and the method names. For example, the
following template is labeled as `op_log operation id short` automatically:

```sh
jj op log -T 'self.id().short()'
```

The exact names of such labels are often straightforward, but are not currently
documented. You can discover the actual label names used with the
`--color=debug` option:

```sh
jj op log -T 'self.id().short()' --color=debug
```

This can be useful if you are trying to figure out whether or not a label is
present or which labels you should define or override colors for to get the
output that you want.

Additionally, you can **manually** insert arbitrary labels using the
`label(label, content)` function. For example,

```sh
jj op log -T '"ID: " ++ self.id().short().substr(0, 1) ++ label("id short", "<redacted>")'
```

will print "ID:" in the default style, and the string `<redacted>` in the same
style as the first character of the id. It would also be fine to use an
arbitrary template instead of the string `"<redacted>"`, possibly including
nested invocations of `label()`.

You are free to use custom label names as well. This will only have a visible
effect if you also [customize their colors][config-colors] explicitly.

To see all the default colors and what customizations apply from your own
config, run:

```sh
jj config list --include-defaults --include-overridden colors
```

The default colors can be found in [`colors.toml`][colors.toml], and the default
explicitly added labels (such as `mutable` or `divergent`) can be found in
[`templates.toml`][templates.toml]. Labels that appear in `colors.toml` but that
are not explicitly present in `templates.toml` will have been automatically
added by `jj` (such as `author` or `change_id`).

[config-colors]: config.md#custom-colors-and-styles
[colors.toml]: https://github.com/jj-vcs/jj/blob/main/cli/src/config/colors.toml
[templates.toml]: https://github.com/jj-vcs/jj/blob/main/cli/src/config/templates.toml

## Configuration

The default templates and aliases() are defined in the `[templates]` and
`[template-aliases]` sections of the config respectively. The exact definitions
can be seen in the [`templates.toml`][templates.toml] file.



New keywords, functions, and `<name>:<value>` patterns can be defined as
aliases, by using any combination of the predefined keywords/functions and other
aliases.

Alias functions can be overloaded by the number of parameters. However, builtin
functions will be shadowed by name, and can't co-exist with aliases.

For example:

```toml
[template-aliases]
sh = "commit_id.short()"
'commit_change_ids' = '''
concat(
  format_field("Commit ID", commit_id),
  format_field("Change ID", change_id),
)
'''
'format_field(key, value)' = 'key ++ ": " ++ value ++ "\n"'
'json:x' = 'json(x) ++ "\n"'
```

### Alias descriptions

Alias descriptions can be surfaced in shell completions by defining the alias
as a table with `.doc` and `.definition` properties. For example:

```toml
[template-aliases]
sh = { definition = 'commit_id.short()', doc = 'Short commit ID' }
```

You can also use the dotted key syntax:

```toml
[template-aliases]
sh.definition = 'commit_id.short()'
sh.doc = 'Short commit ID'
```

## Examples

Print the object IDs on both sides of a diff:

```sh
jj diff -T 'path ++ " " ++ self.source().normal_value() ++ " -> " ++ self.target().normal_value() ++ "\n"'
```

Get short commit IDs of the working-copy parents:

```sh
jj log -G -r @ -T 'parents.map(|c| c.commit_id().short()).join(",")'
```

Show machine-readable list of full commit and change IDs:

```sh
jj log -G -T 'commit_id ++ " " ++ change_id ++ "\n"'
```

Print the description of the current commit, defaulting to `(no description set)`:

```sh
jj log -G -r @ -T 'coalesce(description, "(no description set)\n")'
```

For each revision, print the name of its first bookmark or change ID:

```sh
jj log -T 'try(bookmarks.first().name(), change_id.shortest(8)) ++ "\n"'
```

---

### Testimonials (`docs/testimonials.md`)

# Testimonials

You might not be ready to make the jump to Jujutsu yet. It's understandable; new
tools come with new lessons, failures, and ideas to absorb. They require
practice. In order to provide some motivation, we've collected a number of real,
100% authentic testimonials &mdash; from our loving users, our silly developers
&mdash; all to tip the scales and get you on our side!

## What the users have to say

> I've spent many years of my career working on version control. What I like
> most about Jujutsu is how it has non-obvious solutions to UX problems that
> we've run into in the past. What most people may not realize is that there are
> many novel features which all interlock to make it easy to use.
>
> For example, consider Jujutsu's support for automatically rebasing descendants
> of amended revisions. When we implemented that in Mercurial, we ran into an
> issue: what if there's a merge conflict? Our solution was to warn users and
> just not perform the auto-rebase. Now, suddenly, users have to understand that
> there can be old versions of the same revision visible in their log, and learn
> how to fix this state.
>
> In contrast, Jujutsu's solution is to simply make merge conflicts first-class.
> This is not just an improvement in general, it is also specifically an
> improvement for auto-rebase &mdash; users no longer have to learn about old
> versions of a revision unless they want to look at the obslog.
>
> Over and over, I'm struck by how well Jujutsu demonstrates this kind of
> evolved thinking, which as an experienced version control developer I deeply
> appreciate.

&mdash; Rain, engineer at Oxide Computer Company, former VCS developer

> Jujutsu is amazing... I couldn't have come up with anything remotely as
> elegant.
>
> It's so rare that a solution attacks the innermost core of a problem so
> thoroughly, I genuinely feel blessed to be in its presence. And also a bit
> vindicated in not even trying to learn to use any of the tools that felt like
> more crutches stacked upon a sand castle

&mdash; Anonymous user, speaking from the shadows

> It's the easiest time I've ever had learning a tool this deeply this quickly,
> because of the ability to experiment and undo, instead of triple-checking
> before trying a new scary command.

&mdash; Scott Olson, advanced Git user and now a Jujutsu user

> I initially started to use Jujutsu for personal repos, and it has quickly
> gone from "neat, let's try this more" to "very neat, added to my permanent
> config and automatically installed for new machines".

&mdash; Poliorcetics, on GitHub

> when i worked on the rust compiler, my job was to chain together a bunch of
> strange and cursed tools that broke often. jujutsu breaks about half as
> much, so that's pretty good i guess

&mdash; jyn514, Rust contributor

> Jujutsu is pretty cool, you can even keep most of your existing workflows

&mdash; Ben, who doesn't want you keeping your existing workflow

> Wait, it's not called Jujitsu?

&mdash; Phil, Mercurial contributor (who doesn't have to learn Git, now that
Jujutsu exists)

> When I heard about Jujutsu I decided to try it out before forming an opinion.
> Technically it never formed, because I haven't considered going back.

&mdash; gul banana, computer programmer

> muwhahaha, I have corrupted my cofounder and now 100% of brontosource dev is on JJ.

&mdash; Matt Kulunkundis, cofounder BrontoSource

> jj is genuinely the first tool since Rust that has me excited.

&mdash; David Barsky, rust-analyzer and `tokio-rs/tracing` developer

> Today I did some complex surgery on a legacy repo that has been committed to
> for years by a number of individuals with varying hygienic habits. [..]
>
> Now, this still took me about two hours using jj. But at no point did I
> seriously consider physical violence to humans or inanimate objects.
> Everything progressed calmly and methodically. I was able to introspect
> changes before I made them, and when I did screw up… `jj undo` to the
> rescue.

&mdash; An unknown scientist, who couldn't have done it with Git.

> jj was my first exposure to revsets and at first I was
> "why would I ever need that", but now after exercising that math part of my
> brain that's been stale since high school, they're indispensable

&mdash; Marijan Smetko, who previously only knew Git.

> I think my favorite thing about jj is it makes the way I abused Git via
> lazygit the standard way of doing things

&mdash; Isaac Corbrey, reformed Git user

## What the developers have to say

> I've been a FOSS contributor using Git for over 16 years, and Jujutsu
> continues to amaze me every day. It has that sweet simplicity I was fond of in
> Darcs, but it boils down all my most core and fundamental workflows &mdash;
> developed over years of experience &mdash; into a simple set of primitives.
> The internal design is simple and beautiful; it looks like a database, making
> the implementation elegant, safe, and extensible. All this, using the same Git
> repositories my coworkers use.
>
> It's like if you found out one day that you built your entire home on a vein
> of rich gold. Every day I seem to find new and beautiful emergent behaviors,
> all adding up to a tool that is greater than the sum of its parts.

&mdash; Austin Seipp, "No 1. Jujutsu Fan"

> Honestly, I implemented signing support mostly for that sweet dopamine hit
> that you get from the green checkmark on GitHub. Yeah.

&mdash; Anton Bulakh, contributor and dopamine enthusiast

> I'm sometimes still surprised that navigating with `jj next` and `jj prev`
> works.

&mdash; Philip Metzger, author of `jj next` and `jj prev`

> I'm surprised when it works.

&mdash; Martin von Zweigbergk, project creator and leader

## Spread the word yourself

Are you satisfied with Jujutsu? Ready to recommend it to a Jujillion of your
friends and coworkers? Great! The easiest way to help the project grow is word
of mouth. So make sure to talk to them about it and show off your hip new tool.
Maybe post a link to it on your other favorite tool that you love using, Slack?

If you're not sure what to say, we hired the cheapest marketing team we could
find to design a list of Pre-Approved Endorsements in their laboratory. Just
copy and paste these right into a text box! Shilling for an open source project
has never been easier than this.

> Jujutsu is an alright tool. I guess.

> Jujutsu is my favorite software tool of all time. I am saying this for no
> particular reason, definitely not because I was paid to.

> I love Jujutsu. I love Jujutsu. I love Jujutsu. I love Jujutsu. I love
> Jujutsu. I love Jujutsu. I love Jujutsu. I love Jujutsu.

---

### Tutorial (`docs/tutorial.md`)

# Tutorial

> **Hint:** This tutorial covers the basics accurately, but since it was
> written Jujutsu has gained many new features. For a tutorial covering a wider
> range of features, you may find the (not quite finished) [tutorial by Steve
> Klabnik](https://steveklabnik.github.io/jujutsu-tutorial/) helpful. If you're
> looking for a tutorial that doesn't require experience with Git, take a look
> at [Jujutsu for everyone](https://jj-for-everyone.github.io).

This text assumes that the reader is familiar with Git.

## Preparation

If you haven't already, make sure you
[install and configure Jujutsu](install-and-setup.md).

## Cloning a Git repository

> **Hint:** Most identifiers used in this tutorial will be different when you
> try this at home!

Let's start by cloning GitHub's Hello-World repo using `jj`:

```shell
# Note the "git" before "clone" (there is no support for cloning native jj
# repos yet)
$ jj git clone https://github.com/octocat/Hello-World
Fetching into new repo in "/tmp/tmp.O1DWMiaKd4/Hello-World"
remote: Enumerating objects: 13, done.
remote: Total 13 (delta 0), reused 0 (delta 0), pack-reused 13 (from 1)
bookmark: master@origin          [new] untracked
bookmark: octocat-patch-1@origin [new] untracked
bookmark: test@origin            [new] untracked
Setting the revset alias `trunk()` to `master@origin`
Working copy  (@) now at: kntqzsqt d7439b06 (empty) (no description set)
Parent commit (@-)      : orrkosyo 7fd1a60b master | (empty) Merge pull request #6 from Spaceghost/patch-1
Added 1 files, modified 0 files, removed 0 files
$ cd Hello-World
```

Running `jj st` (short for `jj status`) now yields something like this:

```shell
$ jj st
The working copy has no changes.
Working copy  (@) : kntqzsqt d7439b06 (empty) (no description set)
Parent commit (@-): orrkosyo 7fd1a60b master | (empty) Merge pull request #6 from Spaceghost/patch-1
```

Let's look at that output as it introduces new concepts. You can see two
commits: Parent and working copy. Both are identified using two separate
identifiers: the "change ID" and the "commit ID".

The parent commit, for example, has the change ID `orrkosyo` and the commit ID
`7fd1a60b`.

> **Git users:** The commit ID/hash is what you're used to from Git and should
> match what you see when you look at the repository using `git log` in a Git
> checkout of the repository.
> The change ID however, is a new concept, unique to Jujutsu.

We can also see from the output above that our working copy is an actual commit
with a commit ID (`d7439b06` in the example). When you make a change in the
working copy, the working-copy commit gets automatically amended by the next
`jj` command.

> **Git users:** This is a huge difference from Git where the working copy is a
> separate concept and not yet a commit.

## Changes

A change is a commit that can evolve while keeping a stable identifier (similar
to Gerrit's Change-Id). In other words: You can make changes to files in a
change, resulting in a new commit hash, but the change ID will remain the same.

You can see that our clone operation automatically created a new change:

```shell
Working copy  (@) : kntqzsqt d7439b06 (empty) (no description set)
```

This new change has the ID `kntqzsqt` and it is currently empty (contains no
changes compared to the parent) and has no description.

## Creating our first change

Let's say we want to edit the `README` file in the repo to say "Goodbye"
instead of "Hello". Start by describing the change (adding a commit message) so
we don't forget what we're working on:

```shell
# This brings up $EDITOR (or `nano` or `Notepad` by default).
# Enter something like "Say goodbye" in the editor and then save the file and close
# the editor.
$ jj describe
Working copy  (@) now at: kntqzsqt e427edcf (empty) Say goodbye
Parent commit (@-)      : orrkosyo 7fd1a60b master | (empty) Merge pull request #6 from Spaceghost/patch-1
```

Now make the change in the README:

```shell
# Adjust as necessary for compatibility with your flavor of `sed`
$ sed -i 's/Hello/Goodbye/' README
$ jj st
Working copy changes:
M README
Working copy  (@) : kntqzsqt 5d39e19d Say goodbye
Parent commit (@-): orrkosyo 7fd1a60b master | (empty) Merge pull request #6 from Spaceghost/patch-1
```

Note that you didn't have to tell Jujutsu to add the change like you would with
`git add`. You actually don't even need to tell it when you add new files or
remove existing files. To untrack a path, add it to your `.gitignore` and run
`jj file untrack <path>`.

Also note that the commit hash for our current change (`kntqzsqt`) changed from
`e427edcf` to `5d39e19d`!

To see the diff, run `jj diff`:

```shell
$ jj diff --git  # Feel free to skip the `--git` flag
diff --git a/README b/README
index 980a0d5f19..1ce3f81130 100644
--- a/README
+++ b/README
@@ -1,1 +1,1 @@
-Hello World!
+Goodbye World!
```

Jujutsu's diff format currently defaults to inline coloring of the diff (like
`git diff --color-words`), so we used `--git` above to make the diff readable in
this tutorial.

As you may have noticed, the working-copy commit's ID changed both when we
edited the description and when we edited the README. However, the parent commit
stayed the same. Each change to the working-copy commit amends the previous
version. So how do we tell Jujutsu that we are done amending the current change
and want to start working on a new one? That is what `jj new` is for. That will
create a new commit on top of your current working-copy commit. The new commit
is for the working-copy changes.

So, let's say we're now done with this change, so we create a new change:

```shell
$ jj new
Working copy  (@) now at: mpqrykyp aef4df99 (empty) (no description set)
Parent commit (@-)      : kntqzsqt 5d39e19d Say goodbye
$ jj st
The working copy has no changes.
Working copy  (@) : mpqrykyp aef4df99 (empty) (no description set)
Parent commit (@-): kntqzsqt 5d39e19d Say goodbye
```

If we later realize that we want to make further changes, we can make them in
the working copy and then run `jj squash`. That command squashes (moves) the
changes from a given commit into its parent commit. Like most commands, it acts
on the working-copy commit by default. When run on the working-copy commit, it
behaves very similar to `git commit --amend`.

Alternatively, we can use `jj edit <commit>` to resume editing a commit in the
working copy. Any further changes in the working copy will then amend the
commit. Whether you choose to create a new change and squash, or to edit,
typically depends on how done you are with the change; if the change is almost
done, it makes sense to use `jj new` so you can easily review your adjustments
with `jj diff` before running `jj squash`.

To view how a change has evolved over time, we can use `jj evolog` to see each
recorded change for the current commit. This records changes to the working
copy, message, squashes, rebases, etc.

## The log command and "revsets"

You're probably familiar with `git log`. Jujutsu has very similar functionality
in its `jj log` command:

```shell
$ jj log
@  mpqrykyp martinvonz@google.com 2023-02-12 15:00:22 aef4df99
│  (empty) (no description set)
○  kntqzsqt martinvonz@google.com 2023-02-12 14:56:59 5d39e19d
│  Say goodbye
◆  orrkosyo octocat@nowhere.com 2012-03-06 15:06:50 master 7fd1a60b
│  (empty) Merge pull request #6 from Spaceghost/patch-1
~
```

The `@` indicates the working-copy commit. The first ID on a line
(e.g. "mpqrykyp" above) is the change ID. The second ID is the commit ID. You
can give either ID to commands that take revisions as arguments. We will
generally prefer change IDs because they stay the same when the commit is
rewritten.

By default, `jj log` lists your local commits, with some remote commits added
for context. The `~` indicates that the commit has parents that are not included
in the graph. We can use the `--revisions`/`-r` flag to select a different set
of revisions to list. The flag accepts a ["revset"](revsets.md), which is an
expression in a simple language for specifying revisions. For example, `@`
refers to the working-copy commit, `root()` refers to the root commit,
`bookmarks()` refers to all commits pointed to by bookmarks (similar to Git's
branches). We can combine expressions with `|` for union, `&` for intersection
and `~` for difference. For example:

```shell
$ jj log -r '@ | root() | bookmarks()'
@  mpqrykyp martinvonz@google.com 2023-02-12 15:00:22 aef4df99
│  (empty) (no description set)
~  (elided revisions)
◆  orrkosyo octocat@nowhere.com 2012-03-06 15:06:50 master 7fd1a60b
│  (empty) Merge pull request #6 from Spaceghost/patch-1
~  (elided revisions)
◆  zzzzzzzz root() 00000000
```

The `00000000` commit (change ID `zzzzzzzz`) is a virtual commit that's called
the "root commit". It's the root commit of every repo. The `root()`
function in the revset matches it.

There are also operators for getting the parents (`foo-`), children (`foo+`),
ancestors (`::foo`), descendants (`foo::`), DAG range (`foo::bar`, like
`git log --ancestry-path`), range (`foo..bar`, same as Git's). See
[the revset documentation](revsets.md) for all revset operators and functions.

> **Hint:** If the default `jj log` omits some commits you expect to see, you
> can always run `jj log -r ::` (or, equivalently, `jj log -r 'all()'`) to see
> all the commits.

## Conflicts

Now let's see how Jujutsu deals with merge conflicts. We'll start by making some
commits. We use `jj new` with the `--message`/`-m` option to set change
descriptions (commit messages) right away.

```shell
# Start creating a chain of commits off of the `master` bookmark
$ jj new master -m A; echo a > file1
Working copy  (@) now at: nuvyytnq 00a2aeed (empty) A
Parent commit (@-)      : orrkosyo 7fd1a60b master | (empty) Merge pull request #6 from Spaceghost/patch-1
Added 0 files, modified 1 files, removed 0 files
$ jj new -m B1; echo b1 > file1
Working copy  (@) now at: ovknlmro 967d9f9f (empty) B1
Parent commit (@-)      : nuvyytnq 5dda2f09 A
$ jj new -m B2; echo b2 > file1
Working copy  (@) now at: puqltutt 8ebeaffa (empty) B2
Parent commit (@-)      : ovknlmro 7d7c6e6b B1
$ jj new -m C; echo c > file2
Working copy  (@) now at: qzvqqupx 62a3c6d3 (empty) C
Parent commit (@-)      : puqltutt daa6ffd5 B2
$ jj log
@  qzvqqupx martinvonz@google.com 2023-02-12 15:07:41 2370ddf3
│  C
○  puqltutt martinvonz@google.com 2023-02-12 15:07:33 daa6ffd5
│  B2
○  ovknlmro martinvonz@google.com 2023-02-12 15:07:24 7d7c6e6b
│  B1
○  nuvyytnq martinvonz@google.com 2023-02-12 15:07:05 5dda2f09
│  A
│ ○  kntqzsqt martinvonz@google.com 2023-02-12 14:56:59 5d39e19d
├─╯  Say goodbye
◆  orrkosyo octocat@nowhere.com 2012-03-06 15:06:50 master 7fd1a60b
│  (empty) Merge pull request #6 from Spaceghost/patch-1
~
```

We now have a few commits, where A, B1, and B2 modify the same file, while C
modifies a different file. Let's now rebase B2 directly onto A. We use the
`--source`/`-s` option on the change ID of B2, and `--onto`/`-o` option on A.

```shell
$ jj rebase -s puqltutt -o nuvyytnq  # Replace the IDs by what you have for B2 and A
Rebased 2 commits to destination
Working copy  (@) now at: qzvqqupx 1978b534 (conflict) C
Parent commit (@-)      : puqltutt f7fb5943 (conflict) B2
Added 0 files, modified 1 files, removed 0 files
Warning: There are unresolved conflicts at these paths:
file1    2-sided conflict
New conflicts appeared in 2 commits:
  qzvqqupx 1978b534 (conflict) C
  puqltutt f7fb5943 (conflict) B2
Hint: To resolve the conflicts, start by creating a commit on top of
the first conflicted commit:
  jj new puqltutt
Then use `jj resolve`, or edit the conflict markers in the file directly.
Once the conflicts are resolved, you can inspect the result with `jj diff`.
Then run `jj squash` to move the resolution into the conflicted commit.

$ jj log
@  qzvqqupx martinvonz@google.com 2023-02-12 15:08:33 1978b534 (conflict)
│  C
×  puqltutt martinvonz@google.com 2023-02-12 15:08:33 f7fb5943 (conflict)
│  B2
│ ○  ovknlmro martinvonz@google.com 2023-02-12 15:07:24 7d7c6e6b
├─╯  B1
○  nuvyytnq martinvonz@google.com 2023-02-12 15:07:05 5dda2f09
│  A
│ ○  kntqzsqt martinvonz@google.com 2023-02-12 14:56:59 5d39e19d
├─╯  Say goodbye
◆  orrkosyo octocat@nowhere.com 2012-03-06 15:06:50 master 7fd1a60b
│  (empty) Merge pull request #6 from Spaceghost/patch-1
~
```

There are several things worth noting here. First, the `jj rebase` command said
"Rebased 2 commits". That's because we asked it to rebase commit B2 with the
`-s` option, which also rebases descendants (commit C in this case). Second,
because B2 modified the same file (and word) as B1, rebasing it resulted in
conflicts, as the output indicates. Third, the conflicts did not prevent the
rebase from completing successfully, nor did it prevent C from getting rebased
on top.

Now let's resolve the conflict in B2. We'll do that by creating a new commit on
top of B2. Once we've resolved the conflict, we'll squash the conflict
resolution into the conflicted B2. That might look like this:

```
/* Detailed source-code truncated for AI context efficiency. */
```

Note that commit C automatically got rebased on top of the resolved B2, and that
C is also resolved (since it modified only a different file).

By the way, if we want to get rid of B1 now, we can run `jj abandon
ovknlmro`. That will hide the commit from the log output and will rebase any
descendants to its parent.

## The operation log

Jujutsu keeps a record of all changes you've made to the repo in what's called
the "operation log". Use the `jj op` (short for `jj operation`) family of
commands to interact with it. To list the operations, use `jj op log`:

```shell
$ jj op log
@  d3b77addea49 martinvonz@vonz.svl.corp.google.com 3 minutes ago, lasted 3 milliseconds
│  squash commits into f7fb5943a6b9460eb106dba2fac5cac1625c6f7a
│  args: jj squash
○  6fc1873c1180 martinvonz@vonz.svl.corp.google.com 3 minutes ago, lasted 1 milliseconds
│  snapshot working copy
│  args: jj st
○  ed91f7bcc1fb martinvonz@vonz.svl.corp.google.com 6 minutes ago, lasted 1 milliseconds
│  new empty commit
│  args: jj new puqltutt
○  367400773f87 martinvonz@vonz.svl.corp.google.com 12 minutes ago, lasted 3 milliseconds
│  rebase commit daa6ffd5a09a8a7d09a65796194e69b7ed0a566d and descendants
│  args: jj rebase -s puqltutt -o nuvyytnq
[many more lines]
```

The most useful command is `jj undo`, which will undo your last operation.

```shell
$ jj undo
Reverted operation: d3b77addea49 (2025-05-12 00:27:27) squash commits into f7fb5943a6b9460eb106dba2fac5cac1625c6f7a
Working copy  (@) now at: zxoosnnp 63874fe6 (no description set)
Parent commit (@-)      : puqltutt f7fb5943 (conflict) B2
New conflicts appeared in 2 commits:
  qzvqqupx 1978b534 (conflict) C
  puqltutt f7fb5943 (conflict) B2
Hint: To resolve the conflicts, start by creating a commit on top of
the first conflicted commit:
  jj new puqltutt
Then use `jj resolve`, or edit the conflict markers in the file directly.
Once the conflicts are resolved, you can inspect the result with `jj diff`.
Then run `jj squash` to move the resolution into the conflicted commit.

$ jj log
@  zxoosnnp martinvonz@google.com 2023-02-12 19:34:09 63874fe6
│  (no description set)
│ ×  qzvqqupx martinvonz@google.com 2023-02-12 15:08:33 1978b534 (conflict)
├─╯  C
×  puqltutt martinvonz@google.com 2023-02-12 15:08:33 f7fb5943 (conflict)
│  B2
│ ○  ovknlmro martinvonz@google.com 2023-02-12 15:07:24 7d7c6e6b
├─╯  B1
○  nuvyytnq martinvonz@google.com 2023-02-12 15:07:05 5dda2f09
│  A
│ ○  kntqzsqt martinvonz@google.com 2023-02-12 14:56:59 5d39e19d
├─╯  Say goodbye
◆  orrkosyo octocat@nowhere.com 2012-03-06 15:06:50 master 7fd1a60b
│  (empty) Merge pull request #6 from Spaceghost/patch-1
~
```

As you can perhaps see, that undid the `jj squash` invocation we used for
squashing the conflict resolution into commit B2 earlier. Notice that it also
updated the working copy.

You can also view the repo the way it looked after some earlier operation. For
example, if you want to see `jj log` output right after the `jj rebase`
operation, try `jj log --at-op=367400773f87` but use the hash from your own
`jj op log`.

## Moving content changes between commits

You have already seen how `jj squash` can combine the changes from two commits
into one. There are several other commands for changing the contents of existing
commits.

We'll need some more complex content to test these commands, so let's create a
few more commits:

```shell
$ jj new master -m abc; printf 'a\nb\nc\n' > file
Working copy  (@) now at: ztqrpvnw f94e49cf (empty) abc
Parent commit (@-)      : orrkosyo 7fd1a60b master | (empty) Merge pull request #6 from Spaceghost/patch-1
Added 0 files, modified 0 files, removed 1 files

$ jj new -m ABC; printf 'A\nB\nc\n' > file
Working copy  (@) now at: kwtuwqnm 6f30cd1f (empty) ABC
Parent commit (@-)      : ztqrpvnw 51002261 abc

$ jj new -m ABCD; printf 'A\nB\nC\nD\n' > file
Working copy  (@) now at: mrxqplyk a6749154 (empty) ABCD
Parent commit (@-)      : kwtuwqnm 30aecc08 ABC

$ jj log -r master::@
@  mrxqplyk martinvonz@google.com 2023-02-12 19:38:21 b98c607b
│  ABCD
○  kwtuwqnm martinvonz@google.com 2023-02-12 19:38:12 30aecc08
│  ABC
○  ztqrpvnw martinvonz@google.com 2023-02-12 19:38:03 51002261
│  abc
◆  orrkosyo octocat@nowhere.com 2012-03-06 15:06:50 master 7fd1a60b
│  (empty) Merge pull request #6 from Spaceghost/patch-1
~
```

We "forgot" to capitalize "c" in the second commit when we capitalized the other
letters. We then fixed that in the third commit when we also added "D". It would
be cleaner to move the capitalization of "c" into the second commit. We can do
that by running `jj squash` with the `--interactive`/`-i` option on the third
commit. Remember that `jj squash` moves all the changes from one commit into its
parent. `jj squash -i` moves only part of the changes into its parent. Now try
that:

```shell
$ jj squash -i
Hint: Using default editor ':builtin'; run `jj config set --user ui.diff-editor :builtin` to disable this message.
Rebased 1 descendant commits
Working copy  (@) now at: mrxqplyk 52a6c7fd ABCD
Parent commit (@-)      : kwtuwqnm 643061ac ABC
```

That will bring up the built-in diff editor[^alternative_diff_editors] with a
diff of the changes in the "ABCD" commit. Expand the file by clicking on `(+)`
or with right arrow, then select the sections/line to include by clicking or
using space. Once complete, press `c` to confirm changes, or `q` to exit without
saving. You can also use the mouse to click on the menu items to see more
options (keyboard navigation is currently limited).

[^alternative_diff_editors]: There are many other diff editors you could use.
For example, if you have [Meld](https://meldmerge.org) installed and in the
PATH, you can use it via `jj squash -i --tool meld` or a fancier config with `jj
squash -i --tool meld-3`. You can configure the default with the
[`ui.diff-editor` option](config.md#editing-diffs); those docs also explain how
to specify a path to an executable if it is not in the PATH.

If we look at the diff of the second commit, we now see that all three lines got
capitalized:

```shell
$ jj diff -r @- --git
diff --git a/file b/file
index de980441c3..b1e67221af 100644
--- a/file
+++ b/file
@@ -1,3 +1,3 @@
-a
-b
-c
+A
+B
+C
```

The child change ("ABCD" in our case) will have the same content *state* after
the `jj squash` command. That means that you can move any changes you want into
the parent change, even if they touch the same word, and it won't cause any
conflicts.

Let's try one final command for changing the contents of an existing commit. That
command is `jj diffedit`, which lets you edit the changes in a commit without
checking it out.

```shell
$ jj diffedit -r @-
Hint: Using default editor ':builtin'; run `jj config set --user ui.diff-editor :builtin` to disable this message.
Rebased 1 descendant commits
Working copy  (@) now at: mrxqplyk 1c72cd50 (conflict) ABCD
Parent commit (@-)      : kwtuwqnm 70985eaa ABC
Added 0 files, modified 1 files, removed 0 files
Warning: There are unresolved conflicts at these paths:
file    2-sided conflict
New conflicts appeared in 1 commits:
  mrxqplyk 1c72cd50 (conflict) ABCD
Hint: To resolve the conflicts, start by creating a commit on top of
the conflicted commit:
  jj new mrxqplyk
Then use `jj resolve`, or edit the conflict markers in the file directly.
Once the conflicts are resolved, you can inspect the result with `jj diff`.
Then run `jj squash` to move the resolution into the conflicted commit.
```

In the diff editor, use the arrow keys and spacebar to select all lines but the
last. Press 'c' to save the changes and close it. You can now inspect the
rewritten commit with `jj diff -r @-` again, and you should see your deletion of
the last line. Unlike `jj squash -i`, which left the content state of the commit
unchanged, `jj diffedit` (typically) results in a different state, which means
that descendant commits may have conflicts.

Another command for rewriting contents of existing commits is `jj split`. Now
that you've seen how `jj squash -i` and `jj diffedit` work, you can hopefully
figure out how it works (with the help of the instructions in the diff).

## Using an existing Git repository

If you already have a local Git checkout, you can start using Jujutsu in that
directory instead of cloning:

```shell
$ cd path/to/my-git-repo
$ jj git init
```

This creates a [colocated
workspace](git-compatibility.md#colocated-jujutsugit-workspaces) (`.jj/` next to
`.git/`), so you can use both `jj` and `git` commands in the same directory. See
[Creating a repo backed by an existing Git
repo](git-compatibility.md#creating-a-repo-backed-by-an-existing-git-repo) for
more details.

---

### Windows (`docs/windows.md`)

# Working on Windows

Jujutsu works the same on all platforms, but there are some caveats that Windows
users should be aware of.

## Line endings conversion

Jujutsu has a setting,
[`working-copy.eol-conversion`](config.md#eol-conversion-setting), similar to
Git's [`core.autocrlf`][git-autocrlf][^1], but does not currently honor
`.gitattributes` and the `core.autocrlf` Git config, so it is recommended to
keep the `working-copy.eol-conversion` setting and the `core.autocrlf` Git
config in sync[^1].

!!! note

    If you created a colocated workspace and forgot to keep these two settings
    in sync, resulting in a dirty working copy with only EOL diffs, you can set
    the `working-copy.eol-conversion` setting correctly and run `jj abandon` to
    fix it.

Regardless of this setting, the line endings conversion is skipped on binary
files based on a [heuristic](config.md#binary-file-detection). This behavior is
subject to change when we support the `text` Git attribute.

Jujutsu may incorrectly determine whether a file is a binary file and apply line
endings conversion incorrectly, but currently, Jujutsu doesn't support per-file
configuration of line endings conversion. If this occurs, you can disable line
endings conversion by setting the `working-copy.eol-conversion` config to `none`
or just removing the config altogether.

```powershell
PS> git config core.autocrlf input
# Use `none` instead of `input` to avoid applying EOL conversion.
PS> jj config set --repo working-copy.eol-conversion none
# Abandoning the working copy will cause Jujutsu to overwrite all files with
# CRLF line endings with the line endings they were committed with, probably LF.
PS> jj abandon
```

This means that line endings will be checked out exactly as they are committed
and committed exactly as authored, ensuring Git will check out files with LF
line endings without converting them to CRLF. You'll want to make sure any
tooling you use, especially IDEs, preserve LF line endings.

[^1]: This poses the question of whether we should support reading the
      `core.autocrlf` setting in colocated workspaces. See details at the
      [issue][read-git-config-issue].

[git-autocrlf]: https://git-scm.com/book/en/v2/Customizing-Git-Git-Configuration#_core_autocrlf
[read-git-config-issue]: https://github.com/jj-vcs/jj/issues/4048

## Pagination

On Windows, `jj` will use its integrated pager called `streampager` by default,
unless the config `ui.pager` is explicitly set. See the [pager section of the
config docs](config.md#pager) for more details.

If the built-in pager doesn't meet your needs and you have Git installed, you
can switch to using Git's pager as follows:

```powershell
PS> jj config set --user ui.pager '["C:\\Program Files\\Git\\usr\\bin\\less.exe", "-FRX"]'
PS> jj config set --user ui.paginate auto
```

## Typing `@` in PowerShell

PowerShell uses `@` as part the [array sub-expression operator][array-op], so it
often needs to be escaped or quoted in commands:

```powershell
PS> jj log -r `@
PS> jj log -r '@'
```

One solution is to create a revset alias. For example, to make `HEAD` an alias
for `@`:

```powershell
PS> jj config set --user revset-aliases.HEAD '@'
PS> jj log -r HEAD
```

## WSL sets the execute bit on all files

When viewing a Windows drive from WSL (via _/mnt/c_ or a similar path), Windows
exposes all files with the execute bit set. Since Jujutsu automatically records
changes to the working copy, this sets the execute bit on all files committed in
your repository.

If you only need to access the repository in WSL, the best solution is to clone
the repository in the Linux file system (for example, in
`~/my-repo`).

If you need to use the repository in both WSL and Windows, one solution is to
create a workspace in the Linux file system:

```powershell
PS> jj workspace add --name wsl ~/my-repo
```

Then only use the `~/my-repo` workspace from Linux.

[array-op]: https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_arrays?view=powershell-7.4#the-array-sub-expression-operator

## Symbolic link support

`jj` supports symlinks on Windows only when they are enabled by the operating
system. This requires Windows 10 version 14972 or higher, as well as Developer
Mode. If those conditions are not satisfied, `jj` will materialize symlinks as
ordinary files.

For colocated workspaces, Git support must also be enabled using the
`git config` option `core.symlinks=true`.

---

### Working Copy (`docs/working-copy.md`)

# Working copy

## Introduction

The working copy is where the current working-copy commit's files are written so
you can interact with them. It is also where files are read from in order to
create new commits (though there are many other ways of creating new commits).

Unlike most other VCSs, Jujutsu will automatically create commits from the
working-copy contents when they have changed. Most `jj` commands you run will
commit the working-copy changes if they have changed. The resulting revision
will replace the previous working-copy revision.

Also unlike most other VCSs, added files are implicitly tracked by default. That
means that if you add a new file to the working copy, it will be automatically
committed once you run e.g. `jj st`. Similarly, if you remove a file from the
working copy, it will implicitly be untracked.

The `snapshot.auto-track` config option controls which paths get automatically
tracked when they're added to the working copy. See the
[fileset documentation](filesets.md) for the syntax. Files with paths matching
[ignore files](#ignored-files) are never tracked automatically.

If you set `snapshot.auto-track` to a non-default value, untracked files can be
tracked with `jj file track`.

You can use `jj file untrack` to untrack a file while keeping it in the working
copy. However, first [ignore](#ignored-files) them or remove them from the
`snapshot.auto-track` patterns; otherwise they will be immediately tracked again.

## Conflicts

When you check out a commit with conflicts, those conflicts need to be
represented in the working copy somehow. However, the file system doesn't
understand conflicts. Jujutsu's solution is to add conflict markers to
conflicted files when it writes them to the working copy. It also keeps track of
the (typically 3) different parts involved in the conflict. Whenever it scans
the working copy thereafter, it parses the conflict markers and recreates the
conflict state from them. You can resolve conflicts by replacing the conflict
markers by the resolved text. You don't need to resolve all conflicts at once.
You can even resolve part of a conflict by updating the different parts of the
conflict marker.

To resolve conflicts in a commit, use `jj new <commit>` to create a working-copy
commit on top. You would then have the same conflicts in the working-copy
commit. Once you have resolved the conflicts, you can inspect the conflict
resolutions with `jj diff`. Then run `jj squash` to move the conflict
resolutions into the conflicted commit. Alternatively, you can edit the commit
with conflicts directly in the working copy by using `jj edit <commit>`. The
main disadvantage of that is that it's harder to inspect the conflict
resolutions.

With the `jj resolve` command, you can use an external merge tool to resolve
conflicts that have 2 sides and a base. There is not yet a good way of
resolving conflicts between directories, files, and symlinks
(<https://github.com/jj-vcs/jj/issues/19>). You can use `jj restore` to choose
one side of the conflict, but there's no way to even see where the involved
parts came from.

## Ignored files

You probably don't want build outputs and temporary files to be under version
control. You can tell Jujutsu to not automatically track certain files by using
`.gitignore` files (there's no such thing as `.jjignore` yet). See
<https://git-scm.com/docs/gitignore> for details about the format. `.gitignore`
files are supported in any directory in the working copy, as well as in
`$XDG_CONFIG_HOME/git/ignore` and `$GIT_DIR/info/exclude`.

Ignored files are never tracked automatically (regardless of the value of
`snapshot.auto-track`), but files that were already tracked will remain tracked
even if they match ignore patterns. You can untrack such files with the
`jj file untrack` command.

## Workspaces

You can have multiple working copies backed by a single repo. Use
`jj workspace add` to create a new working copy. The working copy will have a
`.jj/` directory linked to the main repo. The working copy and the `.jj/`
directory together is called a "workspace". Each workspace can have a different
commit checked out.

Having multiple workspaces can be useful for running long-running tests in one
while you continue developing in another, for example. If needed,
`jj workspace root --name <workspace>` prints the root path of the specified
workspace (defaults to the current one).

`jj workspace list` shows every workspace together with its available root path.

When you're done using a workspace, use `jj workspace forget` to make the repo
forget about it. The workspace directory and files are left on disk. This is
useful when the workspace path is no longer accessible or when you want to keep
the files around.

Use `jj workspace remove` to forget a workspace and remove its directory from
disk. Tracked changes are snapshotted before the directory is removed, but
ignored files in that directory are deleted. The main workspace cannot be
removed. Removing the current workspace works on Unix, but Windows won't remove
a directory that a process is running in; run the command from another directory
using `jj -R` instead.

## Stale working copy

Almost all commands go through three main steps:

1. Snapshot the working copy (which gets recorded as an operation)
2. Create new commits etc. "in memory" and record that as a new operation
3. Update the working copy to match the new operation, i.e. to the commit that
   the operation says that `@` should point to

If step 3 doesn't happen for some reason, the working copy is considered
"stale". We can detect that because the working copy (`.jj/working_copy/`)
keeps track of which operation it was last updated to. When the working copy is
stale, use `jj workspace update-stale` to update the files in the working copy.

A common reason that step 3 doesn't happen for a working copy is that you
rewrote the commit from another workspace. When you modify workspace A's
working-copy commit from workspace B, workspace A's working copy will become
stale.

A working copy can also become stale because some error, such as `^C` prevented
step 3 from completing. It's also possible that it was successfully updated in
step 3 but the operation has then been lost (e.g. by `jj op abandon` or
"spontaneously" by certain storage backends). If the operation has been lost,
then `jj workspace update-stale` will create a recovery commit with the
contents of the working copy but parented to the current operation's
working-copy commit.

---

### Design Copy Tracking (`docs/design/copy-tracking.md`)

# Copy Tracking and Tracing Design

Authors: [Daniel Ploch](mailto:dploch@google.com), [Martin von Zweigbergk](mailto:martinvonz@google.com)

**Summary:** This Document documents an approach to tracking and detecting copy
information in jj repos, in a way that is compatible with both Git's detection
model and with custom backends that have more complicated tracking of copy
information. This design affects the output of diff commands as well as the
results of rebasing across remote copies.

## Objective

Add support for copy information that is sufficient for at least the following
use cases:

* Diffing: If a file has been copied, show a diff compared to the source version
  instead of showing a full addition.
* Merging: When one side of a merge (or rebase) has renamed a file and the other
  side has modified it, propagate the changes to the other side. (There are many
  other cases to handle too.)
* Log: It should be possible to run something like `jj log -p <file>` and follow
  the file backwards when it had been created by copying.
* Annotate (blame): Similar to the log use case, we should follow the file
  backwards when it had been created by copying.

The solution should support recording and retrieving copy info in a way that
is performant both for Git, which synthesizes copy info on the fly between
arbitrary trees, and for custom backends which may explicitly record and
re-serve copy info over arbitrarily large commit ranges.

The APIs should be defined in a way that makes it easy for custom backends to
ignore copy info entirely until they are ready to implement it.

### Desired UX

The following sections describe some scenarios and how we would ideally handle
them.

We have not seen much reason to distinguish copies from renames, so a rename
is simply the same thing as a copy plus a deletion. This means that we cannot
distinguish "copy `foo` to `bar` and rename `foo` to `baz`" from "copy `foo`
to `baz` and rename `foo` to `bar`".

#### Restoring from a commit should preserve copies

For example, `jj new X--; jj restore --from X` should restore any copies
made in `X-` and `X` into the new working copy. Transitive copies should
be "flattened". For example, if `X-` renamed `foo` to `bar` and `X` renamed
`bar` to `baz`, then the restored commit should rename `foo` to `baz`.

This also applies to reparenting in general, such as for
["verbatim rebase"](https://github.com/martinvonz/jj/issues/1027).

#### Diff after restore

`jj restore --from X; jj diff --from X` should be empty, at least when it comes
to file contents. It may indicate that renamed file have different history.

#### Lossless round-trip of rebase

Except for the [`A+(A-B)=A` rule][same_change_rule], rebasing is currently never
lossy; rebasing a commit and then rebasing it back yields the same content. We
should ideally preserve this property when possible.

For example:

```console
$ jj log
C rename bar->baz
|
B rename foo->bar
|
A add foo

$ jj rebase -r C -o A
$ jj rebase -r C -o B # Takes us back to the state above
```

#### Reverting the parent commit should be a no-op

Patches should be reversible so you can make a change and then revert it, and
end up with an empty diff across both commits.

For example:

```console
$ jj log
B rename foo->bar
|
A add foo

$ jj revert -r B -o B
$ jj diff --from B- --to B+ # Should be empty
```

#### Parallelize/serialize

This is a special case of the lossless rebase.

```console
$ jj log
E edit qux
|
D rename baz->qux
|
C rename bar->baz
|
B rename foo->bar
|
A add foo

$ jj parallelize B::D
# There should be no conflict in E and it should look like a
# regular edit just like before
$ jj rebase -r C -A B
$ jj rebase -r D -A C
# Now we're back to the same graph as before.
```

#### Copies inside merge commit

We should be able to resolve a naming conflict:

```console
$ jj log
D  resolve naming conflict by choosing `foo` as the source
|\
C | rename bar->baz
| |
| B rename foo->baz
|/
A add foo and bar

$ jj file annotate baz # Should not include changes from C
```

We should also be able to back out that resolution and get back into the
name-conflicted state.

We should be able to rename files that exist on only one side:

```console
$ jj log
D  rename foo2->foo3 and bar2->bar3
|\
C | rename bar->bar2
| |
| B rename foo->foo2
|/
A add foo and bar
```

#### Copies across merge commit

```console
$ jj log
D delete baz
|\
C | rename foo->baz
| |
| B rename foo->bar
|/
A add foo
```

`jj diff --from C --to D` should now show a baz->bar rename (just like
`jj diff --from C --to B` would). `jj diff --from B --to D` should show
no renames. That's despite there being a rename in C.

## High-level Design

Jujutsu uses a snapshot-based model similar to Git's. The algebra for our
first-class conflicts is also based on snapshots and being able to calculate
patches as differences between states. That means that we have to fit copy
information into that snapshot-based model too [^martinvonz_slow].

The proposal is to update tree objects to also contain information about a
file's past names. For example, if file `foo` gets renamed to `bar` in one
commit and then to `baz` in another commit, we will record that `baz` previously
had names `bar` and `foo`.

To support merging two files into one, the list of past names is actually a DAG.
Merging can happen in a merge commit when two sides copy/rename different
source files to the same target file. By having support for it in the model, we
can also support merging multiple files into one in a regular non-merge commit.

To avoid having to store all past paths in the tree object entry, we will write
the copy history as an object and the tree will refer to the object by ID. Each
ID refers to a node in the copy history DAG, similar to how commit IDs refer to
a node in the commit DAG.

Each node in the copy history DAG stores the path. Having the path in the copy
graph can be useful for finding copy sources without having to scan the whole
tree or having to ask the backend.

If we use only the file name as only input to the ID, then we get deterministic
tree IDs. On the other hand, if we add a salt to the copy graph node, then we
can represent that a file was rewritten from scratch. For example, a `foo` might
have copy ID `123` in the previous commit and when the file gets rewritten in
the current commit, it gets copy ID `456` even though there was no copy from an
existing file involved. That makes logical sense, but I'm not sure how useful
it will be.

The data structure might look like this:

```rust
// Current `TreeValue::File` variant:
File { id: FileId, executable: bool },
// New `TreeValue::File` variant:
File { id: FileId, executable: bool, copy_id: CopyId },

// A CopyId is a hash of this struct:
struct CopyHistory {
    path: RepoPath,
    parents: Vec<CopyId>
}
```

Should we support copy tracking for symlinks? Their history is not very useful
for annotation purposes, but knowing the history may at least be useful for
detecting directory renames (if all files and symlinks in a directory were
renamed).

We probably should not support tracking copied directories because it seems
complicated. I haven't spent much thinking about it, so it's also possible that
it's not that complicated.

### Diffing

When diffing two trees, we first diff the trees without considering copy info.
For any copy IDs that changed in that diff, we walk all of their copy graphs to
figure out how they're related and which source file to associate with which
destination file.

The details of the algorithm is left for the implementation. The following
sections provide some examples to hopefully show that it's feasible.

#### Example: Divergent copy and rename

Let's look at an example of how this model would look in this scenario:

```console
M rename foo->baz, create bar
|
| L copy foo->bar, create baz
|/
K add foo
```

Assuming the new files are different in each commit, we get the following trees.
Notation:
* `id`is the hash of the contents (the `FileId`)
* The `2:bar->1:foo` means that copy ID 2 (i.e. hash of the `CopyHistory`
  struct) has file `bar`, which was copied from copy ID `1`, where it was
  called `foo`.

```console
Commit K:
name: foo, id: K, copy_id: 1:foo

Commit L:
name: bar, id: L, copy_id: 2:bar->1:foo
name: baz, id: L, copy_id: 3:baz
name: foo, id: K, copy_id: 1:foo

Commit M:
name: bar, id: M, copy_id: 4:bar
name: baz, id: M, copy_id: 5:baz->1:foo
```

This graph also shows the relationship between the copy IDs and which commits
they appear in:

```mermaid
graph LR

    subgraph L["Commit L"]
        2["2:bar"]
        3["3:baz"]
        subgraph K["Commit K"]
            1["1:foo"]
        end
    end

    subgraph M["Commit M"]
        4["4:bar"]
        5["5:baz"]
    end

    2 --> 1
    5 --> 1
```

Let's first consider the diff from `K` to `M`. Looking at just the trees, that
diff finds that copy IDs 1,4,5 were affected. By walking their graphs, we find
1 and 5 are related, while 4 is not. Considering that copy graph (involving IDs
1 and 5), since `foo` doesn't exist in the destination and `baz` doesn't exist
in the source, we consider it a rename.

Let's now consider the diff from `L` to `M`. This is the same as the diff of the
commit `M2` we'd get by running
`jj new L; jj bookmark create M2; jj restore --from M --to M2` (which would
result in the commit `M2` having the same tree as `M`). Diffing from `L` to
`M` (or `M2`) finds 1,2,3,4,5 as changed copy IDs. By walking their graphs, we
find that 1,2, and 5 are related, while 3 and 4 are not.

The `bar` and `baz` files have unrelated copy graphs, i.e. the copy graphs for
the `bar` file in commit `L` and the `bar` file in commit `M` are disjoint, and
the same is true for the `baz` file. Therefore, we break up their diffs into two
separate diffs for each file.

Among the remaining copy IDs, the shortest path in the copy graph is between
`foo` on the source side and `baz` on the destination side, so we start with these.
Since `foo` doesn't exist on the destination side and `baz` doesn't exist on the
source side (with a related copy ID), we consider it a rename.

The remaining file is `bar` on the source side. Its closest relative on the
destination side is `baz`. Since we already used `baz` as a rename target for
`foo`, we won't consider `bar` renamed to it. So we consider `bar` as copied
into `baz`.

So we get these diffs:

* `baz` is deleted (deleting content `L`)
* `bar` is created (with content `M`)
* `foo` is renamed to `baz` (showing diff from `K` to `M`)
* `bar` is merged into `baz` (showing diff from `L` to `M`)

#### Example: Divergent copy and rename (best rename target)

```console
N copy baz->qux
|
M rename foo->baz
|
| L rename foo->bar
|/
K add foo
```

When diffing `L` to `N`, we find that all files are related. Since `bar` does
not exist in the destination, we should find a rename target to match it with.
We pick `baz` because it's closer in the graph than `qux` is. So the diff is:

* `bar` is renamed to `baz`
* `bar` is copied to `qux`

#### Example: Copy onto deleted file

```console
M copy foo->bar
|
L delete bar
|
K add foo, bar
```

When diffing from `K` to `M`, we notice that `bar` has different and unrelated
copy IDs. We present one record saying that `bar` was deleted, and one record
saying that `bar` was copied from `foo`.

When diffing from `M` to `K`, we will instead present one record that says that
`bar` was created, and one record that says that `bar` was merged into `foo`.

### Merging

When merging, we need to add a phase before the content-level merging where we
handle copies. As before, we start by creating the completely unresolved merged
tree based on the input trees. To find the relevant copy information, we look at
the files changed in each diff and then look up the full copy graph for each.
For each copy graph, we can then walk the copy graph to find possible target
paths, which we then look up in the other side of the merge. If the path exists
in the tree and has the right copy ID, then we know that the files are related.

We assume that the differences between the bases and the first term in the
conflict can be very large, so we don't look at that diff. Assuming that the
commit backend can look up the full copy graph based on a given copy ID, we
don't need that diff for correctness.

Once we have found all copies involved in the merge, we analyze them to find
conflicts, such as when two sides of the merge rename a file to the same
target. If there are conflicts, we leave the trees unchanged. The user can then
resolve the name conflicts using `jj resolve` (once we've added support for
that). Depending on how slow the naming conflict phase turns out to be, we may
want to write a flag to commits indicating that they have unresolved naming
conflicts, so subsequent calls can avoid that phase.

When merging trees, we start by rewriting each diff to match any different names
in the destination tree. For example, if the tree conflict is `A+(B-C)+(D-E)`,
then we will rewrite the `(B-C)` diff and the `(D-E)` diff to the paths in `A`.
To translate the `(B-C)` diff, we calculate renames from `C` to `A` and then we
apply those renames to both `C` and `B`. This may result in conflicts.

If a file has a conflict in the copy ID, it will appear as if it doesn't exist
when materialized. It will therefore not show up in the working copy until the
user has resolved the conflict.

For example:

```console
M set foo="bye"
|
| L rename foo->bar
|/
K add foo="hello"
```

When rebasing `M` onto `L`, we apply the `foo->bar` rename to the trees in `M`
and its parent.

Another example:

```console
N rename foo->bar
|
| M create foo="M"
| |
| L delete foo
|/
K add foo="K"
```

When rebasing `M` onto `N`, we find the `foo->bar` rename in `N`, but since it
is unrelated to the `foo` file in `M` (assuming the `foo` file created in `M`
used a different salt), we will not perform any renames. The new `foo` file
is then simply created in the rebased `M` just like it was before the rebase.

#### Propagating changes across copies?

Should we propagate changes to copies? For example, if you've modified file
`foo` and then rebase it onto a commit that copied `foo` to `bar`, should we
apply your change to `bar` too? Mercurial does that but Git doesn't. It's
particularly useful when a file has been split in two. For example, let's say
you've made various changes in file `foo` and then rebase those change onto a
commit that split `foo` into `foo1` and `foo2` (or `foo` and `bar`). If we
propagate the changes to both files, each change will apply successfully in one
file (assuming the changes do not overlap with the split boundary). Each change
will have a modify/delete conflicts in the other file. Those can relatively
easily be resolved in favor of the deleted hunk. If we do not propagate changes,
then changes that belong in one of the files will instead only appear as
modify/delete conflicts in the first file and you will have to manually copy
over the changes to the copied file.

Propagating changes to copies means that rebasing a commit and then rebasing it
back is no longer a no-op even when ignoring the "same-change rule". For
example, if your commit modifies file `foo` and you rebase that commit onto a
commit that copied `foo` to `bar`, and then you rebase it back, the same change
will be applied twice to `foo`. However, thanks to the same-change rule, we
won't consider it a conflict, so maybe it actually works well in practice.

A third option is to not leave it up to the user whether to propagate the
change across the copy. We can do this by leaving the relevant paths in the
input trees unchanged in the conflicted commit. Then we will redo the copy
tracking process every time the commit is inspected. We can have `jj resolve`
ask the user if they want to propagate the changes to the copy target with a
simple yes/no question per copy target.

Decision: Asking the user about propagating copies seems like the best option.
It avoids surprises, and it makes the conflict algebra work in more cases.

#### Example: Propagate changes to copied file, then rebase back

```console
M foo="M"
|
| L copy foo->bar
|/
K add foo="K"
```

Let's say we rebase `M` onto `L`. Since we decided to not automatically
propagate changes to copies, we will leave the `M+(L-K)` tree unresolved (i.e.
without making any changes to the three trees). If the user does not resolve
the conflict, and instead rebases `M` back onto `K`, the conflict will be
resolved automatically per the usual conflict simplification.

#### Example: Multiple copies

```console
N foo="N"
|
| M foo="M, foo2="M2", foo3="M3"
| |
| L copy foo->foo2, copy foo->foo3
|/
K add foo="K"
```

Let's say we rebase `M` onto `N`. The changes to `foo`, `foo2`, and` foo3` will
then all apply to `foo`, which means we get a 4-sided conflict.

#### Example: Convergent renames

Consider this "convergent copy/rename" scenario:

```console
$ jj log
C rename bar->baz
|
| B rename foo->baz
|/
A add foo, add bar

$ jj new B C
```

It seems clear that `baz`'s copy graph should inherit from both `foo` and `bar`,
producing a merge in copy graph. The trees would look like this:

```
Commit A:
name: foo, id: aaa111, copy_id: 1:foo
name: bar, id: aaa111, copy_id: 2:bar

Commit B:
name: bar, id: aaa111, copy_id: 2:bar
name: baz, id: aaa111, copy_id: 3:baz->1:foo

Commit C:
name: foo, id: aaa111, copy_id: 1:foo
name: baz, id: aaa111, copy_id: 4:baz->2:bar

Merge commit:
name: baz, id: aaa111, copy_id: 5:baz->{3:baz->1:foo,4:baz->2:bar}
```

We used the same content for both `foo` and `bar` above to simplify. If they
had been different, we would have had a conflict in the contents but the copy
ID would still have been clear.

#### Example: Rebasing

```console
$ jj log
C rename bar->baz
|
B rename foo->bar
|
A add foo

$ jj rebase -r C -o A
```

```console
$ jj log
C rename foo->baz
|
| B rename foo->bar
|/
A add foo

$ jj rebase -r C -o B
```

#### Example: Rename added file

A well-known and thorny problem in Mercurial occurs in the following scenario:

```console
$ jj log
C rename foo->bar
|
| B modify foo
|/
A add foo

$ jj squash --from C --into A
```

The problem here for Mercurial is that after squashing C into A, the new A has
file `bar` but no record that it used to be called `foo`. The design proposed
above handles this case because we keep the copy ID of `bar` after squashing,
so we can detect that the modifications to `foo` in commit B should be
propagated to `bar`.

#### Example: Divergent renames

Consider this "divergent rename" scenario:

```console
$ jj log
C rename foo->baz
|
| B rename foo->bar
|/
A add foo

$ jj new B C
```

In this scenario, the regular 3-way merge of the trees without considering copy
info results in a tree without conflicts. However, the user might reasonably
expect to have to choose between the `bar` and `baz` names. Here's what Git says
in this scenario:

```console
$ git merge main
CONFLICT (rename/rename): foo renamed to baz in HEAD and to bar in main.
Automatic merge failed; fix conflicts and then commit the result.

$ git st
HEAD detached from ab0b8e3
You have unmerged paths.
  (fix conflicts and run "git commit")
  (use "git merge --abort" to abort the merge)

Unmerged paths:
  (use "git add/rm <file>..." as appropriate to mark resolution)
        added by them:   bar
        added by us:     baz
        both deleted:    foo
```

Interestingly, Git seems to represent this state by using index states that
would not normally end up in the index as a result of conflicts.

Here's what Mercurial says:

```console
$ hg merge main
note: possible conflict - foo was renamed multiple times to:
 bar
 baz
1 files updated, 0 files merged, 0 files removed, 0 files unresolved
(branch merge, don't forget to commit)
```

Mercurial doesn't have a place to record this state, so it just prints that
note and leaves it at that.

The model and algorithm described in this document would result in a conflict
in the copy ID at both paths after propagating the renames.

#### Example: @jonathantanmy's test case:

TODO: fill this out

```console
$ jj log
E baz="baz" (resolves conflict)
|
D <conflict>
|\
C | rename bar->baz
| |
| B rename foo->baz
|/
A add foo="foo" and bar="bar"

$ jj rebase -r E -o C
$ jj new D E -m F
```

If F is empty (auto-merged), it should have the same state as E before.

### Log

The copy graph contains all past paths and copy IDs of a file, so when doing
`jj log <filename>`, we might want to translate that to a revset that's similar
to `files()` but matches specific (path, copy ID) pairs instead of specific
paths.

### Annotate

TBD

### Representation in Git

Do we ever want to record renames in the Git backend? If we do, we would
presumably store it outside the Git object, similar to how we store the change
id for commits.

What do we use for trees where we *don't* have any copy graph recorded? If we
simply create a new copy graph based on the current path, then the caller will
never find any copies. Do we need an indexing pass to detect all renames in a
repo when running `jj git init`? That can be very expensive for large repos.
For reference, `git log --summary --find-copies-harder` takes about 165 seconds
in the git.git repo on my computer, and about 13 hours in the Nixpkgs repo.

An alternative is to do copy indexing in the background after cloning a repo.
That would mean that copy information would not show up until some time later.
It would also be more work to implement it this way.

How to deal with two trees having the same content but different file ids?
Actually store the additional data linked from the commit object? That would
not work if we point to trees from somewhere that's not a commit. We point to a
tree from the working-copy state.

One could imagine not storing any copy info in Git and instead making the model
described above an implementation detail of the backend. Then it could be used
by the native backend and the Google backend, while we still use on-the-fly
copy detection in the Git backend. However, if we want to be able to tell the
user about details of conflicting copy IDs so they can decide how to resolve
such conflicts, then we would have to somehow represent that abstractly too.

### Representation in cloud repo (e.g. Google)

Let's say you have a commit with some files you've modified. You now want to
sync (rebase) that to an updated main branch. If some of the files you modified
no longer exist on the main branch, we want to figure out if they were renamed
so we should propagate your changes to the new file location. As described
earlier, we can do that by finding files that have a different copy ID since the
last time you synced with the main branch. However, if there are 10 million new
commits on the main branch, there's perhaps tens of thousands of such files
spread across the entire tree. That can therefore be very expensive to
calculate. We therefore need to be able to get help from a custom backend
implementation with this query.

Since we are only interested in copy graphs that involve files modified in the
rebased commit, it should be sufficient if the backend provides a method to
fetch the whole copy graph for a given copy ID (or list of copy IDs). We would
then first find all copy IDs involved in the diff of the rebased commit. Then
we query the backend to get the full copy graphs. We then need to walk the copy
graphs to see if a node exists in the destination tree.

A weakness of this solution is that the search gets expensive if there are very
many related files. That's probably not much of a problem in practice. The
server might want to populate the index only for public/immutable commits.
Otherwise, a user could poison the index by creating tons of copies
(intentionally or by mistake), which would make all future queries about those
files expensive.

## Implementation plan

A rough implementation plan may look like this:

1. Implement support for copy-tracking in the test backend
3. Implement diff algorithm and test it
4. Implement merge algorithm and test it
5. Implement blame algorithm and test it
6. Implement file-following log algorithm and test it
7. Extract some queries to the commit backend trait so cloud-based backends
   (like the Google backend) can provide versions implemented using database
   indexes
8. Implement support for copy-tracking in the Git backend. This may involve
   backfilling, possibly lazily. Or it may involve new abstractions in the
   commit backend trait.
9. Implement CLI for recording copies and for resolving conflicts in copies

## Alternatives considered

### Detect copies (like Git)

Git doesn't record copy info. Instead, it infers it when comparing two trees.

It's hard to make this model scale to very large repos. For example, let's say
you're rebasing your local commit to a new upstream commit that's 1 million
commits ahead. We would then want to find if any of the files in your local
commit has been copied upstream. That's very expensive to do by comparing the
old and the new base trees. However, since the query APIs defined above take
commits (not trees) as input, we allow the backend to take the history into
account when calculating the copies. A backend can then create an index based
on the input files (in your local commit) and find if it's been copied without
comparing the full trees.

### Record logical file identifiers in trees (BitKeeper-like model)

BitKeeper records a file ID (which identifies a logical file, unlike our `FileId`
type) for each path (or maybe it's a path for each file ID). That way you can
compare two arbitrary trees, find the added and deleted files and just compare
the file IDs to figure out which of them are renames.

This model doesn't seem to be easily extensible to support copies (it only
supports renames).

To perform a rebase across millions of commits, we would not want to diff the
full trees because that would be too expensive (probably millions of modified
files). We could perhaps instead find renames by bisecting to find commits that
deleted any of the files modified in the commit we're rebasing.

Another problem is how to synthesize the file IDs in the Git backend. That could
perhaps be done by walking from the root commits and persisting an index.

### Include copy info in the FileId (Mercurial-like model)

Mercurial stores copy info in a metadata section in the file content itself
[^mercurial_changeset_copies]. That means that a file will get a new file
(content) ID if its copy history changes. That's quite similar to the proposal
in this document. One difference is that Mercurial's model stores information
only about the most recent copy. If the file is then modified, it will get a new
file ID. One therefore has to walk the history of the file to find the previous
name (which is usually not much of a problem because Mercurial stores a revision
DAG per file in addition to the revision DAG at the commit level).

### Hybrid snapshot/patch model with copy info stored in commits

We considered storing copy info about the copies/renames in the commit object.
That has some significant impact on the data model:

* Without copy info, if there's a linear chain of commits A..D, you can find
  the total diff by diffing just D-A. That works because (B-A)+(C-B)+(D-C)
  simplifies to just D-A. However, if there is copy info, the total diff will
  involve copy info. If that's associated with the individual commits, we will
  need to aggregate it somehow.
* Restoring from another tree is no longer just a matter of copying that tree;
  we also need to figure out copies between the old tree and the new tree.
* Conflict states are represented by a series of states to add and remove. This
  does not work with the patch-based copy info. We spent a lot of time trying
  to figure out a solution that works, but it seems like the snapshot-based
  conflict model and the patch-based copy info model are not reconcilable.
  Therefore, we won't track conflicted copy info, such as between a `foo`->`baz`
  rename and a `bar`->`baz` rename.
* Since copy records are relative to the auto-merged parents, that unfortunately
  means that the records will depend on the merge algorithm, so it's possible
  that a future change to the merge algorithm will make some copy records
  invalid. We will therefore need to not assume that the copy source exists.

For the state in conflicted commits, we considered using a representation like
this:

```rust
struct MergedTree {
    snapshot: Tree,
    diffs: Diff
}

struct Diff {
    before: Tree,
    after: Tree,
    /// Copies from `before` to `after`
    copies: Vec<CopyInfo>,
    /// Copies from `before` to `snapshot`
    copies_to_snapshot: Vec<CopyInfo>,
}

struct CopyInfo {
    source: RepoPathBuf,
    target: RepoPathBuf,
    // Maybe more fields here for e.g. "do not propagate"
}
```

That works for calculating the resulting tree, but it does not seem to allow for
doing the conflict algebra we currently do. That means that things like
parallelizing commits and then serializing them again would lose copy
information.

[same_change_rule]: https://github.com/martinvonz/jj/blob/560d66ecee5a9904b42dbc0b89333f0c27c683de/lib/src/merge.rs#L98-L111

[^martinvonz_slow]: This took me (@martinvonz) months to really understand.
[^mercurial_changeset_copies]: From around
    <https://repo.mercurial-scm.org/hg/rev/49ad315b39ee>, Mercurial also
    supports storing copy info in commits. That made it the kind of
    snapshot/patch model we described above as not working well.

---

### Design Git Submodule Storage (`docs/design/git-submodule-storage.md`)

# Git submodule storage

## Objective

Decide what approach(es) to Git submodule storage we should pursue.
The decision will be recorded in [./git-submodules.md](./git-submodules.md).

## Use cases to consider

The submodule storage format should support the workflows specified in the
[submodules roadmap](./git-submodules.md). It should be obvious how "Phase 1"
requirements will be supported, and we should have an idea of how "Phases 2,3,X"
might be supported.

Notable use cases and workflows are noted below.

### Fetching submodule commits

Git's protocol is designed for communicating between copies of the same
repository. Notably, a Git fetch calculates the list of required objects by
performing reachability checks between the refs on the local and the remote
side. We should expect that this will only work well if the submodule repository
is stored as a local Git repository.

Rolling our own Git fetch is too complex to be worth the effort.

### "jj op restore" and operation log format

We want `jj op restore` to restore to an "expected" state in the submodule.
There is a potential distinction between running `jj op restore` in the
superproject vs in the submodule, and the expected behavior may be different in
each case, e.g. in the superproject, it might be enough to restore the submodule
working copy, but in the submodule, refs also need to be restored.

Currently, the operation log only references objects and refs in the
superproject, so it is likely that proposed approaches will need to extend this
format. It is also worth considering that submodules may be added, updated or
removed in superproject commits, thus the list of submodules is likely to change
over the repository's lifetime.

### Nested submodules

Git submodules may contain submodules themselves, so our chosen storage schemes
should support that.

We should consider limiting the recursion depth to avoid nasty edge cases (e.g.
cyclical submodules.) that might surprise users.

### Supporting future extensions

There are certain extensions we may want to make in the future, but we don't
have a timeline for them today. Proposed approaches should take these
extensions into account (e.g. the approach should be theoretically extensible),
but a full proposal for implementing them is not necessary.

These extensions are:

- Non-git subrepos
- Colocated Git workspace
- The superproject using a non-git backend

## Proposed design

Git submodules will be stored as full jj repos. In the code, jj commands will
only interact with the submodule's repo as an entire unit, e.g. it cannot query
the submodule's commit backend directly. A well-abstracted submodule will extend
well to non-git backends and non-git subrepos.

The main challenge with this approach is that the submodule repo can be in a
state that is internally valid (when considering only the submodule's repo), but
invalid when considering the superproject-submodule system. This will be managed
by requiring all submodule interactions go through the superproject so that
superproject-submodule coordination can occur. For example, jj will not allow
the user to work on the submodule's repo without going through the superproject
(unlike Git).

The notable workflows could be addressed like so:

### Fetching submodule commits

The submodule would fetch using the equivalent of `jj git fetch`. It remains to
be decided how a "recursive" fetch should work, especially if a newly fetched
superproject commit references an unfetched submodule commit. A reasonable
approximation would be to fetch all branches in the submodule, and then, if the
submodule commit is still missing, gracefully handle it.

### "jj op restore" and operation log format

As full repos, each submodule will have its own operation log. We will continue
to use the existing operation log format, where each operation log tracks their
own repo's commits. As commands are run in the superproject, corresponding
commands will be run in the submodule as necessary, e.g. checking out a
superproject commit will cause a submodule commit to also be checked out.

Since there is no association between a superproject operation and a submodule
operation, `jj op restore` in the superproject will not restore the submodule to
a previous operation. Instead, the appropriate submodule operation(s) will be
created. This is sufficient to preserve the superproject-submodule relationship;
it precludes "recursive" restore (e.g. restoring branches in the superproject
and submodules) but it seems unlikely that we will need such a thing.

### Nested submodules

Since submodules are full repos, they can contain submodules themselves. Nesting
is unlikely to complicate any of the core features, since the top-level
superproject/submodule relationship is almost identical to the submodule/nested
submodule relationship.

### Extending to colocated Git workspaces

Git expects submodules to be in `.git/modules`, so it will not understand this
storage format. To support colocated Git workspaces, we will have to change Git
to allow a submodule's gitdir to be in an alternate location (e.g. we could add
a new `submodule.<name>.gitdir` config option). This is a simple change, so it
should be feasible.

## Alternatives considered

### Git repos in the main Git backend

Since the Git backend contains a Git repository, an 'obvious' default would be
to store them in the Git superproject the same way Git does, i.e. in
`.git/modules`. Since Git submodules are full repositories that can have
submodules, this storage scheme naturally extends to nested submodules.

Most of the work in storing submodules and querying them would be well-isolated
to the Git backend, which gives us a lot of flexibility to make changes without
affecting the rest of jj. However, the operation log will need a significant
rework since it isn't designed to reference submodules, and handling edge cases
(e.g. a submodule being added/removed, nested submodules) will be tricky.

This is rejected because handling that operation log complexity isn't worth it
when very little of the work extends to non-Git backends.

### Store Git submodules as alternate Git backends

Teach jj to use multiple commit backends and store Git submodules as Git
backends. Since submodules are separate from the 'main' backend, a repository
can use whatever backend it wants as its 'main' one, while still having Git
submodules in the 'alternate' Git backends.

This approach extends fairly well to non-Git submodules (which would be stored
in non-Git commit backends). However, this requires significantly reworking the
operation log to account for multiple commit backends. It is also not clear how
nested submodules will be supported since there isn't an obvious way to
represent a nested submodule's relationship to its superproject.

---

### Design Git Submodules (`docs/design/git-submodules.md`)

# Git submodules

This is an aspirational document that describes how jj _will_ support Git
submodules. Readers are assumed to have some familiarity with Git and Git
submodules.

This document is a work in progress; submodules are a big feature, and relevant
details will be filled in incrementally.

## Objective

This proposal aims to replicate the workflows users are used to with Git
submodules, e.g.:

- Cloning submodules
- Making new submodule commits and updating the superproject
- Fetching and pushing updates to the submodule's remote
- Viewing submodule history

When it is convenient, this proposal will also aim to make submodules easier to
use than Git's implementation.

### Non-goals

- Non-Git 'submodules' (e.g. native jj submodules, other VCSes)
- Non-Git backends (e.g. Google internal backend)
- Changing how Git submodules are implemented in Git

## Background

We mainly want to support Git submodules for feature parity, since Git
submodules are a standard feature in Git and are popular enough that we have
received user requests for them. Secondarily (and distantly so), Git submodules
are notoriously difficult to use, so there is an opportunity to improve the UX
over Git's implementation.

### Intro to Git Submodules

[Git submodules](https://git-scm.com/docs/gitsubmodules) are a feature of Git
that allow a repository (submodule) to be embedded inside another repository
(the superproject). Notably, a submodule is a full repository, complete with its
own index, object store and ref store. It can be interacted with like any other
repository, regardless of the superproject.

In a superproject commit, submodule information is captured in two places:

- A `gitlink` entry in the commit's tree, where the value of the `gitlink` entry
  is the submodule commit id. This tells Git what to populate in the working
  tree.

- A top level `.gitmodules` file. This file is in Git's config syntax and
  entries take the form `submodule.<submodule-name>.*`. These include many
  settings about the submodules, but most importantly:

  - `submodule<submodule-name>.path` contains the path from the root of the tree
    to the `gitlink` being described.

  - `submodule<submodule-name>.url` contains the url to clone the submodule
    from.

In the working tree, Git notices the presence of a submodule by the `.git` entry
(signifying the root of a Git repository working tree). This is either the
submodule's actual Git directory (an "old-form" submodule), or a `.git` file
pointing to `<superproject-git-directory>/modules/<submodule-name>`. The latter
is sometimes called the "absorbed form", and is Git's preferred mode of
operation.

## Roadmap

Git submodules should be implemented in an order that supports an increasing set
of workflows, with the goal of getting feedback early and often. When support is
incomplete, jj should not crash, but instead provide fallback behavior and warn
the user where needed.

The goal is to land good support for pure Jujutsu workspaces, while colocated
workspaces will be supported when convenient.

This section should be treated as a set of guidelines, not a strict order of
work.

### Phase 1: Readonly submodules

This includes work that inspects submodule contents but does not create new
objects in the submodule. This requires a way to store submodules in a jj
repository that supports readonly operations.

#### Outcomes

- Submodules can be cloned anew
- New submodule commits can be fetched
- Submodule history and branches can be viewed
- Submodule contents are populated in the working copy
- Superproject gitlink can be updated to an existing submodule commit
- Conflicts in the superproject gitlink can be resolved to an existing submodule
  commit

### Phase 2: Snapshotting new changes

This allows a user to write new contents to a submodule and its remote.

#### Outcomes

- Changes in the working copy can be recorded in a submodule commit
- Submodule branches can be modified
- Submodules and their branches can be pushed to their remote

### Phase 3: Merging/rebasing/conflicts

This allows merging and rebasing of superproject commits in a content-aware way
(in contrast to Git, where only the gitlink commit ids are compared), as well as
workflows that make resolving conflicts easy and sensible.

This can be done in tandem with Phase 2, but will likely require a significant
amount of design work on its own.

#### Outcomes

- Merged/rebased submodules result in merged/rebased working copy content
- Merged/rebased working copy content can be committed, possibly by creating
  sensible merged/rebased submodule commits
- Merge/rebase between submodule and non-submodule gives a sensible result
- Merge/rebase between submodule A and submodule B gives a sensible result

### Phase ?: An ideal world

I.e. outcomes we would like to see if there were no constraints whatsoever.

- Rewriting submodule commits rewrites descendants correctly and updates
  superproject gitlinks.
- Submodule conflicts automatically resolve to the 'correct' submodule commits,
  e.g. a merge between superproject commits creating a merge of the submodule
  commits.
- Nested submodules are as easy to work with as non-nested submodules.
- The operation log captures changes in the submodule.

## Design

### Guiding principles

TODO

### Storing submodules

Possible approaches under discussion. See
[./git-submodule-storage.md](./git-submodule-storage.md).

### Snapshotting new submodule changes

TODO

### Merging/rebasing with submodules

TODO

---

### Design Jj Converge Command (`docs/design/jj-converge-command.md`)

# jj converge (aka resolve-divergence) Command Design

Authors: [David Rieber](mailto:drieber@google.com),
[Martin von Zweigbergk](mailto:martinvonz@google.com)

**Summary:** This document is a proposal for a new `jj converge` command to help
users resolve (or reduce) divergence. The command will use heuristics -- and
sometimes will prompt the user for input -- to rewrite the N visible commits for
a given change with a single new commit, without introducing new divergence in
the process. `jj resolve-divergence` will be an alias for `jj converge`.

## Objective

A [divergent change] occurs when multiple [visible commits] have the same change
ID. Divergence is not a desirable state, but is not a bad state either. In this
regard divergence is similar to conflicts: the user can choose when and how to
deal with divergence. The [Handling divergent commits] guide has some useful
tips, but nevertheless divergence is confusing to our users. We can do better
than that. It should be possible to solve divergence (after the fact) in many
scenarios with the help of this command. Solving divergence means rewriting the
commit graph to end up with a single visible commit for the given change id. For
the purposes of this design doc we call this commit the *"solution"*.

The command should produce informative messages to summarize any changes made,
and will prompt for user input in some situations. The user may of course not
like the solution. `jj undo` can be used in that case.

[divergent change]: ../glossary.md#divergent-change
[visible commits]: ../glossary.md#visible-commits
[Handling divergent commits]: ../guides/divergence.md

## Divergent changes

Divergent commits (for the same change-id) can differ:

*   In their commit description (including tags)
*   In their commit trees
*   In the parent(s) of the commits (commits *B/0* and *B/1* for change *B* have
    different parents)
*   In the commit author
*   It is also possible divergence involves two commits with different
    timestamps that are otherwise identical

As you read this design doc it is important to not confuse the
*predecessor / successor* relationship versus the *ancestor / descendant*
relationship.

### Some divergence scenarios

Divergence can be introduced in many ways. This document does not aim to explain
any / all of those scenarios accurately, this section is only meant to be rough
background material. Here are some examples:

*   In one terminal you type `jj describe` to edit a commit description and
    while the editor is open you take a coffee break, when you come back you
    open another terminal and do something that rewrites the commit (for example
    you modify a file and run `jj log`, causing a snapshot). When you save the
    new description `jj describe` completes and you end up with 2 visible
    commits with the same change id.

*   In general any interactive jj command (`jj split -i`, `jj squash -i`, etc.)
    can lead to divergence in a similar way.

*   You can introduce divergence by making some hidden predecessor of your
    change visible again. There are many ways this could happen.

*   Divergence can happen when mutating two workspaces. For example, assume you
    have workspaces w1 and w2 with working copy commits *A* and *B*
    respectively, where *B* is a child of *A*. In w2 you run `jj git fetch` and
    then rebase the whole branch onto main. Go back to w1 (which is now stale),
    modify some file on disk and take a snapshot (e.g. run `jj log`). This
    introduces divergence.

*   When using the Git backend jj propagates change-id. The change-id is stored
    in the commit header, so after jj git fetch you can end up with a second
    commit with the same change-id.

*   There is a Google-specific jj upload command to upload a commit to Google's
    review / test / submit system, and there is an associated Google-specific
    command to "download" a change from that system back to your jj repo. This
    can introduce divergence very much like in the Git scenario.

*   At Google, snapshotting operations happen concurrently on different machines
    (e.g. two terminals, or more commonly, a terminal and an IDE). Often times
    they end up snapshotting the same content. Google's backend does not hold
    locks while snapshotting because it's a distributed filesystem, so locking
    would be slow.

## Strawman proposal

We look at some examples to illustrate what the command should do, starting with
simple cases and moving on to more complex ones.

### Examples and expected behavior (with basic evolution graph)

The first few examples assume commits *B/0* and *B/1* are visible commits for
change *B*. First we assume *B/0* and *B/1* evolve directly from a common
predecessor commit *P*, which is now hidden (no longer visible). Later we look
at more complex evolution graphs. Note that *P*'s change id is also *B*.

```
Evolution graph for examples 1, 2, 3 and 4.
B/0 and B/1 may have other predecessors for unrelated change-ids, P may have
predecessors (even for change-id B):

B/0
|
| B/1
|/
P
```

We will write `A⁻` to denote the parent trees of commit `A`.

#### Example 1: two commits for change *B*, same parent

```console
$ jj log
B/0
|
| B/1
|/
A
```

In this simple case it is clear the solution should be a child of *A*:

```console
$ jj log
 B (solution)
 |
 | B/0 (not visible)
 |/
 | B/1 (not visible)
 |/
 A
```

Let's now consider two cases: when *P*'s parent is *A*, and when *P* has some
other parent. First, if *P*'s parent is *A* we have:

```console
$ jj log
B/0
|
| B/1
|/
| P (not visible)
|/
A
```

Here *P*, *B/0* and *B/1* are siblings. The command needs to determine the
description, parents, tree and author of the solution. It uses a simple data
structure for this purpose:

```rust
struct MergedState {
  author: Merge<Signature>,
  description: Merge<String>,
  parents: Merge<Vec<CommitId>>,
  tree: Merge<MergedTree>,
}
```

Each of the fields of `MergedState` are populated by doing a merge of the
corresponding fields of *P*, *B/0* and *B/1*. Loosely speaking each merge can be
expressed as `P + (B/0 - P) + (B/1 - P)` for each of the fields. The command
attempts to resolve the various Merge objects trivially, using `same_change:
SameChange::Accept` (later on in this design doc we will tweak the merge
algorithm a bit).

The description is merged as a String value. If the description does not
trivially resolve, the user's merge tool will be invoked, with conflict markers.
If author does not trivially resolve, the user will be presented with the
options to choose from. Once that's all done we have our solution commit *B*.
All descendants of *B/0* and *B/1* are rebased onto *B*. The command records the
operation in the operation log with a new View where *B* is a visible commit
with *{B/0, B/1}* as predecessors. *B/0* and *B/1* become hidden commits.

Note that in some cases the solution may be identical to either *B/0* or *B/1*
(in all regards except the commit timestamp): we choose to create a new commit
*B* to make the evolution graph and op log more clearly show that jj converge
was invoked. Alternatively we could keep the matching commit instead of creating
a new commit (this could result in cycles in the evolog).

#### Example 2: two commits for change *B* with same parent (predecessor has a different parent)

Now lets consider the case where *P* has a different parent:

```console
$ jj log
B/0
|
| B/1
|/
A
| P (not visible)
|/
X
```

In this case we first rebase *P* onto *A* (in-memory) to produce `P' = A + (P -
P⁻)`. This essentially reduces the problem to the previous case. We now produce
the solution as before: `B = P' + (B/0 - P') + (B/1 - P')`. Note that again the
parent of the solution is *A*.

#### Example 3: divergent commits with different parents

```console
$ jj log
B/0
|
|   B/1
|  /
| C
|/
A
```

In this case it is not immediately obvious which commit should be the parent of
the solution. Let's first consider the case where *P* is a child of *A*.

```console
$ jj log
B/0
|
|   B/1
|  /
| C
|/
| P (not visible)
|/
A
```

We determine the parent(s) of the solution as follows:

```
parents = P⁻ + (B/0⁻ - P⁻) + (B/1⁻ - P⁻)
```

In this example the expression evaluates to `{A} + ({A} - {A}) + ({C} - {A}) =
{C}`. Since this expression resolves trivially to *{C}*, we use that as the
parents of the solution.

Note that this simple algorithm produces the desired output in example 1 and
example 2. In example 2, the expression looks like this:

```
parents = P⁻ + (B/0⁻ - P⁻) + (B/1⁻ - P⁻)
        = {X} + ({A} - {X}) + ({A} - {X})
        = {A} + ({A} - {X})
```

That expression resolves trivially to *{A}* when using SameChange::Accept.

#### Example 4: divergent commits with different parents, must prompt user to choose parents

If instead *P* is a child of some other commit *X*, the story is a bit
different:

```console
$ jj log
B/0
|
| B/1
| |
| C
|/
A
| P (not visible)
|/
X
```

In this case parents will be

```
{X} + ({A} - {X}) + ({C} - {X}) = {A} + ({C} - {X})
```

Since this does not trivially resolve, the command prompts the user to select
the desired parents for the solution: either *{A}* or *{C}*.

Assume the user chooses *{C}*. The command then rebases (in memory) *B/0*, *B/1*
and *P* onto the chosen parents:

```
In-memory commits after rebasing B/0, B/1 and P on top of C (edges represent
parent/child relationship):

# B/0' = C + (B/0 - A)
# B/1' = C + (B/1 - C) = B/1
# P' = C + (P - X)

B/0'
|
| B/1'
|/
| P'
|/
C
```

As a result we obtain *B/0'*, *B/1'* and *P'*, and these are sibling commits. At
this point the command does a 3-way merge of `MergedTree` objects to produce
`MergedState::tree` (in reality it is enough to rebase the commit *trees*).

#### Example 5: more than 2 divergent commits

There can be more than 2 visible commits for a given change-id. We are assuming
here *B/0*, *B/1* and *B/2* are all direct successors of commit *P* (which is
invisible).

```console
$ jj log
B/0
|
| B/1
| |
| | B/2
| |/
|/
A
```

This is completely analogous to the first example, we simply have more terms on
all merges. The same thing applies to all previous examples, in all cases we can
deal with any number of divergent commits for change *B*.

### Examples and expected behavior (with arbitrary evolution graph)

So far we only considered simple cases where all divergent commits are direct
successors of a common predecessor *P*. Now we extend the ideas to arbitrary
evolution history. To that end we introduce the *"truncated evolution graph for
B/0, B/1, ... , B/n"*, where *B/0, B/1, ... , B/n* are two or more commits with
the same change-id *B*. This is a directed graph. Its nodes are commits for
change-id *B* and the edges are from a commit to its (immediate) predecessor(s),
ignoring predecessors with unrelated change-ids. The graph is built by
traversing the operation log and associated View objects, adding nodes and
predecessor edges as needed. Nodes are added this way until a single most-recent
common predecessor commit is found. We call the most-recent common predecessor
the *"evolution fork point of B/0, ... , B/n"*. The traversal keeps track of
visited commits to avoid infinite loops [^footnote-about-evolog-cycles]
[^virtual-evolution-fork-point].

#### Example 6: a two-level evolution graph

We continue by looking at a truncated evolution graph that is slightly more
complex than the basic 3-commit case. This will serve as motivation for the
general case. Here is our truncated evolution graph (remember the edges here
represent change evolution, not parent-child relations):

```
Truncated evolution graph. B/0, B/1 and Q may have other predecessors for
unrelated change-ids. P is the evolution fork point (it may have predecessors,
even for change-id B):

B/0     (description: "v3")
|
|  B/1  (description: "v2")
Q /     (description: "v2")
|/
P       (description: "v1")
```

Commit *P* evolved into *Q* and *B/1*, and *Q* evolved into *B/0*. As before
*B/0* and *B/1* are visible, *P* and *Q* are not. Since both sides of the
evolution transitioned from "v1" to "v2", and then one side further transitioned
to "v3", it seems a good heuristic to take "v3" as the description of the
solution. Note that this observation would not be possible if the algorithm only
considered the leafs (*B/0*, *B/1*) and their evolution fork point (*P*).

Note: Why do we care about divergence producing two commits with the exact same
change? It may seem this would be a very uncommon scenario, however, as
mentioned in the last bullet point in the "Some divergence scenarios" section,
this is in fact fairly common at Google due to the distributed nature of
Google's backend filesystem.

To implement the heuristic we outlined above in example 6 (i.e. to produce
"v3"), we propose introducing a new *try_resolve_deduplicating_same_diffs*
method to `Merge<T>`, and using that in calls to attempt to resolve the
MergedState::description, MergedState::parent, MergedState::author and
MergedState::trees. try_resolve_deduplicate_same_diffs is similar to
resolve_trivial, but it counts multiple identical *(X - Y)* terms exactly once,
otherwise it follows the same flow as resolve_trivial with SameChange::Accept.

We illustrate what *try_resolve_deduplicating_same_diffs* does when resolving
the description merge for the case in example 6. We build a `Merge<String>`,
starting with the description of the evolution fork point *P*, then adding
*desc(Y) - desc(X)* terms for each *X->Y* edge in the truncated evolution graph.
Then we call *try_resolve_deduplicating_same_diffs* to get:

```
P + (Q - P) + (B/1 - P) + (B/0 - Q) =
       = v1 + (v2 - v1) + (v2 - v1) + (v3 - v2)    <== collapse duplicate
                                                       (v2 - v1) edge/term
       = v1 + (v2 - v1) + (v3 - v2)
       = v3
```

*try_resolve_deduplicating_same_diffs* returns our desired value "v3". Note that
resolve_trivial (with either SameChange value) would return none. Here is
another example:

```
Truncated evolution graph:

B/0     ( foo.txt contents: "v3" )
|
|  B/1  ( foo.txt contents: "v2" )
Q /     ( foo.txt contents: "v1" )
|/
P       ( foo.txt contents: "v1" )
```

In this case *try_resolve_deduplicating_same_diffs* produces none. jj converge
cannot automatically resolve this merge so the user has to merge the
description: the command invokes the user's merge-tool with base "v1" and sides
"v2"/"v3".

### Edge cases when choosing the parents of the solution

When attempting to produce the solution parents, the command applies
*try_resolve_deduplicating_same_diffs* to MergedState::parents (of type
`Merge<Vec<CommitId>>`). If the result is `Some<Vec<CommitId>>` we have a set of
possible parents for the solution. If these candidate parents are all visible
commits with change-ids other than *B*, and none of those are descendants of
*B/0, B/1, ... B/n*, then we have the desired parents for the solution commit.

On the other hand,

*   if any candidate parent is a descendant of one of the divergent commits we
    are trying to solve, or
*   if any of the candidate parents are hidden and the chain starting at visible
    roots and leading up to and including the parent has any commit for the
    divergent change we are trying to solve (*B*), or for any other visible
    commit (divergent or not), or the chain itself has two or more commits with
    the same change-id,

then we would introduce new divergence or cycles in the commit graph if we based
on the solution on such candidate parents. In these edge cases the command will
simply discard the candidate parents and will instead ask the user to choose
which parents to use (or quit without making any changes). Care must be
taken when picking the options to present to the user for choosing parents:
essentially the user will choose between the parents of *B/0*, the parents of
*B/1*, and so on, but we will skip any *B/i* if any of the parents of *B/i*
descends from any *B/j*. Since the commit graph is a DAG, at least one option is
viable.

## Multiple divergent change-ids

If there are multiple divergent change-ids, the command could prompt the user to
choose one, or apply heuristics to choose one programmatically. In the first
version it is OK to prompt the user.

If the command successfully resolves divergence in the first divergent
change-id, it could continue to process the next divergent change-id, and so on.
To avoid complexity the first implementation will only deal with one divergent
change per invocation.

### Rebasing descendants and persisting

The last step is to rebase all descendants of the divergent commits on top of
the new solution commit, persist the changes and record the operation in the op
log. The command will move local bookmarks pointing to any of the rewritten
divergent commits to point to the solution commit.

## Other edge cases

When the command starts it needs to find the divergent change-ids and their
corresponding visible commits. If the portion of the visible commit graph
leading up to immutable heads is too big, the command should error out.

There could be pathological cases where the evolution history is too long. When
building the truncated evolution graph, if we have traversed too many nodes (say
50) and we have not yet completed the traversal, the algorithm will not traverse
any more commits. We could simply error out, or we could use an incomplete
truncated evolution graph by adding a virtual evolution fork point. It is
probably best to error out.

## Open questions

*   Do we ever have divergence of committer? Is it safe to mess with committer?

## Alternatives considered

### Automatically resolving divergence

It would be nice if divergence was avoided in the first place, at least in some
cases, at the point where jj is about to introduce the second (or third or
fourth, etc.) visible commit for a given change id. This should be investigated
separately.

### Resolve divergence two commits at a time

The algorithm in this proposal should work when there are any number of
divergent commits (for a given change id). In practice we expect most often
there will be just 2 or perhaps a few divergent commits. We could design an
algorithm for just 2 commits, but we chose to think about the more general case.

### Only considering the evolution fork point and visible commits

As explained in example 6 this proposal uses the truncated evolution graph and
*try_resolve_deduplicating_same_diffs* to produce the solution. That example
shows why we think this leads to a better heuristic. We could instead only
consider *P* and *B/0, B/1, ... , B/n*. That would be slightly simpler.

[^footnote-about-evolog-cycles]: It is unclear if the evolution history can
    contain cycles today, but there has been some
    discussion about `jj undo` possibly producing
    cycles. In any case, it is very easy to deal
    with that possibility, so we may as well handle
    it?
[^virtual-evolution-fork-point]: Today there should always be a single evolution
    fork point. However, we could handle cases
    where a change-id "emanates from multiple
    initial commits" by adding a single *virtual
    evolution fork point* commit with empty state:
    empty description, empty tree and empty author,
    and having the root commit as its parent, and
    treating it as a predecessor of all initial
    commits. Again, we probably don't need to worry
    about this, but it is good to know we could
    handle it.

---

### Design Managed Config (`docs/design/managed-config.md`)

# Repo-managed JJ configuration

Author: [Matt Stark](mailto:msta@google.com)

Status: Pending implementation

## Background

The design doc [Secure JJ Config](secure-config.md) introduces a mechanism,
`metadata.binpb`, through which information about a repository / workspace can
be stored. It also discusses how allowing an external user to have control over
your config is a security risk.

## Overview

There is a need for a repository to impose requirements upon users. Examples of
these things include, but are not limited to:

*   Formatter configuration (eg. [Chromium Formatter Config])
*   Pre-upload checks (eg. [Chromium Pre-upload Checks])
*   Syncing scripts (eg. [Chromium Syncing Scripts])
*   Custom aliases / revsets documented by some kind of “getting started with
    &lt;project&gt;” document

It should be fairly obvious that there is a strong benefit to doing so. However,
controlling a user’s config is sufficient to get root access to their machine,
so we require a mechanism more complex than just blindly loading the config
file.

This is currently achieved by projects such as chromium by instructing the user
to symlink `.jj/repo/config.toml` to a file in the repository. This has several
drawbacks:

*   It doesn’t work out-of-the-box. I need to manually symlink it
*   It has no security guarantees. If I update the file in the repo, the user has no
    opportunity to review it.
*   This prevents a user from having their own repo configuration on top of it.

## Objective

*   Create a new layer of configuration between user configuration and repo
    configuration.
  *   This configuration will be stored in version control and henceforth be
      referred to as “managed” configuration.
*   Implement it in a secure manner so that an attacker cannot take control over
    the managed config.

## Detailed Design

The managed configuration will be read from `$REPO/.config/jj/config.toml`. This
is intentionally designed to be very similar to `$HOME/.config/jj/config.toml`
for the user configuration.

Any data stored here will be added to the `metadata.binpb` that was created
in [secure config](secure-config.md) (that is, we will add additional fields to
the `Metadata` struct). This will ensure that we don't suffer from the "zip file
problem".

### Security

#### Trust levels

We will add the following fields to metadata:
```
enum TrustLevel {
    // There's no "optional" in protos. It just returns the zero value.
    UNSET = 0;
    // The user wishes to completely ignore the managed config file.
    IGNORED = 1;
    // In trusted mode, we directly read from the managed config file.
    // This presents a security risk, so the user is expected to only do this
    // for a repo they trust.
    TRUSTED = 2;
    // In notify mode, the user is expected to manually replicate any changes
    // they want from the managed config to the repo config.
    NOTIFY = 3;
    // In review mode, the user has to review every config individually.
    REVIEW = 4;
}

message Metadata {
    // previous fields

    // The trust level associated with this repo.
    TrustLevel trust_level = 2;

    // When trust_level is review, the following fields are used.
    set<Hash> approved_configs = 3;
    set<Hash> rejected_configs = 4;
    // This is a can of worms and thus out of scope of the MVP.
    // There's no one correct way to choose the last approved.
    // Sometimes, the user wants be the last appproved config in the workspace.
    // At other times, the user wants be the last appproved config in the repo.
    string last_approved_config = 5;
}
```

Note that although the `Metadata` struct is stored for both the repo and the
workspace, this will only be used for the repository, not the workspace.

This is because trust is associated with repositories, not workspaces. If I
were to add an approved config in one workspace, for example, there is no
reason I wouldn't trust it in another.

#### User interface

Everything here only becomes relevant when a repo has a managed config. If it
does not have a managed config, we skip everything here.

##### Unset trust

If a repository has a managed config and unset trust, we first use
`ui.can_prompt()` to determine whether we are in a TUI.

*   If we are in a TUI, we ask the user what trust level they would like.
*   If we are not in a TUI, we warn them that we are ignoring the repo config (we
    return `IGNORE`), and that they can run `jj config managed --ignore/notify/trust` in a terminal to configure this.

##### Ignored

Nice and easy. We just completely ignore the managed config file.

##### Trusted

Nice and easy. We just read the managed config file.

##### Notify

Roughly speaking, if `mtime(repo_config_file) > mtime(managed_config)`, then
the config is up to date.

If it isn't up to date, we print a warning. The user is then expected to update
their repo config with any options they want from the managed config, which
will stop the warning from printing as the mtime has changed.

This notify option has a rather painful UX when it comes to keeping it in sync
(particularly for a user who constantly switches between different branches
synced to different versions), but I choose it for several reasons. The first is
that the vast majority of users will simply select to blindly trust the repo.
The only people who will choose this option are the very security conscious, and
this is by far the most secure mechanism. Secondly, it is so much simpler than
an approval based mechanism, as we don’t need to worry about things such as
workspaces being synced to different places. It provides far fewer edge cases.

In the future we may consider improving on the UX, doing things such as printing
a diff since the last seen config (per `last_approved_config`), but that isn't
a part of the MVP.

##### Review

This is, by far, the most difficult option. There is no doubt that this
provides value to some users, but the precise details of how this should behave
is unclear (and much harder to implement than the other options).

The biggest problem with this is that there is not a clear "correct" set of
semantics. If a config is not approved / rejected, we have 3 options:
* Fail the command. This seems like a bad idea since users have an assumption
  that most jj commands will not fail.
* Use no config. This can potentially have quite bad effects on the repo.
* Use best-effort to use an approved config (probably most recently approved).
* Force the user to make a selection before running a command
  * This is impossible in general due to `ui.can_prompt()` returning `false`
    * We would need to fall back to another option if we can't prompt.
  * But `ui.can_prompt()` does not mean that we *should* prompt
    * E.g., a script that runs jj to do a bisection probably doesn't want
      you to prompt every time the config changes.
    * This probably isn't too bad if it only happens once (e.g., setting trust
      level), but if it happens many times, it could be very annoying.

Consider the following examples

###### User is working on config file.

1. User runs `vim .config/jj/config.toml`
2. User tries to test out the config, but needs to manually approve their own
   local change to the config every single time (this can at least be
   mitigated via setting it to trust temporarily)

###### Disabling config messes with the repo

1. User approves a config.
2. User runs `jj git fetch`. There has been an update to the managed config.
3. User runs `jj rebase -b @ -o trunk()`. The managed config is now not
   approved, and thus the next jj command will have the config disabled.

Depending on what has changed, a variety of things could happen:
* A user in a repo that defines `trunk()` could run
  `jj rebase -b <revset> -o trunk()`
  * This would result in it going to the wrong trunk
* A user in a repo that defines `immutable_heads() = ...` could run something
  like `jj rebase -b mutable() -o trunk()`
  * This would rebase a bunch of immutable commits
* A user in a repo that defines `pre-upload` hooks (which don't yet exist,
  but are being planned) runs `jj gerrit upload` and bypasses them entirely.

Thanks to the magic of `jj undo`, everything except the upload is recoverable.
What makes them a problem, however, is the fact that a user would see the
message "please approve / reject the config" and not suspect that the command
has failed.

###### Best-effort messes with the user

If we use best-effort instead, we are likely to run into a different set of
issues.

The first is that it's no longer clear which config is being used. If I:
1. Have two workspaces A and B with the same state, both approved.
2. Sync workspace B and approve the config
3. Check out an old version of workspace A

Now which config is used?
* The most recently approved globally?
* The most recently approved for this workspace?
* Some algorithm which determines that the config for A was at a closer commit
  than B?

The second issue is that things may be incorrect. If `foo.py` was renamed to
`bar.py` and the reference was changed in `config.toml`, for example, the
config is going to break. This is not as large an issue as disabling config,
IMO, as changes will usually result in an obvious error.

###### Conclusion

The vast majority of users will not use this option, as very few users are
security-conscious, working on a repo they don't trust, and working on a repo
with managed config.

Due to:
* Significant implementation complexity
  * My prototype without this feature is only ~200 lines of code and ~100 lines
    of tests. This feature would likely double that for the most hacky simple
    implementation, and add significantly more if we wanted a proper nice UX.
* The lack of a single "correct" solution
* The fact that most users just want the "trust" option
* The fact that it is no more difficult to add other options, then add the
  review option after the fact
* The time that would be required to get consensus on the various decisions
  that would need to be made in terms of how this would work.
  * There was already a lot of
    [discussion](https://github.com/jj-vcs/jj/pull/7761#discussion_r2476135221)
    on this topic, but no clear conclusion was reached.
  * Technical (e.g., disable vs. best-effort)
  * UX
    * When reviewing, do we review diffs or full config files?
    * Do we prompt the user to review before running the command?

I propose that the "review" option not be put in the MVP, and and instead be
added in later.

##### Manually changing

These options will also be able to be manually set via `jj config managed
--ignore/notify/trust`.

### Where to read from

This is the trickiest part of the proposal. Consider the following workflow:

```
jj new main@origin
jj ...
jj new stable-branch@origin
```

There are some edge cases we need to consider:

*   `stable-branch` may have existed before the config was added
*   `stable-branch` may have a different copy of the managed config

The naive assumption would be that you want to read the config from `@`, as the
config will always match the version of the code you're using. However, it turns
out that some things want to refer to `@`, while others want to read the config
from `trunk()`.

Consider several different use cases:

*   My formatter was previously `clang-format --foo`, but the option `--foo` was
    deprecated in the latest version of `clang-format`
  *   Here, you want to read from `trunk()`
*   My formatter was previously `$repo/formatter --foo`, but the option `--foo`
    was deprecated in the latest version of `formatter`
  *   Here, you want to read from `@`
*   We decide to split long lines and add a new formatter (or pre-upload hook)
    config `formatter --line-length=80`
  *   Here, you probably want to read from `@`
*   We decide to add a pre-upload check that validates that all commit
    descriptions contain a reference to a bug
  *   This should be applied to old branches as well, so you want `trunk()`
*   We add a new helpful alias / revset
  *   This should be applied to old branches as well, so you want `trunk()`
*   We move our formatter
  *   If it’s external, you want to read from `trunk()`
  *   If it’s internal, you want to read from `@`

All in all, you can see a general pattern.

*   If something refers to an in-repo tool, you **probably** want the config to
    be read from `@`
*   Otherwise, you **probably** want to read from `trunk()`
*   I say probably, because the split long lines example doesn’t conform to this
    rule.

#### Problematic examples

This is problematic with `trunk()` because if you add the `--reorder-hooks` and
then checkout `stable-branch` it will incorrectly attempt to reorder imports

```
[fix.tools.rustfmt]
command = ["rustfmt", "--reorder-imports"]
```

##### Solution 1: formatter config

In practice, it is highly unlikely that a formatter config would be written that
way. Far more likely, you would see an entry in `config.toml` like:

```
[fix.tools.rustfmt]
command = ["rustfmt"]
```

`.rustfmt.toml`:
```
reorder_imports = true
```

This simply works out of the box, since the formatter is reading the config from
`@`'s `.rustfmt.toml`

##### Solution 2 (more general but convoluted): Wrapper script

As long as the formatter is in-repo, we can just write a wrapper script which
does this for us.

```
[fix.tools.rustfmt]
command = ["rustfmt.py"]
```

`rustfmt.py`:
```
os.execv(["rustfmt", "--reorder-imports"])
```

#### Solution 3 (for scripts that need to run at trunk)

If you write a script for which the API keeps changing, eg. you add / remove
flags to it, you can do something like this:

```
[aliases]
upload = ["util", "exec", "--", "bash", "-c", "python3", "-c", "$(jj file show -r 'trunk()' upload.py)"]
```

#### Decision: Trunk vs @

`@` and `trunk()` are the only two reasonable candidates as places to read from,
IMO. I personally believe that if only one option is available, `trunk()` would
be much more appropriate, for the reasons specified above.

However, @pmetzger has pointed out that in a future world where Git isn’t the
backend, this decision may come back to bite us (as build tools may be checked
in to the build).

For now, we will only support reading from the working copy (`@`), but in the
future, we may consider supporting other revisions.

To achieve this, there are several methods. I'll list some of them, but they
don't need review, as they are out of scope of this proposal.

The simplest that comes to mind is a `--managed-config-from-revision` flag or
config option.

Alternatively, a more general approach could be to create some kind of include
directive in `config.toml` files, like so (this has already been discussed and
rejected before, so it may not be viable).
```
includes = [
    {
        path = "other_config.toml",
        revision = "trunk()",
    },
]
```

#### Read from filesystem or from repo objects?

The tradeoffs are:
* If we read it from the filesystem, then it will not be read if the path is
  not tracked, e.g. because you have done `jj sparse --clear --add lib` or
  because the file matches .gitignore and is not yet tracked.
  * This could be mitigated by always including `.config/jj` in `jj sparse`
* If we read it from the repo objects, then changes will not apply to the next
  command unless we load this config after snapshotting
* Reading from the filesystem is faster
* mtime is only available when reading from the filesystem

Reading from the filesystem seems strictly superior, given the mitigation
strategy available, so we will go ahead with that for now. If we ever decide to
support reading from `trunk()`, however, we will need to support reading from
the repo.

[Chromium Formatter Config]: https://source.chromium.org/chromium/chromium/src/+/main:tools/jj/config.toml;l=47-96;drc=080c978973f87ff2a1cfa514a13285baeaf3eedc
[Chromium Pre-upload Checks]: https://source.chromium.org/chromium/chromium/src/+/main:tools/jj/upload.py;drc=96f39fbbb720ca391d43cbb199a85af7d3309dd3
[Chromium Syncing Scripts]: https://source.chromium.org/chromium/chromium/src/+/main:tools/jj/sync.py;drc=6ff08dcdd1fdeb1654a4d3da81d8adaeae4bbbf7

---

--- METRICS ---
- Files Extracted: 47
- Estimated Token Budget: ~127293 tokens
- Canonical Reference: https://codewiki.google/github.com/jj-vcs/jj
