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
your current agreements on file or to sign a new one.
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.
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.
All submissions, including submissions by project members, require review. We
use GitHub pull requests for this purpose. Consult
GitHub Help 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.
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 you realize that some comments require non-trivial
changes, please ask your reviewer to take another look.
If your employer pays anyone (not necessarily you) to contribute to Jujutsu,
please make sure your GitHub username is recorded.
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.
Before sending a PR for a large change which designs/redesigns or reworks an
existing component, we require an architecture review from multiple
stakeholders, which we do with Design Docs, see the
process here.
We appreciate bug
reports
about any problems, however small, lurking in our documentation
website 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. You do not need to
know Rust to work with them. See below for instructions on how to preview the
HTML docs 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. Working
on them requires setting up a Rust development environment, as described
below, and may occasionally require adjusting a test.
To develop jj, the mandatory steps are simply
to install Rust (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.
These are listed roughly in order of decreasing importance.
Nearly any change to jj’s CLI will require writing or updating snapshot
tests that use the insta 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.
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.
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.89.0.
Your code needs to pass cargo clippy. You can also
use cargo +nightly clippy if you wish to see more warnings.
You may also want to install and use bacon,
to automatically build, check, and / or run tests.
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.
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:
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. 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.:
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.
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:
Terminal window
sudomkdir-p/Volumes/RAMDisk
sudochmoda+wx/Volumes/RAMDisk
sudomount_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.)
You can double-check whether or not the RAM disk is mounted with
Terminal window
mount|greptmpfs
Some details and speculation
Hard drive speed is not the issue here. Experimentally, when /tmp
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
fdatasync. A likely curlprit is the presence of global kernel locks in APFS. There are some benchmarks in
this PR.
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!
If you have Nix installed, you can use the provided
flake.nix to get a development environment with all necessary tools
pre-configured:
Terminal window
nixdevelop
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:
Terminal window
nixdevelop
code.# or your editor of choice
!!! note “direnv users”
If you use direnv with
nix-direnv, you can echo "source_env .envrc.recommended" >> .envrc to automatically activate the
development environment when entering the directory.
An experimental development setup is available using
mise. 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.
The only thing you need is 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:
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, 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):
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.
Make sure github.com/jjfan/jj includes the gh-pages bookmark of the jj repo
and run git fetch origin gh-pages.
Go to the GitHub repository settings, enable GitHub Pages, and configure them
to use the gh-pages bookmark (this is usually the default).
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.
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/.
(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.
Terminal window
jjnewv1.33.1# Let's say `jj 1.33.1` is the currently the latest release
(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:
Terminal window
# This will LOSE any changes you made to `gh-pages`
jjgitfetch--remoteupstream
jjbookmarksetgh-pages-rgh-pages@upstream
jjgitpush--remoteorigin--bookmarkgh-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.
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.
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
prostlibrary 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.
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.
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.
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.
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.
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.