-R, --repository <REPOSITORY> — Path to repository to operate on
+
+
-R, --repository <REPOSITORY> — Path to repository to operate on
+
By default, Jujutsu searches for the closest .jj/ directory in an ancestor of the current working directory.
+
--ignore-working-copy — Don't snapshot the working copy, and don't update it
-
Possible values: true, false
+
By default, Jujutsu snapshots the working copy at the beginning of every command. The working copy is also updated at the end of the command, if the command modified the working-copy commit (@). If you want to avoid snapshotting the working copy and instead see a possibly stale working copy commit, you can use --ignore-working-copy. This may be useful e.g. in a command prompt, especially if you have another process that commits the working copy.
+
Loading the repository at a specific operation with --at-operation implies --ignore-working-copy.
By default, Jujutsu prevents rewriting commits in the configured set of immutable commits. This option disables that check and lets you rewrite any commit but the root commit.
+
This option only affects the check. It does not affect the immutable_heads() revset or the immutable template keyword.
--at-operation <AT_OPERATION> — Operation to load the repo at
+
Operation to load the repo at. By default, Jujutsu loads the repo at the most recent operation. You can use --at-op=<operation ID> to see what the repo looked like at an earlier operation. For example jj --at-op=<operation ID> st will show you what jj st would have shown you when the given operation had just finished.
+
Use jj op log to find the operation ID you want. Any unambiguous prefix of the operation ID is enough.
+
When loading the repo at an earlier operation, the working copy will be ignored, as if --ignore-working-copy had been specified.
+
It is possible to run mutating commands when loading the repo at an earlier operation. Doing that is equivalent to having run concurrent commands starting at the earlier operation. There's rarely a reason to do that, but it is possible.
Default value: @
--debug — Enable debug logging
-
Possible values: true, false
-
-
-
--color <WHEN> — When to colorize output (always, never, debug, auto)
+
--color <WHEN> — When to colorize output (always, never, debug, auto)
--quiet — Silence non-primary command output
-
Possible values: true, false
+
For example, jj file list will still list files, but it won't tell you if the working copy was snapshotted or if descendants were rebased.
+
Warnings and errors will still be printed.
--no-pager — Disable the pager
-
Possible values: true, false
-
-
-
--config-toml <TOML> — Additional configuration options (can be repeated)
+
--config-toml <TOML> — Additional configuration options (can be repeated)
--glob <GLOB> — Deprecated. Please prefix the pattern with glob: instead
+
+
<NAMES> — The branches to delete
+
By default, the specified name matches exactly. Use glob: prefix to select branches by wildcard pattern. For details, see https://github.com/martinvonz/jj/blob/main/docs/revsets.md#string-patterns.
--glob <GLOB> — Deprecated. Please prefix the pattern with glob: instead
+
+
<NAMES> — The branches to forget
+
By default, the specified name matches exactly. Use glob: prefix to select branches by wildcard pattern. For details, see https://github.com/martinvonz/jj/blob/main/docs/revsets.md#string-patterns.
By default, the specified name matches exactly. Use glob: prefix to select branches by wildcard pattern. For details, see https://github.com/martinvonz/jj/blob/main/docs/revsets.md#string-patterns.
<NAMES> — Move branches matching the given name patterns
+
By default, the specified name matches exactly. Use glob: prefix to select branches by wildcard pattern. For details, see https://github.com/martinvonz/jj/blob/main/docs/revsets.md#string-patterns.
A tracking remote branch will be imported as a local branch of the same name. Changes to it will propagate to the existing local branch on future pulls.
By default, the specified name matches exactly. Use glob: prefix to select branches by wildcard pattern. For details, see https://github.com/martinvonz/jj/blob/main/docs/revsets.md#string-patterns.
By default, the specified name matches exactly. Use glob: prefix to select branches by wildcard pattern. For details, see https://github.com/martinvonz/jj/blob/main/docs/revsets.md#string-patterns.
With the -r option, which is the default, shows the changes compared to the parent revision. If there are several parent revisions (i.e., the given revision is a merge), then they will be merged and the changes from the result to the given revision will be shown.
+
With the --from and/or --to options, shows the difference from/to the given revisions. If either is left out, it defaults to the working-copy commit. For example, jj diff --from main shows the changes from "main" (perhaps a branch name) to the working-copy commit.
-r, --revision <REVISION> — Show changes in this revision, compared to its parent(s)
+
If the revision is a merge commit, this shows changes from the automatic merge of the contents of all of its parents to the contents of the revision itself.
+
+
+
--from <FROM> — Show changes from this revision
+
+
--to <TO> — Show changes to this revision
+
-s, --summary — For each path, show only whether it was modified, added, or deleted
+
--stat — Show a histogram of the changes
+
+
--types — For each path, show only its type before and after
+
The diff is shown as two letters. The first letter indicates the type before and the second letter indicates the type after. '-' indicates that the path was not present, 'F' represents a regular file, `L' represents a symlink, 'C' represents a conflict, and 'G' represents a Git submodule.
+
+
+
--name-only — For each path, show only its path
+
Typically useful for shell commands like: jj diff -r @- --name_only | xargs perl -pi -e's/OLD/NEW/g
+
+
+
--git — Show a Git-format diff
+
+
--color-words — Show a word-level diff with changes indicated only by color
+
--tool <TOOL> — Generate diff by external command
+
--context <CONTEXT> — Number of lines of context to show
Touch up the content changes in a revision with a diff editor
+
With the -r option, which is the default, starts a diff editor on the changes in the revision.
+
With the --from and/or --to options, starts a diff editor comparing the "from" revision to the "to" revision.
+
Edit the right side of the diff until it looks the way you want. Once you close the editor, the revision specified with -r or --to will be updated. Descendants will be rebased on top as usual, which may result in conflicts.
+
See jj restore if you want to move entire files from one revision to another. See jj squash -i or jj unsquash -i if you instead want to move changes into or out of the parent revision.
With the -r option, which is the default, shows the changes compared to the parent revision. If there are several parent revisions (i.e., the given revision is a merge), then they will be merged and the changes from the result to the given revision will be shown.
-
With the --from and/or --to options, shows the difference from/to the given revisions. If either is left out, it defaults to the working-copy commit. For example, jj diff --from main shows the changes from "main" (perhaps a branch name) to the working-copy commit.
Touch up the content changes in a revision with a diff editor
-
With the -r option, which is the default, starts a diff editor on the changes in the revision.
-
With the --from and/or --to options, starts a diff editor comparing the "from" revision to the "to" revision.
-
Edit the right side of the diff until it looks the way you want. Once you close the editor, the revision specified with -r or --to will be updated. Descendants will be rebased on top as usual, which may result in conflicts.
-
See jj restore if you want to move entire files from one revision to another. See jj squash -i or jj unsquash -i if you instead want to move changes into or out of the parent revision.
-s, --source <SOURCE> — Fix files in the specified revision(s) and their descendants. If no revisions are specified, this defaults to the revsets.fix setting, or reachable(@, mutable()) if it is not set
<DESTINATION> — The destination directory where the jj repo will be created. If the directory does not exist, it will be created. If no directory is given, the current directory is used
+
<DESTINATION> — The destination directory where the jj repo will be created. If the directory does not exist, it will be created. If no directory is given, the current directory is used.
--colocate — Specifies that the jj repo should also be a valid git repo, allowing the use of both jj and git commands in the same directory
-
Possible values: true, false
+
--colocate — Specifies that the jj repo should also be a valid git repo, allowing the use of both jj and git commands in the same directory.
+
This is done by placing the backing git repo into a .git directory in the root of the jj repo along with the .jj directory. If the .git directory already exists, all the existing commits will be imported.
+
This option is mutually exclusive with --git-repo.
-
--git-repo <GIT_REPO> — Specifies a path to an existing git repository to be used as the backing git repo for the newly created jj repo
--colocate — Whether or not to colocate the Jujutsu repo with the git repo
-
Possible values: true, false
+
--git-repo <GIT_REPO> — Specifies a path to an existing git repository to be used as the backing git repo for the newly created jj repo.
+
If the specified --git-repo path happens to be the same as the jj repo path (both .jj and .git directories are in the same working directory), then both jj and git commands will work on the same repo. This is called a co-located repo.
+
This option is mutually exclusive with --colocate.
By default, pushes any branches pointing to remote_branches(remote=<remote>)..@. Use --branch to push specific branches. Use --all to push all branches. Use --change to generate branch names based on the change IDs of specific commits.
Before the command actually moves, creates, or deletes a remote branch, it makes several safety checks. If there is a problem, you may need to run jj git fetch --remote <remote name> and/or resolve some branch conflicts.
--remote <REMOTE> — The remote to push to (only named remotes are supported)
-
-b, --branch <BRANCH> — Push only this branch, or branches matching a pattern (can be repeated)
+
+
-b, --branch <BRANCH> — Push only this branch, or branches matching a pattern (can be repeated)
+
By default, the specified name matches exactly. Use glob: prefix to select branches by wildcard pattern. For details, see https://martinvonz.github.io/jj/latest/revsets#string-patterns.
+
--all — Push all branches (including deleted branches)
-
Possible values: true, false
--tracked — Push all tracked branches (including deleted branches)
-
Possible values: true, false
+
This usually means that the branch was already pushed to or fetched from the relevant remote. For details, see https://martinvonz.github.io/jj/latest/branches#remotes-and-tracked-branches
--deleted — Push all deleted branches
-
Possible values: true, false
+
Only tracked branches can be successfully deleted on the remote. A warning will be printed if any untracked branches on the remote correspond to missing local branches.
--allow-empty-description — Allow pushing commits with empty descriptions
-
Possible values: true, false
-
-
-
-r, --revisions <REVISIONS> — Push branches pointing to these commits (can be repeated)
+
-r, --revisions <REVISIONS> — Push branches pointing to these commits (can be repeated)
-c, --change <CHANGE> — Push this commit by creating a branch based on its change ID (can be repeated)
-
-
--dry-run — Only display what will change on the remote
-
Possible values: true, false
-
+
--dry-run — Only display what will change on the remote
This excludes changes from other commits by temporarily rebasing --from onto --to's parents. If you wish to compare the same change across versions, consider jj obslog -p instead.
-s, --summary — For each path, show only whether it was modified, added, or deleted
-
Possible values: true, false
-
-
-
--stat — Show a histogram of the changes
-
Possible values: true, false
-
+
-s, --summary — For each path, show only whether it was modified, added, or deleted
+
--stat — Show a histogram of the changes
--types — For each path, show only its type before and after
-
Possible values: true, false
+
The diff is shown as two letters. The first letter indicates the type before and the second letter indicates the type after. '-' indicates that the path was not present, 'F' represents a regular file, `L' represents a symlink, 'C' represents a conflict, and 'G' represents a Git submodule.
+
+
+
--name-only — For each path, show only its path
+
Typically useful for shell commands like: jj diff -r @- --name_only | xargs perl -pi -e's/OLD/NEW/g
--git — Show a Git-format diff
-
Possible values: true, false
-
-
-
--color-words — Show a word-level diff with changes indicated only by color
-
Possible values: true, false
-
-
-
--tool <TOOL> — Generate diff by external command
+
--color-words — Show a word-level diff with changes indicated only by color
+
--tool <TOOL> — Generate diff by external command
--context <CONTEXT> — Number of lines of context to show
Renders a graphical view of the project's history, ordered with children before parents. By default, the output only includes mutable revisions, along with some additional revisions for context.
Spans of revisions that are not included in the graph per --revisions are rendered as a synthetic node labeled "(elided revisions)".
-r, --revisions <REVISIONS> — Which revisions to show. If no paths nor revisions are specified, this defaults to the revsets.log setting, or @ | ancestors(immutable_heads().., 2) | trunk() if it is not set
+
--reversed — Show revisions in the opposite order (older revisions first)
-
--reversed — Show revisions in the opposite order (older revisions first)
-
Possible values: true, false
-
-
-
-l, --limit <LIMIT> — Limit number of revisions to show
+
-n, --limit <LIMIT> — Limit number of revisions to show
+
Applied after revisions are filtered and reordered.
--no-graph — Don't show the graph, show a flat list of revisions
-
Possible values: true, false
-T, --template <TEMPLATE> — Render each revision using the given template
+
For the syntax, see https://github.com/martinvonz/jj/blob/main/docs/templates.md
-p, --patch — Show patch
-
Possible values: true, false
-
-
-
-s, --summary — For each path, show only whether it was modified, added, or deleted
-
Possible values: true, false
-
-
-
--stat — Show a histogram of the changes
-
Possible values: true, false
+
-s, --summary — For each path, show only whether it was modified, added, or deleted
+
--stat — Show a histogram of the changes
--types — For each path, show only its type before and after
-
Possible values: true, false
+
The diff is shown as two letters. The first letter indicates the type before and the second letter indicates the type after. '-' indicates that the path was not present, 'F' represents a regular file, `L' represents a symlink, 'C' represents a conflict, and 'G' represents a Git submodule.
+
+
+
--name-only — For each path, show only its path
+
Typically useful for shell commands like: jj diff -r @- --name_only | xargs perl -pi -e's/OLD/NEW/g
--git — Show a Git-format diff
-
Possible values: true, false
-
-
-
--color-words — Show a word-level diff with changes indicated only by color
-
Possible values: true, false
-
-
-
--tool <TOOL> — Generate diff by external command
+
--color-words — Show a word-level diff with changes indicated only by color
+
--tool <TOOL> — Generate diff by external command
--context <CONTEXT> — Number of lines of context to show
Note that you can create a merge commit by specifying multiple revisions as argument. For example, jj new main @ will create a new commit with the main branch and the working copy as parents.
For more information, see https://github.com/martinvonz/jj/blob/main/docs/working-copy.md.
-l, --limit <LIMIT> — Limit number of revisions to show
-
-
-
--no-graph — Don't show the graph, show a flat list of revisions
-
Possible values: true, false
+
-n, --limit <LIMIT> — Limit number of revisions to show
+
--no-graph — Don't show the graph, show a flat list of revisions
-T, --template <TEMPLATE> — Render each revision using the given template
+
For the syntax, see https://github.com/martinvonz/jj/blob/main/docs/templates.md
-p, --patch — Show patch compared to the previous version of this change
-
Possible values: true, false
+
If the previous version has different parents, it will be temporarily rebased to the parents of the new version, so the diff is not contaminated by unrelated changes.
-s, --summary — For each path, show only whether it was modified, added, or deleted
-
Possible values: true, false
-
-
-
--stat — Show a histogram of the changes
-
Possible values: true, false
+
--stat — Show a histogram of the changes
--types — For each path, show only its type before and after
-
Possible values: true, false
+
The diff is shown as two letters. The first letter indicates the type before and the second letter indicates the type after. '-' indicates that the path was not present, 'F' represents a regular file, `L' represents a symlink, 'C' represents a conflict, and 'G' represents a Git submodule.
+
+
+
--name-only — For each path, show only its path
+
Typically useful for shell commands like: jj diff -r @- --name_only | xargs perl -pi -e's/OLD/NEW/g
--git — Show a Git-format diff
-
Possible values: true, false
-
-
-
--color-words — Show a word-level diff with changes indicated only by color
-
Possible values: true, false
-
-
-
--tool <TOOL> — Generate diff by external command
+
--color-words — Show a word-level diff with changes indicated only by color
+
--tool <TOOL> — Generate diff by external command
--context <CONTEXT> — Number of lines of context to show
Use jj op log to find an operation to restore to. Use e.g. jj --at-op=<operation ID> log before restoring to an operation to see the state of the repo at that operation.
--what <WHAT> — What portions of the local state to restore (can be repeated)
+
This option is EXPERIMENTAL.
Default values: repo, remote-tracking
Possible values:
@@ -6667,7 +6750,7 @@ remains a descendant of both 1 and 2.
not in the target set, was a descendant of 1 before, so it remains a
descendant, and it was an ancestor of 3 before, so it remains an ancestor.
-b, --branch <BRANCH> — Rebase the whole branch relative to destination's ancestors (can be repeated)
-
-s, --source <SOURCE> — Rebase specified revision(s) together with their trees of descendants (can be repeated)
-
-r, --revisions <REVISIONS> — Rebase the given revisions, rebasing descendants onto this revision's parent(s)
-
-d, --destination <DESTINATION> — The revision(s) to rebase onto (can be repeated to create a merge commit)
-
-A, --insert-after <INSERT_AFTER> — The revision(s) to insert after (can be repeated to create a merge commit)
-
-B, --insert-before <INSERT_BEFORE> — The revision(s) to insert before (can be repeated to create a merge commit)
-
--skip-empty — If true, when rebasing would produce an empty commit, the commit is abandoned. It will not be abandoned if it was already empty before the rebase. Will never skip merge commits with multiple non-empty parents
-
Possible values: true, false
+
-b, --branch <BRANCH> — Rebase the whole branch relative to destination's ancestors (can be repeated)
+
jj rebase -b=br -d=dst is equivalent to jj rebase '-s=roots(dst..br)' -d=dst.
+
If none of -b, -s, or -r is provided, then the default is -b @.
-
-L, --allow-large-revsets — Deprecated. Please prefix the revset with all: instead
-
Possible values: true, false
+
-s, --source <SOURCE> — Rebase specified revision(s) together with their trees of descendants (can be repeated)
+
Each specified revision will become a direct child of the destination revision(s), even if some of the source revisions are descendants of others.
+
If none of -b, -s, or -r is provided, then the default is -b @.
+
+
+
-r, --revisions <REVISIONS> — Rebase the given revisions, rebasing descendants onto this revision's parent(s)
+
Unlike -s or -b, you may jj rebase -r a revision A onto a descendant of A.
+
If none of -b, -s, or -r is provided, then the default is -b @.
+
+
+
-d, --destination <DESTINATION> — The revision(s) to rebase onto (can be repeated to create a merge commit)
+
+
+
-A, --insert-after <INSERT_AFTER> — The revision(s) to insert after (can be repeated to create a merge commit)
+
Only works with -r.
+
+
+
-B, --insert-before <INSERT_BEFORE> — The revision(s) to insert before (can be repeated to create a merge commit)
+
Only works with -r.
+
+
+
--skip-empty — If true, when rebasing would produce an empty commit, the commit is abandoned. It will not be abandoned if it was already empty before the rebase. Will never skip merge commits with multiple non-empty parents
@@ -6805,11 +6901,11 @@ commit. This is true in general; it is not specific to this command.
Only conflicts that can be resolved with a 3-way merge are supported. See docs for merge tool configuration instructions.
Note that conflicts can also be resolved without using this command. You may edit the conflict markers in the conflicted file directly with a text editor.
<PATHS> — Restrict to these paths when searching for a conflict to resolve. We will attempt to resolve the first conflict we can find. You can use the --list argument to find paths to use here
@@ -6830,16 +6923,19 @@ commit. This is true in general; it is not specific to this command.
When neither --from nor --to is specified, the command restores into the working copy from its parent(s). jj restore without arguments is similar to jj abandon, except that it leaves an empty revision with its description and other metadata preserved.
See jj diffedit if you'd like to restore portions of files rather than entire files.
--to <TO> — Revision to restore into (destination)
-
-c, --changes-in <REVISION> — Undo the changes in a revision as compared to the merge of its parents
-
-r, --revision <REVISION> — Prints an error. DO NOT USE
+
+
-c, --changes-in <REVISION> — Undo the changes in a revision as compared to the merge of its parents.
+
This undoes the changes that can be seen with jj diff -r REVISION. If REVISION only has a single parent, this option is equivalent to jj restore --to REVISION --from REVISION-.
+
The default behavior of jj restore is equivalent to jj restore --changes-in @.
-r — Ignored (but lets you pass -r for consistency with other commands)
-
Possible values: true, false
-
-
-T, --template <TEMPLATE> — Render a revision using the given template
+
For the syntax, see https://github.com/martinvonz/jj/blob/main/docs/templates.md
-s, --summary — For each path, show only whether it was modified, added, or deleted
-
Possible values: true, false
-
-
-
--stat — Show a histogram of the changes
-
Possible values: true, false
+
--stat — Show a histogram of the changes
--types — For each path, show only its type before and after
-
Possible values: true, false
+
The diff is shown as two letters. The first letter indicates the type before and the second letter indicates the type after. '-' indicates that the path was not present, 'F' represents a regular file, `L' represents a symlink, 'C' represents a conflict, and 'G' represents a Git submodule.
+
+
+
--name-only — For each path, show only its path
+
Typically useful for shell commands like: jj diff -r @- --name_only | xargs perl -pi -e's/OLD/NEW/g
--git — Show a Git-format diff
-
Possible values: true, false
-
-
-
--color-words — Show a word-level diff with changes indicated only by color
-
Possible values: true, false
-
-
-
--tool <TOOL> — Generate diff by external command
+
--color-words — Show a word-level diff with changes indicated only by color
+
--tool <TOOL> — Generate diff by external command
--context <CONTEXT> — Number of lines of context to show
List the patterns that are currently present in the working copy
By default, a newly cloned or initialized repo will have have a pattern matching all files from the repo root. That pattern is rendered as . (a single period).
Update the patterns that are present in the working copy
For example, if all you need is the README.md and the lib/ directory, use jj sparse set --clear --add README.md --add lib. If you no longer need the lib directory, use jj sparse set --remove lib.
Starts a diff editor on the changes in the revision. Edit the right side of the diff until it has the content you want in the first revision. Once you close the editor, your edited content will replace the previous revision. The remaining changes will be put in a new revision on top.
If the change you split had a description, you will be asked to enter a change description for each commit. If the change did not have a description, the second part will not get a description, and you will be asked for a description only for the first part.
Splitting an empty commit is not supported because the same effect can be achieved with jj new.
Move changes from a revision into another revision
With the -r option, moves the changes from the specified revision to the parent revision. Fails if there are several parent revisions (i.e., the given revision is a merge).
With the --from and/or --into options, moves changes from/to the given revisions. If either is left out, it defaults to the working-copy commit. For example, jj squash --into @-- moves changes from the working-copy commit to the grandparent.
-
If, after moving changes out, the source revision is empty compared to its parent(s), it will be abandoned. Without --interactive, the source revision will always be empty.
+
If, after moving changes out, the source revision is empty compared to its parent(s), and --keep-emptied is not set, it will be abandoned. Without --interactive or paths, the source revision will always be empty.
If the source became empty and both the source and destination had a non-empty description, you will be asked for the combined description. If either was empty, then the other one will be used.
If a working-copy commit gets abandoned, it will be given a new, empty commit. This is true in general; it is not specific to this command.
By default, the specified name matches exactly. Use glob: prefix to select tags by wildcard pattern. For details, see https://github.com/martinvonz/jj/blob/main/docs/revsets.md#string-patterns.
--what <WHAT> — What portions of the local state to restore (can be repeated)
+
This option is EXPERIMENTAL.
Default values: repo, remote-tracking
Possible values:
@@ -7109,7 +7178,7 @@ compinit
If the source became empty and both the source and destination had a non-empty description, you will be asked for the combined description. If either was empty, then the other one will be used.
If a working-copy commit gets abandoned, it will be given a new, empty commit. This is true in general; it is not specific to this command.
Each workspace has its own working-copy commit. When you have more than one workspace attached to a repo, they are indicated by @<workspace name> in jj log.
-r, --revision <REVISION> — A list of parent revisions for the working-copy commit of the newly created workspace. You may specify nothing, or any number of parents
+
+
--name <NAME> — A name for the workspace
+
To override the default, which is the basename of the destination directory.
+
+
+
-r, --revision <REVISION> — A list of parent revisions for the working-copy commit of the newly created workspace. You may specify nothing, or any number of parents.
+
If no revisions are specified, the new workspace will be created, and its working-copy commit will exist on top of the parent(s) of the working-copy commit in the current workspace, i.e. they will share the same parent(s).
+
If any revisions are specified, the new workspace will be created, and the new working-copy commit will be created with all these revisions as parents, i.e. the working-copy commit will exist as if you had run jj new r1 r2 r3 ....
@@ -2351,6 +2431,14 @@ the editor when describing changes with an empty description. This could be a
useful reminder to fill in things like BUG=, TESTED= etc.
# Possible values: "color-words" (default), "git", "summary"ui.diff.format="git"
@@ -2482,10 +2570,12 @@ a $):
ui.pager > $PAGER
less -FRX is the default pager in the absence of any other setting, except
on Windows where it is :builtin.
-
The special value :builtin enables usage of the
-integrated pager. It is likely if you
-are using a standard Linux distro, your system has $PAGER set already
-and that will be preferred over the built-in. To use the built-in:
If you are using a standard Linux distro, your system likely already has
+$PAGER set and that will be preferred over the built-in. To use the built-in:
jj config set --user ui.pager :builtin
It is possible the default will change to :builtin for all platforms in the
@@ -2799,6 +2889,9 @@ snapshots without having to rescan the entire working copy.
You can configure jj to use watchman triggers to automatically create
+snapshots on filestem changes by setting
+core.watchman.register_snapshot_trigger = true.
You can check whether Watchman is enabled and whether it is installed correctly
using jj debug watchman status.
Before sending a PR for a large change which designs/redesigns or reworks an
+existing component, we require an architecture review from multiple
+stakeholders, which we do with Design Docs, see the
+process here.
Summary: This Document documents an approach to tracking and detecting copy
+information in jj repos, in a way that is compatible with both Git's detection
+model and with custom backends that have more complicated tracking of copy
+information. This design affects the output of diff commands as well as the
+results of rebasing across remote copies.
Implement extensible APIs for recording and retrieving copy info for the
+purposes of diffing and rebasing across renames and copies more accurately.
+This should be performant both for Git, which synthesizes copy info on the fly
+between arbitrary trees, and for custom extensions which may explicitly record
+and re-serve copy info over arbitrarily large commit ranges.
+
The APIs should be defined in a way that makes it easy for custom backends to
+ignore copy info entirely until they are ready to implement it.
Copy information will be served both by a new Backend trait method described
+below, as well as a new field on Commit objects for backends that support copy
+tracking:
+
/// An individual copy source.
+pubstructCopySource{
+/// The source path a target was copied from.
+///
+/// It is not required that the source path is different than the target
+/// path. A custom backend may choose to represent 'rollbacks' as copies
+/// from a file unto itself, from a specific prior commit.
+path:RepoPathBuf,
+file:FileId,
+/// The source commit the target was copied from. If not specified, then the
+/// parent of the target commit is the source commit. Backends may use this
+/// field to implement 'integration' logic, where a source may be
+/// periodically merged into a target, similar to a branch, but the
+/// branching occurs at the file level rather than the repository level. It
+/// also follows naturally that any copy source targeted to a specific
+/// commit should avoid copy propagation on rebasing, which is desirable
+/// for 'fork' style copies.
+///
+/// If specified, it is required that the commit id is an ancestor of the
+/// commit with which this copy source is associated.
+commit:Option<CommitId>,
+}
+
+pubenumCopySources{
+Resolved(CopySource),
+Conflict(HashSet<CopySource>),
+}
+
+/// An individual copy event, from file A -> B.
+pubstructCopyRecord{
+/// The destination of the copy, B.
+target:RepoPathBuf,
+/// The CommitId where the copy took place.
+id:CommitId,
+/// The source of the copy, A.
+sources:CopySources,
+}
+
+/// Backend options for fetching copy records.
+pubstructCopyRecordOpts{
+// TODO: Probably something for git similarity detection
+}
+
+pubtypeCopyRecordStream=BoxStream<BackendResult<CopyRecord>>;
+
+pubtraitBackend{
+/// Get all copy records for `paths` in the dag range `roots..heads`.
+///
+/// The exact order these are returned is unspecified, but it is guaranteed
+/// to be reverse-topological. That is, for any two copy records with
+/// different commit ids A and B, if A is an ancestor of B, A is streamed
+/// after B.
+///
+/// Streaming by design to better support large backends which may have very
+/// large single-file histories. This also allows more iterative algorithms
+/// like blame/annotate to short-circuit after a point without wasting
+/// unnecessary resources.
+asyncfnget_copy_records(&self,paths:&[RepoPathBuf],roots:&[CommitId],heads:&[CommitId])->CopyRecordStream;
+}
+
+
Obtaining copy records for a single commit requires first computing the files
+list for that commit, then calling get_copy_records with heads = [id] and
+roots = parents(). This enables commands like jj diff to produce better
+diffs that take copy sources into account.
Backends that support tracking copy records at the commit level will do so
+through a new field on backend::Commit objects:
+
pubstructCommit{
+...
+copies:Option<HashMap<RepoPathBuf,CopySources>>,
+}
+
+pubtraitBackend{
+/// Whether this backend supports storing explicit copy records on write.
+fnsupports_copy_tracking(&self)->bool;
+}
+
+
This field will be ignored by backends that do not support copy tracking, and
+always set to None when read from such backends. Backends that do support copy
+tracking are required to preserve the field value always.
+
This API will enable the creation of new jj commands for recording copies:
+
jjcp$SRC$DEST[OPTIONS]
+jjmv$SRC$DEST[OPTIONS]
+
+
These commands will rewrite the target commit to reflect the given move/copy
+instructions in its tree, as well as recording the rewrites on the Commit
+object itself for backends that support it (for backends that do not,
+these copy records will be silently discarded).
+
Flags for the first two commands will include:
+
-r/--revision
+ perform the copy or move at the specified revision
+ defaults to the working copy commit if unspecified
+-f
+ force overwrite the destination path
+--after
+ record the copy retroactively, without modifying the targeted commit tree
+--resolve
+ overwrite all previous copy intents for this $DEST
+--allow-ignore-copy
+ don't error if the backend doesn't support copy tracking
+--from REV
+ specify a commit id for the copy source that isn't the parent commit
+
+
For backends which do not support copy tracking, it will be an error to use
+--after, since this has no effect on anything and the user should know that.
+The supports_copy_tracking() trait method is used to determine this.
+
An additional command is provided to deliberately discard copy info for a
+destination path, possibly as a means of resolving a conflict.
In general, we want to support the following use cases:
+
+
A rebase of an edited file A across a rename of A->B should transparently move the edits to B.
+
A rebase of an edited file A across a copy from A->B should optionally copy the edits to B. A configuration option should be defined to enable/disable this behavior.
+
TODO: Others?
+
+
Using the aforementioned copy tracing API, both of these should be feasible. A
+detailed approach to a specific edge case is detailed in the next section.
A well known and thorny problem in Mercurial occurs in the following scenario:
+
+
Create a new file A
+
Create new commits on top that make changes to file A
+
Whoops, I should rename file A to B. Do so, amend the first commit.
+
Because the first commit created file A, there is no rename to record; it's changing to a commit that instead creates file B.
+
All child commits get sad on evolve
+
+
In jj, we have an opportunity to fix this because all rebasing occurs atomically
+and transactionally within memory. The exact implementation of this is yet to be
+determined, but conceptually the following should produce desirable results:
+
+
Rebase commit A from parents [B] to parents [C]
+
Get copy records from [D]->[B] and [D]->[C], where [D] are the common ancestors of [B] and [C]
+
DescendantRebaser maintains an in-memory map of commits to extra copy info, which it may inject into (2). When squashing a rename of a newly created file into the commit that creates that file, DescendentRebase will return this rename for all rebases of descendants of the newly modified commit. The rename lives ephemerally in memory and has no persistence after the rebase completes.
+
A to-be-determined algorithm diffs the copy records between [D]->[B] and [D]->[C] in order to make changes to the rebased commit. This results in edits to renamed files being propagated to those renamed files, and avoiding conflicts on the deletion of their sources. A copy/move may also be undone in this way; abandoning a commit which renames A->B should move all descendant edits of B back into A.
With copy-tracking, a whole new class of conflicts become possible. These need
+to be well-defined and have well documented resolution paths. Because copy info
+in a commit is keyed by destination, conflicts can only occur at the
+destination of a copy, not at a source (that's called forking).
Suppose we create commit A by renaming file F1 -> F2, then we split A. What
+happens to the copy info? I argue that this is straightforward:
+
+
If F2 is preserved at all in the parent commit, the copy info stays on the parent commit.
+
Otherwise, the copy info goes onto the child commit.
+
+
Things get a little messier if A also modifies F1, and this modification is
+separated from the copy, but I think this is messy only in an academic sense and
+the user gets a sane result either way. If they want to separate the
+modification from the copy while still putting it in an earlier commit, they can
+express this intent after with jj cp --after --from.
Suppose we create commit A by renaming file F1 -> F, then we create a sibling
+commit B by renaming file F2 -> F. What happens when we create a merge commit
+with parents A and B?
+
In terms of copy info there is no conflict here, because C does not have copy
+info and needs none, but resolving the contents of F becomes more complicated.
+We need to (1) identify the greatest common ancestor of A and B (D)
+(which we do anyway), and (2) invoke get_copy_records() on F for each of
+D::A and D::B to identify the 'real' source file id for each parent. If
+these are the same, then we can use that as the base for a better 3-way merge.
+Otherwise, we must treat it as an add+add conflict where the base is the empty
+file id.
+
It is possible that F1 and F2 both came from a common source file G, but that
+these copies precede D. In such case, we will not produce as good of a merge
+resolution as we theoretically could, but (1) this seems extremely niche and
+unlikely, and (2) we cannot reasonably achieve this without implementing some
+analogue of Mercurial's linknodes concept, and it would be nice to avoid that
+additional complexity.
Suppose we create commit A by renaming file F1 -> F, then we create child
+commit B in which we replace F by renaming F2 -> F. This touches on two issues.
+
Firstly, if these two commits are squashed together, then we have a destination
+F with two copy sources, F1 and F2. In this case, we can store a
+CopySources::Conflict([F1, F2]) as the copy source for F, and treat this
+commit as 'conflicted' in jj log. jj status will need modification to show
+this conflicted state, and jj resolve will need some way of handling the
+conflicted copy sources (possibly printing them in some structured text form,
+and using the user's merge tool to resolve them). Alternatively, the user can
+'resolve directly' by running jj cp --after --resolve with the desired copy
+info.
+
Secondly, who is to say that commit B is 'replacing' F at all? In some version
+control systems, it is possible to 'integrate' a file X into an existing file Y,
+by e.g. propagating changes in X since its previous 'integrate' into Y, without
+erasing Y's prior history in that moment for the purpose of archaeology. With
+the commit metadata currently defined, it is not possible to distinguish
+between a 'replacement' operation and an 'integrate' operation.
One solution is to add a destructive: bool field or similar to the
+CopySource struct, to explicitly distinguish between these two types of copy
+records. It then becomes possible to record a non-destructive copy using
+--after to recognize that a file F was 'merged into' its destination, which
+can be useful in handling parallel edits of F that later sync this information.
Alternatively, we can keep copy-tracking simple in jj by taking a stronger
+stance here and treating all copies-onto-existing-files as 'replacement'
+operations. This makes integrations with more complex VCSs that do support
+'integrate'-style operations trickier, but it is possible that a more generic
+commit extension system is better suited to such backends.
An implementation of jj blame or jj annotate does not currently exist, but
+when it does we'll definitely want it to be copy-tracing aware to provide
+better annotations for users doing archaeology. The Read APIs provided are
+expected to be sufficient for these use cases.
Git uses rename detection rather than copy tracking, generating copy info on
+the fly between two arbitrary trees. It does not have any place for explicit
+copy info that exchanges with other users of the same git repo, so any
+enhancements jj adds here would be local only and could potentially introduce
+confusion when collaborating with other users.
All copy/move information will be read and written at the file level. While
+jj cp|mv may accept directory paths as a convenience and perform the
+appropriate tree modification operations, the renames will be recorded at the
+file level, one for each copied/moved file.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/latest/design/git-submodule-storage/index.html b/latest/design/git-submodule-storage/index.html
index 58efb2319..df5c34489 100644
--- a/latest/design/git-submodule-storage/index.html
+++ b/latest/design/git-submodule-storage/index.html
@@ -448,6 +448,26 @@
+
The place to describe all new interfaces and interactions and how it plays into
+the existing code and behavior. This is the place for all nitty-gritty details
+which interact with the system.
If there's a feature in another VCS which shares some similarities to your
+proposed work, it belongs here. An example would be Jujutsu sparse workspaces
+and Perforce client workspaces.
Jujutsu uses Design Docs to drive technical decisions on large projects and it
+is the place to discuss your proposed design or new component. It is a very
+thorough process, in which the design doc must be approved before PRs for the
+feature will be accepted. It shares some similarities with Rust RFCs but
+mostly addresses technical problems and gauges the technical and social
+concerns of all stakeholders.
+
So if you want to start building the native backend or the server component for
+Jujutsu, you'll need to go through this process.