diff --git a/prerelease/search/search_index.js b/prerelease/search/search_index.js
index b7d717a774..f8ba4cefae 100644
--- a/prerelease/search/search_index.js
+++ b/prerelease/search/search_index.js
@@ -1 +1 @@
-var __index = {"config":{"lang":["en"],"separator":"[\\s\\-]+","pipeline":["stopWordFilter"]},"docs":[{"location":"FAQ.html","title":"Frequently asked questions","text":""},{"location":"FAQ.html#why-does-my-branch-not-move-to-the-new-commit-after-jj-newcommit","title":"Why does my branch not move to the new commit after jj new/commit
?","text":"If you're familiar with Git, you might expect the current branch to move forward when you commit. However, Jujutsu does not have a concept of a \"current branch\".
To move branches, use jj branch set
.
"},{"location":"FAQ.html#i-made-a-commit-and-jj-git-push-all-says-nothing-changed-instead-of-pushing-it-what-do-i-do","title":"I made a commit and jj git push --all
says \"Nothing changed\" instead of pushing it. What do I do?","text":"jj git push --all
pushes all branches, not all revisions. You have two options:
- Using
jj git push --change
will automatically create a branch and push it. - Using
jj branch
commands to create or move a branch to either the commit you want to push or a descendant on it. Unlike Git, Jujutsu doesn't do this automatically (see previous question).
"},{"location":"FAQ.html#where-is-my-commit-why-is-it-not-visible-in-jj-log","title":"Where is my commit, why is it not visible in jj log
?","text":"Is your commit visible with jj log -r 'all()'
?
If yes, you should be aware that jj log
only shows the revisions matching revsets.log
by default. You can change it as described in config to show more revisions.
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.
"},{"location":"FAQ.html#can-i-prevent-jujutsu-from-recording-my-unfinished-work-im-not-ready-to-commit-it","title":"Can I prevent Jujutsu from recording my unfinished work? I'm not ready to commit it.","text":"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.
For more options see the next question.
"},{"location":"FAQ.html#can-i-add-a-portion-of-the-edits-i-made-to-a-file-similarly-to-git-add-p-or-hg-commit-i","title":"Can I add a portion of the edits I made to a file, similarly to git add -p
or hg commit -i
?","text":"At the moment the best options to partially add a file are: jj split
, jj amend -i
and jj move -i
.
"},{"location":"FAQ.html#is-there-something-like-git-rebase-interactive-or-hg-histedit","title":"Is there something like git rebase --interactive
or hg histedit
?","text":"Not yet, you can check this issue for updates.
To reorder commits, it is for now recommended to rebase commits individually, which may require multiple invocations of jj rebase -r
or jj rebase -s
.
To squash or split commits, use jj squash
and jj split
.
"},{"location":"FAQ.html#how-can-i-keep-my-scratch-files-in-the-repository","title":"How can I keep my scratch files in the repository?","text":"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 $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).
You can find more details on gitignore
files here.
"},{"location":"FAQ.html#how-can-i-keep-local-changes-around-but-not-use-them-for-pull-requests","title":"How can I keep local changes around, but not use them for Pull Requests?","text":"In general, you should separate out the changes to their own commit (using e.g. jj split
). After that, one possible workflow is to rebase your pending PRs on top of the commit with the local changes. Then, just before pushing to a remote, use jj rebase -s child_of_commit_with_local_changes -d main
to move the PRs back on top of main
.
If you have several PRs, you can try jj rebase -s all:commit_with_local_changes+ -d main
(note the +
) to move them all at once.
An alternative workflow would be to rebase the commit with local changes on top of the PR you're working on and then do jj new commit_with_local_changes
. You'll then need to use jj new --before
to create new commits and jj move --to
to move new changes into the correct commits.
"},{"location":"FAQ.html#i-accidentally-amended-the-working-copy-how-do-i-move-the-new-changes-into-its-own-commit","title":"I accidentally amended the working copy. How do I move the new changes into its own commit?","text":"Use jj obslog -p
to see how your working-copy commit has evolved. Find the commit you want to restore the contents to. Let's say the current commit (with the changes intended for a new commit) are in commit X and the state you wanted is in commit Y. Note the commit id (normally in blue at the end of the line in the log output) of each of them. Now use jj new
to create a new working-copy commit, then run jj restore --from Y --to @-
to restore the parent commit to the old state, and jj restore --from X
to restore the new working-copy commit to the new state.
"},{"location":"branches.html","title":"Branches","text":""},{"location":"branches.html#introduction","title":"Introduction","text":"Branches are named pointers to revisions (just like they are in Git). You can move them without affecting the target revision's identity. Branches automatically move when revisions are rewritten (e.g. by jj rebase
). You can pass a branch's name to commands that want a revision as argument. For example, jj co main
will check out the revision pointed to by the \"main\" branch. Use jj branch list
to list branches and jj branch
to create, move, or delete branches. There is currently no concept of an active/current/checked-out branch.
"},{"location":"branches.html#remotes","title":"Remotes","text":"Jujutsu identifies a branch by its name across remotes (this is unlike Git and more like Mercurial's \"bookmarks\"). For example, a branch called \"main\" in your local repo is considered the same branch as a branch by the same name on a remote. When you pull from a remote (currently only via jj git fetch
), any branches from the remote will be imported as branches in your local repo.
Jujutsu also records the last seen position on each remote (just like Git's remote-tracking branches). You can refer to these with <branch name>@<remote name>
, such as jj new main@origin
. Most commands don't show the remote branch if it has the same target as the local branch. The local branch (without @<remote name>
) is considered the branch's desired target. Consequently, if you want to update a branch on a remote, you first update the branch locally and then push the update to the remote. If a local branch also exists on some remote but points to a different target there, jj log
will show the branch name with an asterisk suffix (e.g. main*
). That is meant to remind you that you may want to push the branch to some remote.
When you pull from a remote, any changes compared to the current record of the remote's state will be propagated to the local branch. Let's say you run jj git fetch --remote origin
and the remote's \"main\" branch has moved so its target is now ahead of the local record in main@origin
. That will update main@origin
to the new target. It will also apply the change to the local branch main
. If the local target had also moved compared to main@origin
(probably because you had run jj branch set main
), then the two updates will be merged. If one is ahead of the other, then that target will be the new target. Otherwise, the local branch will be conflicted (see next section for details).
"},{"location":"branches.html#conflicts","title":"Conflicts","text":"Branches can end up in a conflicted state. When that happens, jj status
will include information about the conflicted branches (and instructions for how to mitigate it). jj branch list
will have details. jj log
will show the branch name with a question mark suffix (e.g. main?
) on each of the conflicted branch's potential target revisions. Using the branch name to look up a revision will resolve to all potential targets. That means that jj co main
will error out, complaining that the revset resolved to multiple revisions.
Both local branches (e.g. main
) and the remote branch (e.g. main@origin
) can have conflicts. Both can end up in that state if concurrent operations were run in the repo. The local branch more typically becomes conflicted because it was updated both locally and on a remote.
To resolve a conflicted state in a local branch (e.g. main
), you can move the branch to the desired target with jj branch
. You may want to first either merge the conflicted targets with jj merge
, or you may want to rebase one side on top of the other with jj rebase
.
To resolve a conflicted state in a remote branch (e.g. main@origin
), simply pull from the remote (e.g. jj git fetch
). The conflict resolution will also propagate to the local branch (which was presumably also conflicted).
"},{"location":"code-of-conduct.html","title":"Code of Conduct","text":""},{"location":"code-of-conduct.html#our-pledge","title":"Our Pledge","text":"In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation.
"},{"location":"code-of-conduct.html#our-standards","title":"Our Standards","text":"Examples of behavior that contributes to creating a positive environment include:
- Using welcoming and inclusive language
- Being respectful of differing viewpoints and experiences
- Gracefully accepting constructive criticism
- Focusing on what is best for the community
- Showing empathy towards other community members
Examples of unacceptable behavior by participants include:
- The use of sexualized language or imagery and unwelcome sexual attention or advances
- Trolling, insulting/derogatory comments, and personal or political attacks
- Public or private harassment
- Publishing others' private information, such as a physical or electronic address, without explicit permission
- Other conduct which could reasonably be considered inappropriate in a professional setting
"},{"location":"code-of-conduct.html#our-responsibilities","title":"Our Responsibilities","text":"Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
Project maintainers 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, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
"},{"location":"code-of-conduct.html#scope","title":"Scope","text":"This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
This Code of Conduct also applies outside the project spaces when the Project Steward has a reasonable belief that an individual's behavior may have a negative impact on the project or its community.
"},{"location":"code-of-conduct.html#conflict-resolution","title":"Conflict Resolution","text":"We do not believe that all conflict is bad; healthy debate and disagreement often yield positive results. However, it is never okay to be disrespectful or to engage in behavior that violates the project\u2019s code of conduct.
If you see someone violating the code of conduct, you are encouraged to address the behavior directly with those involved. Many issues can be resolved quickly and easily, and this gives people more control over the outcome of their dispute. If you are unable to resolve the matter for any reason, or if the behavior is threatening or harassing, report it. We are dedicated to providing an environment where participants feel welcome and safe.
Reports should be directed to [PROJECT STEWARD NAME(s) AND EMAIL(s)], the Project Steward(s) for [PROJECT NAME]. It is the Project Steward\u2019s duty to receive and address reported violations of the code of conduct. They will then work with a committee consisting of representatives from the Open Source Programs Office and the Google Open Source Strategy team. If for any reason you are uncomfortable reaching out to the Project Steward, please email opensource@google.com.
We will investigate every complaint, but you may not receive a direct response. We will use our discretion in determining when and how to follow up on reported incidents, which may range from not taking action to permanent expulsion from the project and project-sponsored spaces. We will notify the accused of the report and provide them an opportunity to discuss it before any action is taken. The identity of the reporter will be omitted from the details of the report supplied to the accused. In potentially harmful situations, such as ongoing harassment or threats to anyone's safety, we may take action without notice.
"},{"location":"code-of-conduct.html#attribution","title":"Attribution","text":"This Code of Conduct is adapted from the Contributor Covenant, version 1.4, available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
"},{"location":"config.html","title":"Configuration","text":"These are the config settings available to jj/Jujutsu.
"},{"location":"config.html#config-files-and-toml","title":"Config files and TOML","text":"The config settings are loaded from the following locations. Less common ways to specify jj
config settings are discussed in a later section.
- The user config file
.jj/repo/config.toml
(per-repository)
See the TOML site and the syntax guide for a description of the syntax.
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.
"},{"location":"config.html#dotted-style-and-headings","title":"Dotted style and headings","text":"In TOML, anything under a heading can be dotted instead. For example, user.name = \"YOUR NAME\"
is equivalent to:
[user]\nname = \"YOUR NAME\"\n
For future reference, here are a couple of more complicated examples,
# Dotted style\ntemplate-aliases.\"format_short_id(id)\" = \"id.shortest(12)\"\ncolors.\"commit_id prefix\".bold = true\n\n# is equivalent to:\n[template-aliases]\n\"format_short_id(id)\" = \"id.shortest(12)\"\n\n[colors]\n\"commit_id prefix\" = { bold = true }\n
Jujutsu favors the dotted style in these instructions, if only because it's easier to write down in an unconfusing way. If you are confident with TOML then use whichever suits you in your config. If you mix dotted keys and headings, put the dotted keys before the first heading.
That's probably enough TOML to keep you out of trouble but the syntax guide is very short if you ever need to check.
"},{"location":"config.html#user-settings","title":"User settings","text":"user.name = \"YOUR NAME\"\nuser.email = \"YOUR_EMAIL@example.com\"\n
Don't forget to change these to your own details!
"},{"location":"config.html#ui-settings","title":"UI settings","text":""},{"location":"config.html#colorizing-output","title":"Colorizing output","text":"Possible values are always
, never
and auto
(default: auto
). auto
will use color only when writing to a terminal.
This setting overrides the NO_COLOR
environment variable (if set).
ui.color = \"never\" # Turn off color\n
"},{"location":"config.html#custom-colors-and-styles","title":"Custom colors and styles","text":"You can customize the colors used for various elements of the UI. For example:
colors.commit_id = \"green\"\n
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).
If you use a string value for a color, as in the example above, it will be used for the foreground color. You can also set the background color, or make the text bold or underlined. For that, you need to use a table:
colors.commit_id = { fg = \"green\", bg = \"red\", bold = true, underline = true }\n
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:
colors.commit_id = \"green\"\ncolors.\"working_copy commit_id\" = { underline = true }\n
Parts of the style that are not overridden - such as the foreground color in the example above - are inherited from the parent style.
Which elements can be colored is not yet documented, but see the default color configuration for some examples of what's possible.
"},{"location":"config.html#default-command","title":"Default command","text":"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\"
).
ui.default-command = \"log\"\n
"},{"location":"config.html#default-description","title":"Default description","text":"The value of the ui.default-description
setting will be used to prepopulate the editor when describing changes with an empty description. This could be a useful reminder to fill in things like BUG=, TESTED= etc.
ui.default-description = \"\\n\\nTESTED=TODO\"\n
"},{"location":"config.html#diff-format","title":"Diff format","text":"# Possible values: \"color-words\" (default), \"git\", \"summary\"\nui.diff.format = \"git\"\n
"},{"location":"config.html#generating-diffs-by-external-command","title":"Generating diffs by external command","text":"If ui.diff.tool
is set, the specified diff command will be called instead of the internal diff function.
# Use Difftastic by default\nui.diff.tool = [\"difft\", \"--color=always\", \"$left\", \"$right\"]\n# Use tool named \"<name>\" (see below)\nui.diff.tool = \"<name>\"\n
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.
[merge-tools.<name>]\n# program = \"<name>\" # Defaults to the name of the tool if not specified\ndiff-args = [\"--color=always\", \"$left\", \"$right\"]\n
$left
and $right
are replaced with the paths to the left and right directories to diff respectively.
"},{"location":"config.html#default-revisions-to-log","title":"Default revisions to log","text":"You can configure the revisions jj log
without -r
should show.
# Show commits that are not in `main`\nrevsets.log = \"main..\"\n
"},{"location":"config.html#graph-style","title":"Graph style","text":"# Possible values: \"curved\" (default), \"square\", \"ascii\", \"ascii-large\",\n# \"legacy\"\nui.graph.style = \"square\"\n
"},{"location":"config.html#wrap-log-content","title":"Wrap log content","text":"If enabled, log
/obslog
/op log
content will be wrapped based on the terminal width.
ui.log-word-wrap = true\n
"},{"location":"config.html#display-of-commit-and-change-ids","title":"Display of commit and change ids","text":"Can be customized by the format_short_id()
template alias.
[template-aliases]\n# Highlight unique prefix and show at least 12 characters (default)\n'format_short_id(id)' = 'id.shortest(12)'\n# Just the shortest possible unique prefix\n'format_short_id(id)' = 'id.shortest()'\n# Show unique prefix and the rest surrounded by brackets\n'format_short_id(id)' = 'id.shortest(12).prefix() ++ \"[\" ++ id.shortest(12).rest() ++ \"]\"'\n# Always show 12 characters\n'format_short_id(id)' = 'id.short(12)'\n
To customize these separately, use the format_short_commit_id()
and format_short_change_id()
aliases:
[template-aliases]\n# Uppercase change ids. `jj` treats change and commit ids as case-insensitive.\n'format_short_change_id(id)' = 'format_short_id(id).upper()'\n
To get shorter prefixes for certain revisions, set revsets.short-prefixes
:
# Prioritize the current branch\nrevsets.short-prefixes = \"(main..@)::\"\n
"},{"location":"config.html#relative-timestamps","title":"Relative timestamps","text":"Can be customized by the format_timestamp()
template alias.
[template-aliases]\n# Full timestamp in ISO 8601 format (default)\n'format_timestamp(timestamp)' = 'timestamp'\n# Relative timestamp rendered as \"x days/hours/seconds ago\"\n'format_timestamp(timestamp)' = 'timestamp.ago()'\n
jj op log
defaults to relative timestamps. To use absolute timestamps, you will need to modify the format_time_range()
template alias.
[template-aliases]\n'format_time_range(time_range)' = 'time_range.start() ++ \" - \" ++ time_range.end()'\n
"},{"location":"config.html#author-format","title":"Author format","text":"Can be customized by the format_short_signature()
template alias.
[template-aliases]\n# Full email address (default)\n'format_short_signature(signature)' = 'signature.email()'\n# Both name and email address\n'format_short_signature(signature)' = 'signature'\n# Username part of the email address\n'format_short_signature(signature)' = 'signature.username()'\n
"},{"location":"config.html#pager","title":"Pager","text":"Windows users: Note that pagination is disabled by default on Windows for now (#2040).
The default pager is can be set via ui.pager
or the PAGER
environment variable. The priority is as follows (environment variables are marked with a $
):
ui.pager
> $PAGER
less -FRX
is the default pager in the absence of any other setting.
Additionally, paging behavior can be toggled via ui.paginate
like so:
# Enable pagination for commands that support it (default)\nui.paginate = \"auto\"\n# Disable all pagination, equivalent to using --no-pager\nui.paginate = \"never\"\n
"},{"location":"config.html#processing-contents-to-be-paged","title":"Processing contents to be paged","text":"If you'd like to pass the output through a formatter e.g. 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:
ui.pager = [\"sh\", \"-c\", \"diff-so-fancy | less -RFX\"]
"},{"location":"config.html#aliases","title":"Aliases","text":"You can define aliases for commands, including their arguments. For example:
# `jj l` shows commits on the working-copy commit's (anonymous) branch\n# compared to the `main` branch\naliases.l = [\"log\", \"-r\", \"(main..@):: | (main..@)-\"]\n
"},{"location":"config.html#editor","title":"Editor","text":"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
Pico is the default editor (Notepad on Windows) in the absence of any other setting, but you could set it explicitly too.
ui.editor = \"pico\"\n
To use NeoVim instead:
ui.editor = \"nvim\"\n
For GUI editors you possibly need to use a -w
or --wait
. Some examples:
ui.editor = \"code -w\" # VS Code\nui.editor = \"bbedit -w\" # BBEdit\nui.editor = \"subl -n -w\" # Sublime Text\nui.editor = \"mate -w\" # TextMate\nui.editor = [\"C:/Program Files/Notepad++/notepad++.exe\",\n\"-multiInst\", \"-notabbar\", \"-nosession\", \"-noPlugin\"] # Notepad++\nui.editor = \"idea --temp-project --wait\" #IntelliJ\n
Obviously, you would only set one line, don't copy them all in!
"},{"location":"config.html#editing-diffs","title":"Editing diffs","text":"The ui.diff-editor
setting affects the tool used for editing diffs (e.g. jj split
, jj amend -i
). The default is meld
.
jj
makes the following substitutions:
$left
and $right
are replaced with the paths to the left and right directories to diff respectively.
If no arguments are specified, [\"$left\", \"$right\"]
are set by default.
For example:
# Use merge-tools.kdiff3.edit-args\nui.diff-editor = \"kdiff3\"\n# Specify edit-args inline\nui.diff-editor = [\"kdiff3\", \"--merge\", \"$left\", \"$right\"]\n
If ui.diff-editor
consists of a single word, e.g. \"kdiff3\"
, the arguments will be read from the following config keys.
# merge-tools.kdiff3.program = \"kdiff3\" # Defaults to the name of the tool if not specified\nmerge-tools.kdiff3.edit-args = [\n\"--merge\", \"--cs\", \"CreateBakFiles=0\", \"$left\", \"$right\"]\n
"},{"location":"config.html#experimental-3-pane-diff-editing","title":"Experimental 3-pane diff editing","text":"The special \"meld-3\"
diff editor sets up Meld to show 3 panes: the sides of 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
.
To configure other diff editors, 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
.
"},{"location":"config.html#setting-up-scm-diff-editor","title":"Setting up scm-diff-editor
","text":"scm-diff-editor
is a terminal-based diff editor that is part of the git-branchless suite of tools. It's a good alternative to Meld, especially if you don't have a graphical environment (e.g. when using SSH). To install it:
cargo install --git https://github.com/arxanas/git-branchless scm-record --features scm-diff-editor\n
Then config it as follows:
ui.diff-editor = [\"scm-diff-editor\", \"--dir-diff\", \"$left\", \"$right\"]\n
"},{"location":"config.html#jj-instructions","title":"JJ-INSTRUCTIONS
","text":"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
.
"},{"location":"config.html#using-vim-as-a-diff-editor","title":"Using Vim as a diff editor","text":"Using ui.diff-editor = \"vimdiff\"
is possible but not recommended. For a better experience, you can follow these instructions to configure the DirDiff Vim plugin and/or the vimtabdiff Python script.
"},{"location":"config.html#3-way-merge-tools-for-conflict-resolution","title":"3-way merge tools for conflict resolution","text":"The ui.merge-editor
key specifies the tool used for three-way merge tools by jj resolve
. For example:
# Use merge-tools.meld.merge-args\nui.merge-editor = \"meld\" # Or \"kdiff3\" or \"vimdiff\"\n# Specify merge-args inline\nui.merge-editor = [\"meld\", \"$left\", \"$base\", \"$right\", \"-o\", \"$output\"]\n
The \"meld\", \"kdiff3\", and \"vimdiff\" tools can be used out of the box, as long as they are installed.
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.)
# merge-tools.kdiff3.program = \"kdiff3\" # Defaults to the name of the tool if not specified\nmerge-tools.kdiff3.merge-args = [\"$base\", \"$left\", \"$right\", \"-o\", \"$output\", \"--auto\"]\nmerge-tools.meld.merge-args = [\"$left\", \"$base\", \"$right\", \"-o\", \"$output\", \"--auto-merge\"]\n\nmerge-tools.vimdiff.merge-args = [\"-f\", \"-d\", \"$output\", \"-M\",\n\"$left\", \"$base\", \"$right\",\n\"-c\", \"wincmd J\", \"-c\", \"set modifiable\",\n\"-c\", \"set write\"]\nmerge-tools.vimdiff.program = \"vim\"\nmerge-tools.vimdiff.merge-tool-edits-conflict-markers = true # See below for an explanation\n
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.
"},{"location":"config.html#editing-conflict-markers-with-a-tool-or-a-text-editor","title":"Editing conflict markers with a tool or a text editor","text":"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. This is appropriate for most graphical merge tools.
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.
"},{"location":"config.html#git-settings","title":"Git settings","text":""},{"location":"config.html#automatic-local-branch-creation","title":"Automatic local branch creation","text":"By default, when jj
imports a remote-tracking branch from Git, it also creates a local branch with the same name. In some repositories, this may be undesirable, e.g.:
- There is a remote with a lot of historical branches that you don't want to be exported to the co-located Git repo.
- There are multiple remotes with conflicting views of that branch, resulting in an unhelpful conflicted state.
You can disable this behavior by setting git.auto-local-branch
like so,
git.auto-local-branch = false\n
Note that this setting may make it easier to accidentally delete remote branches. Since the local branch isn't created, the remote branch will be deleted if you push the branch with jj git push --branch
or jj git push --all
.
"},{"location":"config.html#prefix-for-generated-branches-on-push","title":"Prefix for generated branches on push","text":"jj git push --change
generates branch names with a prefix of \"push-\" by default. You can pick a different prefix by setting git.push-branch-prefix
. For example:
git.push-branch-prefix = \"martinvonz/push-\"\n
"},{"location":"config.html#filesystem-monitor","title":"Filesystem monitor","text":"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.
"},{"location":"config.html#watchman","title":"Watchman","text":"To configure the Watchman filesystem monitor, set core.fsmonitor = \"watchman\"
. Ensure that you have installed the Watchman executable on your system.
Debugging commands are available under jj debug watchman
.
"},{"location":"config.html#user-config-file","title":"User config file","text":"On all platforms, the user's global jj
configuration file is located at either ~/.jjconfig.toml
(where ~
represents $HOME
on Unix-likes, or %USERPROFILE%
on Windows) or in a platform-specific directory. The platform-specific location is recommended for better integration with platform services. It is an error for both of these files to exist.
Platform Value Example Linux $XDG_CONFIG_HOME/jj/config.toml
/home/alice/.config/jj/config.toml
macOS $HOME/Library/Application Support/jj/config.toml
/Users/Alice/Library/Application Support/jj/config.toml
Windows {FOLDERID_RoamingAppData}\\jj\\config.toml
C:\\Users\\Alice\\AppData\\Roaming\\jj\\config.toml
The location of the jj
config file can also be overridden with the JJ_CONFIG
environment variable. If it is not empty, it should contain the path to a TOML file that will be used instead of any configuration file in the default locations. For example,
env JJ_CONFIG=/dev/null jj log # Ignores any settings specified in the config file.\n
You can use one or more --config-toml
options on the command line to specify additional configuration settings. This overrides settings defined in config files or environment variables. For example,
jj --config-toml='ui.color=\"always\"' --config-toml='ui.diff-editor=\"kdiff3\"' split\n
Config specified this way must be valid TOML. In particular, string values must be surrounded by quotes. To pass these quotes to jj
, most shells require surrounding those quotes with single quotes as shown above.
In sh
-compatible shells, --config-toml
can be used to merge entire TOML files with the config specified in .jjconfig.toml
:
jj --config-toml=\"$(cat extra-config.toml)\" log\n
"},{"location":"conflicts.html","title":"First-class conflicts","text":""},{"location":"conflicts.html#introduction","title":"Introduction","text":"Like Pijul and Darcs but 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 for how this works).
"},{"location":"conflicts.html#advantages","title":"Advantages","text":"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.
- 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. Since the changes in a merge commit are displayed and rebased as expected, evil merges 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 and octopus merges 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.
"},{"location":"conflicts.html#conflict-markers","title":"Conflict markers","text":"Conflicts are \"materialized\" using conflict markers in various contexts. For example, when you run 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). Here's an example of how Git can render a conflict using its \"diff3\" style:
<<<<<<< left\n apple\n grapefruit\n orange\n ======= base\n apple\n grape\n orange\n ||||||| right\n APPLE\n GRAPE\n ORANGE\n >>>>>>>\n
In this example, the left side changed \"grape\" to \"grapefruit\", and the right side made all lines uppercase. To resolve the conflict, we would presumably keep the right side (the third section) and replace \"GRAPE\" by \"GRAPEFRUIT\". This way of visually finding the changes between the base and one side and then applying them to the other side is a common way of resolving conflicts when using Git's \"diff3\" style.
Jujutsu helps you by combining the base and one side into a unified diff for you, making it easier to spot the differences to apply to the other side. Here's how that would look for the same example as above:
<<<<<<<\n %%%%%%%\n apple\n -grape\n +grapefruit\n orange\n +++++++\n APPLE\n GRAPE\n ORANGE\n >>>>>>>\n
As in Git, the <<<<<<<
and >>>>>>>
lines mark the start and end of the conflict. The %%%%%%%
line indicates the start of a diff. The +++++++
line indicates the start of a snapshot (not a diff).
There is another reason for this format (in addition to helping you spot the differences): The format supports more complex conflicts involving more than 3 inputs. Such conflicts can arise when you merge more than 2 commits. They would typically be rendered as a single snapshot (as above) but with more than one unified diffs. The process for resolving them is similar: Manually apply each diff onto the snapshot.
"},{"location":"contributing.html","title":"How to Contribute","text":""},{"location":"contributing.html#policies","title":"Policies","text":"We'd love to accept your patches and contributions to this project. There are just a few small guidelines you need to follow.
"},{"location":"contributing.html#contributor-license-agreement","title":"Contributor License Agreement","text":"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.
"},{"location":"contributing.html#code-reviews","title":"Code reviews","text":"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.
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 the 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.
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 checking out the commit (jj checkout/new <commit>
) and then squash in the changes when you're done (jj squash
). jj git push
will automatically force-push the branch.
When your first PR has been approved, we typically 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.
"},{"location":"contributing.html#community-guidelines","title":"Community Guidelines","text":"This project follows Google's Open Source Community Guidelines.
"},{"location":"contributing.html#contributing-to-the-documentation","title":"Contributing to the documentation","text":"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.
"},{"location":"contributing.html#learning-rust","title":"Learning Rust","text":"In addition to the Rust Book and the other excellent resources at https://www.rust-lang.org/learn, we recommend the \"Comprehensive Rust\" mini-course for an overview, especially if you are familiar with C++.
"},{"location":"contributing.html#setting-up-a-development-environment","title":"Setting up a development environment","text":"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.
"},{"location":"contributing.html#summary","title":"Summary","text":"One-time setup:
rustup toolchain add nightly # wanted for 'rustfmt'\nrustup toolchain add 1.71 # also specified in Cargo.toml\ncargo install cargo-insta\ncargo install cargo-watch\ncargo install cargo-nextest\n
During development (adapt according to your preference):
cargo watch --ignore '.jj/**' -s \\\n 'cargo clippy --workspace --all-targets \\\n && cargo +1.71 check --workspace --all-targets'\ncargo +nightly fmt # Occasionally\ncargo nextest run --workspace # Occasionally\ncargo insta test --workspace --test-runner nextest # Occasionally\n
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
.
"},{"location":"contributing.html#explanation","title":"Explanation","text":"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.71.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 cargo-watch
. In this case, you should exclude .jj
. directory from the filesystem watcher, as it gets updated on every jj log
.
-
To run tests more quickly, use cargo nextest run --workspace
. To use nextest
with insta
, use cargo insta test --workspace --test-runner nextest
.
"},{"location":"contributing.html#previewing-the-html-documentation","title":"Previewing the HTML documentation","text":"The documentation for jj
is automatically published to the website at https://martinvonz.github.io/jj/.
When editing documentation, we'd appreciate it if you checked that the result will look as expected when published to the website.
"},{"location":"contributing.html#setting-up-the-prerequisites","title":"Setting up the prerequisites","text":"To build the website, you must have Python and poetry
installed. If your distribution packages poetry
, something like apt install python3-poetry
is likely the best way to install it. Otherwise, you can download Python from https://python.org or follow the Python installation instructions. Finally, follow the Poetry installation instructions.
Once you have poetry
installed, you should ask it to install the rest of the required tools into a virtual environment as follows:
poetry install\n
If you get requests to \"unlock a keyring\" or error messages about failing to do so, this is a known poetry
bug. The workaround is to run the following and then to try poetry install
again:
# For sh-compatible shells or recent versions of `fish`\nexport PYTHON_KEYRING_BACKEND=keyring.backends.fail.Keyring\n
"},{"location":"contributing.html#building-the-html-docs-locally-with-live-reload","title":"Building the HTML docs locally (with live reload)","text":"The HTML docs are built with MkDocs. After following the above steps, you should be able to view the docs by running
# Note: this and all the commands below should be run from the root of\n# the `jj` source tree.\npoetry run -- mkdocs serve\n
and opening http://127.0.0.1:8000 in your browser.
As you edit the md
files, the website should be rebuilt and reloaded in your browser automatically, unless build errors occur.
You should occasionally check the terminal from which you ran mkdocs serve
for any build errors or warnings. Warnings about \"GET /versions.json HTTP/1.1\" code 404
are expected and harmless.
"},{"location":"contributing.html#how-to-build-the-entire-website-not-usually-necessary","title":"How to build the entire website (not usually necessary)","text":"The full jj
website includes the documentation for several jj
versions (prerelease
, latest release, and the older releases). The top-level URL https://martinvonz.github.io/jj redirects to https://martinvonz.github.io/jj/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 poetry 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
as a co-located jj + git
repository (jj clone --colocate
), cloned from your fork of jj
(e.g. jjfan.github.com/jj
). You can also use a pure Git repo if you prefer.
-
Make sure jjfan.github.com/jj
includes the gh-pages
branch 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
branch (this is usually the default).
-
Run the same sh
script that is used in GitHub CI (details below):
.github/scripts/docs-build-deploy 'https://jjfan.github.io/jj/'\\\nprerelease main --push\n
This should build the version of the docs from the current commit, deploy it as a new commit to the gh-pages
branch, and push the gh-pages
branch 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.
jj new v1.33.1 # Let's say `jj 1.33.1` is the currently the latest release\n.github/scripts/docs-build-deploy 'https://jjfan.github.io/jj/'\\\nv1.33.1 latest --push\n
-
(Optional) When you are done, you may want to reset the gh-branches
to the same spot as it is in the upstream. If you configured the upstream
remote, this can be done with:
# This will LOSE any changes you made to `gh-pages`\njj git fetch --remote upstream\njj branch set gh-pages -r gh-pages@upstream\njj git push --remote origin --branch gh-pages\n
If you want to preserve some of the changes you made, you can do jj branch set my-changes -r gh-pages
BEFORE running the above commands.
"},{"location":"contributing.html#explanation-of-the-docs-build-deploy-script","title":"Explanation of the docs-build-deploy
script","text":"The script sets up the site_url
mkdocs config to 'https://jjfan.github.io/jj/'
. If this config does not match the URL where you loaded the website, some minor website features (like the version switching widget) will have reduced functionality.
Then, the script passes the rest of its arguments to potery run -- mike deploy
, which does the rest of the job. Run poetry run -- mike help deploy
to find out what the arguments do.
If you need to do something more complicated, you can use poetry run -- mike ...
commands. You can also edit the gh-pages
branch 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
branch of https://martinvonz.github.com/jj (instead of the usual main
branch).
"},{"location":"contributing.html#modifying-protobuffers-this-is-not-common","title":"Modifying protobuffers (this is not common)","text":"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
.
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.
"},{"location":"git-comparison.html","title":"Comparison with Git","text":""},{"location":"git-comparison.html#introduction","title":"Introduction","text":"This document attempts to describe how Jujutsu is different from Git. See the Git-compatibility doc for information about how the jj
command interoperates with Git repos.
"},{"location":"git-comparison.html#overview","title":"Overview","text":"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 further down 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.
- There's no index (staging area). That also results in a simpler CLI for similar reasons. 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. Details. - No need for branch names. Git lets you check out a commit without attaching a branch. 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). 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 'current branch'; instead, you update branches manually. For example, if you check out a commit with a branch, new commits are created on top of the branch, then you issue a later command to update the branch.
- 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.
- 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. - 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 branch by that name in your local repo as well. If you then move it and push back to the remote, the main
branch on the remote will be updated. Details. - 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
- 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
).
"},{"location":"git-comparison.html#the-index","title":"The index","text":"Git's \"index\" 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.
"},{"location":"git-comparison.html#command-equivalence-table","title":"Command equivalence table","text":"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/amend -r <revision>
will move the diff from that revision into its parent.
Use case Jujutsu command Git command Create a new repo jj init --git
(without --git
, you get a native Jujutsu repo, which is slow and whose format will change) git init
Clone an existing repo jj git clone <source> <destination>
(there is no support for cloning non-Git repos yet) git clone <source> <destination>
Update the local repo with all branches from a remote jj git fetch [--remote <remote>]
(there is no support for fetching into non-Git repos yet) git fetch [<remote>]
Update a remote repo with all branches from the local repo jj git push --all [--remote <remote>]
(there is no support for pushing from non-Git repos yet) git push --all [<remote>]
Update a remote repo with a single branch from the local repo jj git push --branch <branch name> [--remote <remote>]
(there is no support for pushing from non-Git repos yet) git push <remote> <branch name>
Show summary of current work and repo status jj st
git status
Show diff of the current change jj diff
git diff HEAD
Show diff of another change jj diff -r <revision>
git diff <revision>^ <revision>
Show diff from another change to the current change jj diff --from <revision>
git diff <revision>
Show diff from change A to change B jj diff --from A --to B
git diff A B
Show description and diff of a change jj show <revision>
git show <revision>
Add a file to the current change touch filename
touch filename; git add filename
Remove a file from the current change rm filename
git rm filename
Modify a file in the current change echo stuff >> filename
echo stuff >> filename
Finish work on the current change and start a new change jj commit
git commit -a
See log of commits jj log
git log --oneline --graph --decorate
Abandon the current change and start a new change jj abandon
git reset --hard
(cannot be undone) Make the current change empty jj restore
git reset --hard
(same as abandoning a change since Git has no concept of a \"change\") Discard working copy changes in some files jj restore <paths>...
git restore <paths>...
or git checkout HEAD -- <paths>...
Edit description (commit message) of the current change jj describe
Not supported Edit description (commit message) of the previous change jj describe @-
git commit --amend
(first make sure that nothing is staged) Temporarily put away the current change Not needed git stash
Start working on a new change based on the <main> branch jj co main
git switch -c topic main
or git checkout -b topic main
(may need to stash or commit first) Move branch A onto branch B jj rebase -b A -d B
git rebase B A
(may need to rebase other descendant branches separately) Move change A and its descendants onto change B jj rebase -s A -d B
git rebase --onto B A^ <some descendant branch>
(may need to rebase other descendant branches separately) Reorder changes from A-B-C-D to A-C-B-D jj rebase -r C -d A; rebase -s B -d C
(pass change IDs, not commit IDs, to not have to look up commit ID of rewritten C) git rebase -i A
Move the diff in the current change into the parent change jj squash/amend
git commit --amend -a
Interactively move part of the diff in the current change into the parent change jj squash/amend -i
git add -p; git commit --amend
Move the diff in the working copy into an ancestor jj move --to X
git commit --fixup=X; git rebase -i --autosquash X^
Interactively move part of the diff in an arbitrary change to another arbitrary change jj move -i --from X --to Y
Not supported Interactively split the changes in the working copy in two jj split
git commit -p
Interactively split an arbitrary change in two jj split -r <revision>
Not supported (can be emulated with the \"edit\" action in git rebase -i
) Interactively edit the diff in a given change jj diffedit -r <revision>
Not supported (can be emulated with the \"edit\" action in git rebase -i
) Resolve conflicts and continue interrupted operation echo resolved > filename; jj squash/amend
(operations don't get interrupted, so no need to continue) echo resolved > filename; git add filename; git rebase/merge/cherry-pick --continue
Create a copy of a commit on top of another commit jj duplicate <source>; jj rebase -r <duplicate commit> -d <destination>
(there's no single command for it yet) git co <destination>; git cherry-pick <source>
List branches jj branch list
git branch
Create a branch jj branch create <name> -r <revision>
git branch <name> <revision>
Move a branch forward jj branch set <name> -r <revision>
git branch -f <name> <revision>
Move a branch backward or sideways jj branch set <name> -r <revision> --allow-backwards
git branch -f <name> <revision>
Delete a branch jj branch delete <name>
git branch --delete <name>
See log of operations performed on the repo jj op log
Not supported Undo an earlier operation jj [op] undo <operation ID>
(jj undo
is an alias for jj op undo
) Not supported"},{"location":"git-compatibility.html","title":"Git compatibility","text":"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).
"},{"location":"git-compatibility.html#supported-features","title":"Supported features","text":"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.
- 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>\"]
). core.excludesFile
- Authentication: Partial. Only
ssh-agent
, a password-less key ( only ~/.ssh/id_rsa
, ~/.ssh/id_ed25519
or ~/.ssh/id_ed25519_sk
), or a credential.helper
. - Branches: Yes. You can read more about how branches work in Jujutsu and how they interoperate with Git.
- Tags: Partial. You can check out tagged commits by name (pointed to be either annotated or lightweight tags), but you cannot create new tags.
- .gitignore: Yes. Ignores in
.gitignore
files are supported. So are ignores in .git/info/exclude
or configured via Git's core.excludesfile
config. 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 about adding support for at least the
eol
attribute. - Hooks: No. There's #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. - 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, however. - Bare repositories: Yes. You can use
jj 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. We use the libgit2 library, which doesn't have support for partial clones.
- Shallow clones: No. We use the libgit2 library, which doesn't have support for shallow clones.
- 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: No. (#58)
- Git LFS: No. (#80)
"},{"location":"git-compatibility.html#creating-an-empty-repo","title":"Creating an empty repo","text":"To create an empty repo using the Git backend, use jj init --git <name>
. Since the command creates a Jujutsu repo, it will have a .jj/
directory. The underlying Git repo will be inside of that directory (currently in .jj/repo/store/git/
).
"},{"location":"git-compatibility.html#creating-a-repo-backed-by-an-existing-git-repo","title":"Creating a repo backed by an existing Git repo","text":"To create a Jujutsu repo backed by a Git repo you already have on disk, use jj init --git-repo=<path to Git repo> <name>
. The repo will work similar to a Git worktree, meaning that the working copies 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.
"},{"location":"git-compatibility.html#creating-a-repo-by-cloning-a-git-repo","title":"Creating a repo by cloning a Git repo","text":"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.
"},{"location":"git-compatibility.html#co-located-jujutsugit-repos","title":"Co-located Jujutsu/Git repos","text":"A \"co-located\" Jujutsu repo is a hybrid Jujutsu/Git repo. These can be created if you initialize the Jujutsu repo in an existing Git repo by running jj init --git-repo=.
or with jj git clone --colocate
. The Git repo and the Jujutsu repo 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 repo 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.
There are a few downsides to this mode of operation. Generally, using co-located repos may require you to deal with more involved Jujutsu and Git concepts.
-
Interleaving jj
and git
commands increases the chance of confusing branch conflicts or conflicted (AKA divergent) change ids. 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.
-
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 repositories are less resilient to concurrency 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.
"},{"location":"git-compatibility.html#converting-a-repo-into-a-co-located-repo","title":"Converting a repo into a co-located repo","text":"A Jujutsu repo backed by a Git repo has a full Git repo inside, so it is technically possible (though not officially supported) to convert it into a co-located repo like so:
# Move the Git repo\nmv .jj/repo/store/git .git\n# Tell jj where to find it\necho -n '../../../.git' > .jj/repo/store/git_target\n# Ignore the .jj directory in Git\necho /.jj/ > .git/info/exclude\n# Make the Git repository non-bare and set HEAD\ngit config --unset core.bare\njj st\n
We may officially support this in the future. If you try this, we would appreciate feedback and bug reports.
"},{"location":"git-compatibility.html#branches","title":"Branches","text":"TODO: Describe how branches are mapped
"},{"location":"git-compatibility.html#format-mapping-details","title":"Format mapping details","text":"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.
Commit metadata that cannot be represented in Git commits (such as the Change ID) is stored outside of the Git repo (currently in .jj/store/extra/
).
Paths with conflicts cannot be represented in Git. They appear as files with a .jjconflict
suffix in the Git repo. They contain a JSON representation with information about the conflict. They are not meant to be human-readable.
"},{"location":"github.html","title":"Using Jujutsu with GitHub and GitLab Projects","text":"This guide assumes a basic understanding of either Git or Mercurial.
"},{"location":"github.html#set-up-an-ssh-key","title":"Set up an SSH key","text":"As of December 2022 it's recommended to set up an SSH key to work with GitHub projects. See GitHub's Tutorial. This restriction may be lifted in the future, see issue #469 for more information and progress on authenticated http.
"},{"location":"github.html#basic-workflow","title":"Basic workflow","text":"The simplest way to start with Jujutsu, is creating a stack of commits, before creating any branch.
# Start a new commit off of `main`\n$ jj new main\n# Refactor some files, then add a description and start a new commit\n$ jj commit -m 'refactor(foo): restructure foo()'\n# Add a feature, then add a description and start a new commit\n$ jj commit -m 'feat(bar): add support for bar'\n# Create a branch so we can push it to GitHub\n$ jj branch create bar -r @-\n# Push the branch to GitHub (pushes only `bar`)\n$ jj git push\n
While it's possible to create a branch and commit on top of it in a Git like manner, it's not recommended, as no further commits will be placed on the branch.
"},{"location":"github.html#updating-the-repository","title":"Updating the repository.","text":"As of December 2022, Jujutsu has no equivalent to a git pull
command. Until such a command is added, you need to use jj git fetch
followed by a jj rebase -d $main_branch
to update your changes.
"},{"location":"github.html#working-in-a-git-co-located-repository","title":"Working in a Git co-located repository","text":"After doing jj init --git-repo=.
, git will be in a detached HEAD state, which is unusual, as git mainly works with branches. In a co-located repository, jj
isn't the source of truth. But Jujutsu allows an incremental migration, as jj commit
updates the HEAD of the git repository.
$ nvim docs/tutorial.md\n$ # Do some more work.\n$ jj commit -m \"Update tutorial\"\n$ jj branch create doc-update\n$ # Move the previous revision to doc-update.\n$ jj branch set doc-update -r @-\n$ jj git push\n
"},{"location":"github.html#working-in-a-jujutsu-repository","title":"Working in a Jujutsu repository","text":"In a Jujutsu repository, the workflow is simplified. If there's no need for explicitly named branches, you just can generate one for a change. As Jujutsu is able to create a branch for a revision.
$ # Do your work\n$ jj commit\n$ # Jujutsu automatically creates a branch\n$ jj git push --change $revision\n
"},{"location":"github.html#addressing-review-comments","title":"Addressing review comments","text":"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 branch1. Some projects (such as Jujutsu and LLVM) instead prefer that you keep your commits clean by rewriting them and then force-pushing2.
"},{"location":"github.html#adding-new-commits","title":"Adding new commits","text":"If your project prefers that you address review comments by adding commits on top, you can do that by doing something like this:
$ # Create a new commit on top of the `your-feature` branch from above.\n$ jj new your-feature\n$ # Address the comments, by updating the code\n$ jj diff\n$ # Give the fix a description and create a new working-copy on top.\n$ jj commit -m 'address pr comments'\n$ # Update the branch to point to the new commit.\n$ jj branch set your-feature -r @-\n$ # Push it to your remote\n$ jj git push.\n
"},{"location":"github.html#rewriting-commits","title":"Rewriting commits","text":"If your project prefers that you keep commits clean, you can do that by doing something like this:
$ # Create a new commit on top of the second-to-last commit in `your-feature`,\n$ # as reviews requested a fix there.\n$ jj new your-feature-\n$ # Address the comments by updating the code\n$ # Review the changes\n$ jj diff\n$ # Squash the changes into the parent commit\n$ jj squash\n$ # Push the updated branch to the remote. Jujutsu automatically makes it a force push\n$ jj git push --branch your-feature\n
"},{"location":"github.html#using-github-cli","title":"Using GitHub CLI","text":"GitHub CLI will have trouble finding the proper git repository path in jj repos that aren't co-located (see issue #1008). You can configure the $GIT_DIR
environment variable to point it to the right path:
$ GIT_DIR=.jj/repo/store/git gh issue list\n
You can make that automatic by installing direnv and defining hooks in a .envrc file in the repository root to configure $GIT_DIR
. Just add this line into .envrc:
export GIT_DIR=$PWD/.jj/repo/store/git\n
and run direnv allow
to approve it for direnv to run. Then GitHub CLI will work automatically even in repos that aren't co-located so you can execute commands like gh issue list
normally.
"},{"location":"github.html#useful-revsets","title":"Useful Revsets","text":"Log all revisions across all local branches, which aren't on the main branch nor on any remote jj log -r 'branches() & ~(main | remote_branches())'
Log all revisions which you authored, across all branches which aren't on any remote jj log -r 'mine() & branches() & ~remote_branches()'
Log all remote branches, which you authored or committed to jj log -r 'remote_branches() & (mine() | committer(your@email.com))'
Log all descendants of the current working copy, which aren't on a remote jj log -r '::@ & ~remote_branches()'
"},{"location":"github.html#merge-conflicts","title":"Merge conflicts","text":"For a detailed overview, how Jujutsu handles conflicts, revisit the tutorial.
"},{"location":"github.html#using-several-remotes","title":"Using several remotes","text":"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. In this case, 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 (for example .jj/repo/config.toml
):
[git]\nfetch = \"upstream\"\npush = \"origin\"\n
The default for both git.fetch
and git.push
is \"origin\".
-
This is a GitHub Style review, as GitHub currently only is able to compare branches.\u00a0\u21a9
-
If you're wondering why we prefer clean commits in this project, see e.g. this blog post \u21a9
"},{"location":"glossary.html","title":"Glossary","text":""},{"location":"glossary.html#anonymous-branch","title":"Anonymous branch","text":"An anonymous branch is a chain of commits that doesn't have any named branches 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, which stores a list of heads of such branches.
"},{"location":"glossary.html#backend","title":"Backend","text":"A backend is an implementation of the storage layer. There are currently two builtin commit backends: the Git backend and the native backend. The Git backend stores commits in a Git repository. The native backend is used for testing purposes only. Alternative backends could be used, for example, if somebody wanted to use jj with a humongous monorepo (as Google does).
There are also pluggable backends for storing other information than commits, such as the \"operation store backend\" for storing the operation log.
"},{"location":"glossary.html#branch","title":"Branch","text":"A branch is a named pointer to a commit. They automatically follow the commit if it gets rewritten. Branches are sometimes called \"named branches\" to distinguish them from anonymous branches, but note that they are more similar to Git's branches than to Mercurial's named branches. See here for details.
"},{"location":"glossary.html#change","title":"Change","text":"A change is a commit as it evolves over time.
"},{"location":"glossary.html#change-id","title":"Change ID","text":"A change ID is a unique identifier for a 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.
For the git backend, Change IDs are currently maintained only locally and not exchanged via push/fetch operations.
"},{"location":"glossary.html#commit","title":"Commit","text":"A snapshot of the files in the repository at a given point in time (technically a tree object), together with some metadata. The metadata includes the author, the date, and pointers to the commit's parents. Through the pointers to the parents, the commits form a Directed Acyclic Graph (DAG) .
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\".
"},{"location":"glossary.html#commit-id","title":"Commit ID","text":"A commit ID is a unique identifier for a 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.
"},{"location":"glossary.html#co-located-repos","title":"Co-located repos","text":"When using the Git backend and the backing Git repository's .git/
directory is a sibling of .jj/
, we call the repository \"co-located\". Most tools designed for Git can be easily used on such repositories. jj
and git
commands can be used interchangeably.
See here for details.
"},{"location":"glossary.html#conflict","title":"Conflict","text":"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 for details.
Conflicts can also occur in branches. For example, if you moved a branch locally, and it was also moved on the remote, then the branch will be in a conflicted state after you pull from the remote. See here for details.
Similar to a branch conflict, when a change is rewritten locally and remotely, for example, then the change will be in a conflicted state. We call that a divergent change.
"},{"location":"glossary.html#divergent-change","title":"Divergent change","text":"A divergent change is a change that has more than one visible commit.
"},{"location":"glossary.html#head","title":"Head","text":"A head is a commit with no descendants. The context in which it has no descendants varies. For example, the heads(X)
revset function returns commits that have no descendants within the set X
itself. The view records which anonymous heads (heads without a branch pointing to them) are visible at a given operation. Note that this is quite different from Git's HEAD.
"},{"location":"glossary.html#operation","title":"Operation","text":"A snapshot of the visible commits and branches at a given point in time (technically a view object), together with some metadata. The metadata includes the username, hostname, timestamps, and pointers to the operation's parents.
"},{"location":"glossary.html#operation-log","title":"Operation log","text":"The operation log is the DAG formed by 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.
"},{"location":"glossary.html#repository","title":"Repository","text":"Basically everything under .jj/
, i.e. the full set of operations and commits.
"},{"location":"glossary.html#remote","title":"Remote","text":"TODO
"},{"location":"glossary.html#revision","title":"Revision","text":"A synonym for Commit.
"},{"location":"glossary.html#revset","title":"Revset","text":"Jujutsu supports a functional language for selecting a set of revisions. Expressions in this language are called \"revsets\". See here for details. We also often use the term \"revset\" for the set of revisions selected by a revset.
"},{"location":"glossary.html#rewrite","title":"Rewrite","text":"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, but the 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.
"},{"location":"glossary.html#root-commit","title":"Root commit","text":"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 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.
"},{"location":"glossary.html#tree","title":"Tree","text":"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.
"},{"location":"glossary.html#visible-commits","title":"Visible commits","text":"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. Ancestors of a visible commit are implicitly visible.
"},{"location":"glossary.html#view","title":"View","text":"A view is a snapshot of branches and their targets, anonymous heads, and working-copy commits. The anonymous heads define which commits are visible.
A view object is similar to a tree object in that it represents a snapshot without history, and an operation object is similar to a commit object in that it adds metadata and history.
"},{"location":"glossary.html#workspace","title":"Workspace","text":"A workspace is a working copy and an associated repository. There can be multiple workspaces for a single repository. Each workspace has a .jj/
directory, but the commits and operations will be stored in the initial workspace; the other workspaces will have pointers to the initial workspace. See here for details.
This is what Git calls a \"worktree\".
"},{"location":"glossary.html#working-copy","title":"Working copy","text":"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 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 for details.
This is what Git calls a \"working tree\".
"},{"location":"glossary.html#working-copy-commit","title":"Working-copy commit","text":"A commit that corresponds to the current state of the working copy. There is one working-copy commit per workspace. The current working-copy commits are tracked in the operation log.
"},{"location":"install-and-setup.html","title":"Installation and setup","text":""},{"location":"install-and-setup.html#installation","title":"Installation","text":""},{"location":"install-and-setup.html#download-pre-built-binaries-for-a-release","title":"Download pre-built binaries for a release","text":"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).
If you'd like to install a prerelease version, you'll need to use one of the options below.
"},{"location":"install-and-setup.html#linux","title":"Linux","text":""},{"location":"install-and-setup.html#build-using-cargo","title":"Build using cargo
","text":"First make sure that you have the libssl-dev
, openssl
, and pkg-config
packages installed by running something like this:
sudo apt-get install libssl-dev openssl pkg-config\n
Now run either:
# To install the *prerelease* version from the main branch\ncargo install --git https://github.com/martinvonz/jj.git --locked --bin jj jj-cli\n
or:
# To install the latest release\ncargo install --locked --bin jj jj-cli\n
"},{"location":"install-and-setup.html#nix-os","title":"Nix OS","text":"If you're on Nix OS you can install a released version of jj
using the nixpkgs jujutsu
package.
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:
nix run 'github:martinvonz/jj'\n
You can also add this flake url to your system input flakes. Or you can install the flake to your user profile:
# Installs the prerelease version from the main branch\nnix profile install 'github:martinvonz/jj'\n
"},{"location":"install-and-setup.html#homebrew","title":"Homebrew","text":"If you use linuxbrew, you can run:
# Installs the latest release\nbrew install jj\n
"},{"location":"install-and-setup.html#mac","title":"Mac","text":""},{"location":"install-and-setup.html#homebrew_1","title":"Homebrew","text":"If you use Homebrew, you can run:
# Installs the latest release\nbrew install jj\n
"},{"location":"install-and-setup.html#macports","title":"MacPorts","text":"You can also install jj
via the MacPorts jujutsu
port:
# Installs the latest release\nsudo port install jujutsu\n
"},{"location":"install-and-setup.html#from-source","title":"From Source","text":"You may need to run some or all of these:
xcode-select --install\nbrew install openssl\nbrew install pkg-config\nexport PKG_CONFIG_PATH=\"$(brew --prefix)/opt/openssl@3/lib/pkgconfig\"\n
Now run either:
# To install the *prerelease* version from the main branch\ncargo install --git https://github.com/martinvonz/jj.git --locked --bin jj jj-cli\n
or:
# To install the latest release\ncargo install --locked --bin jj jj-cli\n
"},{"location":"install-and-setup.html#windows","title":"Windows","text":"Run either:
# To install the *prerelease* version from the main branch\ncargo install --git https://github.com/martinvonz/jj.git --locked --bin jj jj-cli --features vendored-openssl\n
or:
# To install the latest release\ncargo install --locked --bin jj jj-cli --features vendored-openssl\n
"},{"location":"install-and-setup.html#initial-configuration","title":"Initial configuration","text":"You may want to configure your name and email so commits are made in your name.
$ jj config set --user user.name \"Martin von Zweigbergk\"\n$ jj config set --user user.email \"martinvonz@google.com\"\n
"},{"location":"install-and-setup.html#command-line-completion","title":"Command-line completion","text":"To set up command-line completion, source the output of jj util completion --bash/--zsh/--fish
. Exactly how to source it depends on your shell.
"},{"location":"install-and-setup.html#bash","title":"Bash","text":"source <(jj util completion) # --bash is the default\n
"},{"location":"install-and-setup.html#zsh","title":"Zsh","text":"autoload -U compinit\ncompinit\nsource <(jj util completion --zsh)\n
"},{"location":"install-and-setup.html#fish","title":"Fish","text":"jj util completion --fish | source\n
"},{"location":"install-and-setup.html#xonsh","title":"Xonsh","text":"source-bash $(jj util completion)\n
"},{"location":"operation-log.html","title":"Operation log","text":""},{"location":"operation-log.html#introduction","title":"Introduction","text":"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 branch, 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 an operation (jj [op] undo
), which doesn't need to be the most recent one. 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 as well as the -
operator (e.g. @-
) to get the parent of an operation.
"},{"location":"operation-log.html#concurrent-operations","title":"Concurrent operations","text":"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.
"},{"location":"operation-log.html#loading-an-old-version-of-the-repo","title":"Loading an old version of the repo","text":"The top-level --at-operation/--at-op
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.
"},{"location":"related-work.html","title":"Related work","text":"Similar tools:
- 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: A heavily modified fork of Mercurial developed and used at Meta. It is compatible with Git, has undo functionality, and a graphical interface. See how it is different from Jujutsu.
- GitUp: A Mac-only GUI for Git. Like Jujutsu, supports undo and restoring the repo to an earlier snapshot. Backed by its GitUpKit library.
- Gitless: 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).
- Pijul: Architecturally quite different from Jujutsu, but its \"first-class conflicts\" feature seems quite similar to ours.
- Breezy: Another VCS that's similar in that it has multiple storage backends, including its own format as well as .git support.
- Sturdy: A Git backed GUI that eliminates local and remote as well as the idea of an \"index\"/\"staging area\".
"},{"location":"revsets.html","title":"Revsets","text":"Jujutsu supports a functional language for selecting a set of revisions. Expressions in this language are called \"revsets\" (the idea comes from Mercurial). The language consists of symbols, operators, and functions.
Most jj
commands accept a revset (or multiple). Many commands, such as jj diff -r <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.
The commits listed by jj log
without arguments are called \"visible commits\". Other commits are only included if you explicitly mention them (e.g. by commit ID or a Git ref pointing to them).
"},{"location":"revsets.html#symbols","title":"Symbols","text":"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 branch.
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 all visible commits with that change ID (there is typically only one visible commit with a given change ID). A unique prefix of the full change ID can also be used. It is an error to use a non-unique prefix.
Use double quotes 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-\"'
.
"},{"location":"revsets.html#priority","title":"Priority","text":"Jujutsu attempts to resolve a symbol in the following order:
- Tag name
- Branch name
- Git ref
- Commit ID or change ID
"},{"location":"revsets.html#operators","title":"Operators","text":"The following operators are supported. x
and y
below can be any revset, not only symbols.
x & y
: Revisions that are in both x
and y
. x | y
: Revisions that are in either x
or y
(or both). x ~ y
: Revisions that are in x
but not in y
. ~x
: Revisions that are not in x
. x-
: Parents of x
. x+
: Children of x
. ::x
: Ancestors of x
, including the commits in x
itself. x::
: Descendants of x
, including the commits in x
itself. 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
. ::
: All visible commits in the repo. Equivalent to all()
. :x
, x:
, and x:y
: Deprecated versions of ::x
, x::
, and x::y
We plan to delete them in jj 0.15+. 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). ..x
: Ancestors of x
, including the commits in x
itself, but excluding the root commit. Equivalent to ::x ~ root()
. x..
: Revisions that are not ancestors of x
. ..
: All visible commits in the repo, but excluding the root commit. Equivalent to ~root()
.
You can use parentheses to control evaluation order, such as (x & y) | z
or x & (y | z)
.
"},{"location":"revsets.html#functions","title":"Functions","text":"You can also specify revisions by using functions. Some functions take other revsets (expressions) as arguments.
parents(x)
: Same as x-
. children(x)
: Same as 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)
: Same as x::
. connected(x)
: Same as x::x
. Useful when x
includes several commits. all()
: All visible commits in the repo. none()
: No commits. This function is rarely useful; it is provided for completeness. branches([pattern])
: All local branch targets. If pattern
is specified, branches whose name contains the given string are selected. For example, branches(push)
would match the branches push-123
and repushed
but not the branch main
. If a branch is in a conflicted state, all its possible targets are included. remote_branches([branch_pattern[, [remote=]remote_pattern]])
: All remote branch targets across all remotes. If just the branch_pattern
is specified, branches whose name contains the given string across all remotes are selected. If both branch_pattern
and remote_pattern
are specified, the selection is further restricted to just the remotes whose name contains remote_pattern
. For example, remote_branches(push, ri)
would match the branches push-123@origin
and repushed@private
but not push-123@upstream
or main@origin
or main@upstream
. If a branch is in a conflicted state, all its possible targets are included. tags()
: All tag targets. If a tag is in a conflicted state, all its possible targets are included. git_refs()
: All Git ref targets as of the last import. If a Git ref is in a conflicted state, all its possible targets are included. git_head()
: The Git HEAD
target as of the last import. Equivalent to present(HEAD@git)
. visible_heads()
: All visible heads (same as heads(all())
). 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
. Note that this is different from Mercurial's heads(x)
function, which is equivalent to x ~ x-
. roots(x)
: Commits in x
that are not descendants of other commits in x
. Note that this is different from Mercurial's 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. merges()
: Merge commits. description(pattern)
: Commits with the given string in their description. author(pattern)
: Commits with the given string in the author's name or email. mine()
: Commits where the author's email matches the email of the current user. committer(pattern)
: Commits with the given string in the committer's name or email. empty()
: Commits modifying no files. This also includes merges()
without user modifications and root()
. file(pattern..)
: Commits modifying the paths specified by the pattern..
. 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, file(foo)
will match files foo
, foo/bar
, foo/bar/baz
, but not file foobar
. conflict()
: Commits with conflicts. present(x)
: Same as x
, but evaluated to none()
if any of the commits in x
doesn't exist (e.g. is an unknown branch name.)
"},{"location":"revsets.html#string-patterns","title":"String patterns","text":"Functions that perform string matching support the following pattern syntax.
\"string\"
, substring:\"string\"
: Matches strings that contain string
. exact:\"string\"
: Matches strings exactly equal to string
.
"},{"location":"revsets.html#aliases","title":"Aliases","text":"New symbols and functions can be defined in the config file, by using any combination of the predefined symbols/functions and other aliases.
For example:
[revset-aliases]\n'mine' = 'author(martinvonz)'\n'user(x)' = 'author(x) | committer(x)'\n
"},{"location":"revsets.html#examples","title":"Examples","text":"Show the parent(s) of the working-copy commit (like git log -1 HEAD
):
jj log -r @-\n
Show commits not on any remote branch:
jj log -r 'remote_branches()..'\n
Show commits not on origin
(if you have other remotes like fork
):
jj log -r 'remote_branches(remote=origin)..'\n
Show all ancestors of the working copy (almost like plain git log
)
jj log -r ::@\n
Show the initial commits in the repo (the ones Git calls \"root commits\"):
jj log -r root()+\n
Show some important commits (like git --simplify-by-decoration
):
jj log -r 'tags() | branches()'\n
Show local commits leading up to the working copy, as well as descendants of those commits:
jj log -r '(remote_branches()..@)::'\n
Show commits authored by \"martinvonz\" and containing the word \"reset\" in the description:
jj log -r 'author(martinvonz) & description(reset)'\n
"},{"location":"sapling-comparison.html","title":"Comparison with Sapling","text":""},{"location":"sapling-comparison.html#introduction","title":"Introduction","text":"This document attempts to describe how jj is different from Sapling. Sapling is a VCS developed by Meta. It is a heavily modified fork of Mercurial. Because jj has copied many ideas from Mercurial, there are many similarities between the two tools, such as:
- A user-friendly CLI
- A \"revset\" 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
"},{"location":"sapling-comparison.html#differences","title":"Differences","text":"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. 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. 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, which records how the repo has changed over time. Sapling has a similar feature with its 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 much more polished and feature-complete. For example, jj has no
blame/annotate
or bisect
commands, and also no copy/rename support. Sapling also has very nice web UI called Interactive Smartlog, 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
).
"},{"location":"templates.html","title":"Templates","text":"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.
"},{"location":"templates.html#keywords","title":"Keywords","text":"Keywords represent objects of different types; the types are described in a follow-up section.
"},{"location":"templates.html#commit-keywords","title":"Commit keywords","text":"The following keywords can be used in jj log
/jj obslog
templates.
description: String
change_id: ChangeId
commit_id: CommitId
parents: List<Commit>
author: Signature
committer: Signature
working_copies: String
: For multi-workspace repository, indicate working-copy commit as <workspace name>@
. current_working_copy: Boolean
: True for the working-copy commit of the current workspace. branches: String
tags: String
git_refs: String
git_head: String
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). conflict: Boolean
: True if the commit contains merge conflicts. empty: Boolean
: True if the commit modifies no files. root: Boolean
: True if the commit is the root commit.
"},{"location":"templates.html#operation-keywords","title":"Operation keywords","text":"The following keywords can be used in jj op log
templates.
current_operation: Boolean
description: String
id: OperationId
tags: String
time: TimestampRange
user: String
"},{"location":"templates.html#operators","title":"Operators","text":"The following operators are supported.
x.f()
: Method call. x ++ y
: Concatenate x
and y
templates.
"},{"location":"templates.html#global-functions","title":"Global functions","text":"The following functions are defined.
fill(width: Integer, content: Template) -> Template
: Fill lines at the given width
. indent(prefix: Template, content: Template) -> Template
: Indent non-empty lines by the given prefix
. label(label: Template, content: Template) -> Template
: Apply label to the content. The label
is evaluated as a space-separated string. if(condition: Boolean, then: Template[, else: Template]) -> Template
: Conditionally evaluate then
/else
template content. concat(content: Template...) -> Template
: Same as content_1 ++ ... ++ content_n
. separate(separator: Template, content: Template...) -> Template
: Insert separator between non-empty contents.
"},{"location":"templates.html#types","title":"Types","text":""},{"location":"templates.html#boolean-type","title":"Boolean type","text":"No methods are defined. Can be constructed with false
or true
literal.
"},{"location":"templates.html#commit-type","title":"Commit type","text":"This type cannot be printed. All commit keywords are accessible as 0-argument methods.
"},{"location":"templates.html#commitid-changeid-type","title":"CommitId / ChangeId type","text":"The following methods are defined.
.short([len: Integer]) -> String
.shortest([min_len: Integer]) -> ShortestIdPrefix
: Shortest unique prefix.
"},{"location":"templates.html#integer-type","title":"Integer type","text":"No methods are defined.
"},{"location":"templates.html#list-type","title":"List type","text":"The following methods are defined.
.join(separator: Template) -> Template
: Concatenate elements with the given separator
. .map(|item| expression) -> ListTemplate
: Apply template expression
to each element. Example: parents.map(|c| c.commit_id().short())
"},{"location":"templates.html#listtemplate-type","title":"ListTemplate type","text":"The following methods are defined. See also the List
type.
.join(separator: Template) -> Template
"},{"location":"templates.html#operationid-type","title":"OperationId type","text":"The following methods are defined.
.short([len: Integer]) -> String
"},{"location":"templates.html#shortestidprefix-type","title":"ShortestIdPrefix type","text":"The following methods are defined.
.prefix() -> String
.rest() -> String
.upper() -> ShortestIdPrefix
.lower() -> ShortestIdPrefix
"},{"location":"templates.html#signature-type","title":"Signature type","text":"The following methods are defined.
.name() -> String
.email() -> String
.username() -> String
.timestamp() -> Timestamp
"},{"location":"templates.html#string-type","title":"String type","text":"A string can be implicitly converted to Boolean
. The following methods are defined.
.contains(needle: Template) -> Boolean
.first_line() -> String
.lines() -> List<String>
: Split into lines excluding newline characters. .upper() -> String
.lower() -> String
.starts_with(needle: Template) -> Boolean
.ends_with(needle: Template) -> Boolean
.remove_prefix(needle: Template) -> String
: Removes the passed prefix, if present .remove_suffix(needle: Template) -> String
: Removes the passed suffix, if present .substr(start: Integer, end: Integer) -> String
: Extract substring. Negative values count from the end.
"},{"location":"templates.html#template-type","title":"Template type","text":"Most types can be implicitly converted to Template
. No methods are defined.
"},{"location":"templates.html#timestamp-type","title":"Timestamp type","text":"The following methods are defined.
.ago() -> String
: Format as relative timestamp. .format(format: String) -> String
: Format with the specified strftime-like format string. .utc() -> Timestamp
: Convert timestamp into UTC timezone.
"},{"location":"templates.html#timestamprange-type","title":"TimestampRange type","text":"The following methods are defined.
.start() -> Timestamp
.end() -> Timestamp
.duration() -> String
"},{"location":"templates.html#configuration","title":"Configuration","text":"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 cli/src/config/templates.toml
file in jj's source tree.
New keywords and functions can be defined as aliases, by using any combination of the predefined keywords/functions and other aliases.
For example:
[template-aliases]\n'commit_change_ids' = '''\nconcat(\n format_field(\"Commit ID\", commit_id),\n format_field(\"Change ID\", commit_id),\n)\n'''\n'format_field(key, value)' = 'key ++ \": \" ++ value ++ \"\\n\"'\n
"},{"location":"tutorial.html","title":"Tutorial","text":"This text assumes that the reader is familiar with Git.
"},{"location":"tutorial.html#preparation","title":"Preparation","text":"If you haven't already, make sure you install and configure Jujutsu.
"},{"location":"tutorial.html#cloning-a-git-repo","title":"Cloning a Git repo","text":"Let's start by cloning GitHub's Hello-World repo using jj
:
# Note the \"git\" before \"clone\" (there is no support for cloning native jj\n# repos yet)\n$ jj git clone https://github.com/octocat/Hello-World\nFetching into new repo in \"/tmp/tmp.O1DWMiaKd4/Hello-World\"\nWorking copy now at: d7439b06fbef (no description set)\nAdded 1 files, modified 0 files, removed 0 files\n$ cd Hello-World\n