mirror of
https://github.com/jj-vcs/jj.git
synced 2026-07-03 14:02:54 +08:00
The comment about classify_bookmark_push_action() was also removed because --bookmark=NAME supports auto-tracking.
347 lines
11 KiB
Markdown
347 lines
11 KiB
Markdown
# Using Jujutsu with GitHub and GitLab Projects
|
||
|
||
This guide assumes a basic understanding of either Git or Mercurial.
|
||
|
||
## Basic workflow
|
||
|
||
The simplest way to start with Jujutsu is to create a stack of commits first.
|
||
You will only need to create a bookmark when you need to push the stack to a
|
||
remote. There are two primary workflows: using a generated bookmark name or
|
||
naming a bookmark.
|
||
|
||
### Using a generated bookmark name
|
||
|
||
In this example we're letting Jujutsu auto-create a bookmark.
|
||
|
||
```shell
|
||
# Start a new commit off of the default bookmark.
|
||
$ jj new main
|
||
# Refactor some files, then add a description and start a new commit
|
||
$ jj commit -m 'refactor(foo): restructure foo()'
|
||
# Add a feature, then add a description and start a new commit
|
||
$ jj commit -m 'feat(bar): add support for bar'
|
||
# Let Jujutsu generate a bookmark name and push that to GitHub. Note that we
|
||
# push the working-copy commit's *parent* because the working-copy commit
|
||
# itself is empty.
|
||
$ jj git push --change @- # or -c for short
|
||
```
|
||
|
||
### Using a named bookmark
|
||
|
||
In this example, we create a bookmark named `bar` and then push it to the remote.
|
||
|
||
```shell
|
||
# Start a new commit off of the default bookmark.
|
||
$ jj new main
|
||
# Refactor some files, then add a description and start a new commit
|
||
$ jj commit -m 'refactor(foo): restructure foo()'
|
||
# Add a feature, then add a description and start a new commit
|
||
$ jj commit -m 'feat(bar): add support for bar'
|
||
# Create a bookmark so we can push it to GitHub. Note that we created the bookmark
|
||
# on the working-copy commit's *parent* because the working copy itself is empty.
|
||
$ jj bookmark create bar -r @- # `bar` now contains the previous two commits.
|
||
# Set the bookmark to be tracked on the remote.
|
||
$ jj bookmark track bar
|
||
# Push the bookmark to GitHub (pushes only `bar`)
|
||
$ jj git push
|
||
```
|
||
|
||
While it's possible to create a bookmark in advance and commit on top of it in a
|
||
Git-like manner, you will then need to move the bookmark manually when you create
|
||
a new commits. Unlike Git, Jujutsu will not do it automatically.
|
||
|
||
## Updating the repository
|
||
|
||
Jujutsu does not currently have a direct equivalent of `git pull`, though it may
|
||
gain [a similar command someday][sync-issue].
|
||
|
||
In the meantime, updating your branches is a two step process. The first is to
|
||
`jj git fetch` to fetch everything that's happened on your remote. The second is
|
||
to rebase all of your branches on top of your `main` branch with `jj rebase -o
|
||
main`. This command's default is the same as passing `-b @`, so if you have more
|
||
than one outstanding branch, you'll need to call `jj rebase` again with the `-b`
|
||
flag for each of them, or pass multiple `-b` arguments, one for every branch.
|
||
|
||
[sync-issue]: https://github.com/jj-vcs/jj/issues/1039
|
||
|
||
## Working in a Git colocated workspaces
|
||
|
||
After doing `jj git init`, which
|
||
[colocates](git-compatibility.md#colocated-jujutsugit-repos) the `.jj` and
|
||
`.git` directories, Git will be in a [detached HEAD state][detached], which is
|
||
unusual, as Git mainly works with named branches; jj does not.
|
||
|
||
In a colocated workspace, every `jj` command will automatically synchronize
|
||
Jujutsu's view of the repo with Git's view. For example, `jj commit` updates the
|
||
HEAD of the Git repository, enabling an incremental migration.
|
||
|
||
```shell
|
||
$ nvim docs/tutorial.md
|
||
$ # Do some more work.
|
||
$ jj commit -m "Update tutorial"
|
||
# Create a bookmark on the working-copy commit's parent
|
||
$ jj bookmark create doc-update -r @-
|
||
$ jj bookmark track doc-update
|
||
$ jj git push
|
||
```
|
||
|
||
## Working in a Jujutsu repository
|
||
|
||
In a Jujutsu repository, the workflow is simplified. If there's no need for
|
||
explicitly named bookmarks, you can just generate one for a change. As Jujutsu is
|
||
able to create a bookmark for a revision.
|
||
|
||
```shell
|
||
$ # Do your work
|
||
$ jj commit
|
||
$ # Push change "mw", letting Jujutsu automatically create a bookmark called
|
||
$ # "push-mwmpwkwknuz"
|
||
$ jj git push -c mw
|
||
```
|
||
|
||
## Addressing review comments
|
||
|
||
There are two workflows for addressing review comments, depending on your
|
||
project's preference. Many projects prefer that you address comments by adding
|
||
commits to your bookmark[^1]. Some projects (such as Jujutsu and LLVM) instead
|
||
prefer that you keep your commits clean by rewriting them and then
|
||
force-pushing[^2].
|
||
|
||
### Adding new commits
|
||
|
||
If your project prefers that you address review comments by adding commits on
|
||
top, you can do that by doing something like this:
|
||
|
||
```shell
|
||
$ # Create a new commit on top of the `your-feature` bookmark from above.
|
||
$ jj new your-feature
|
||
$ # Address the comments by updating the code. Then review the changes.
|
||
$ jj diff
|
||
$ # Give the fix a description and create a new working-copy on top.
|
||
$ jj commit -m 'address pr comments'
|
||
$ # Update the bookmark to point to the new commit.
|
||
$ jj bookmark move your-feature --to @-
|
||
$ # Push it to your remote
|
||
$ jj git push
|
||
```
|
||
|
||
Notably, the above workflow creates a new commit for you. The same can be
|
||
achieved without creating a new commit.
|
||
|
||
!!! warning
|
||
|
||
We strongly suggest to `jj new` after the example below, as all further edits
|
||
still get amended to the previous commit.
|
||
|
||
```shell
|
||
$ # Create a new commit on top of the `your-feature` bookmark from above.
|
||
$ jj new your-feature
|
||
$ # Address the comments by updating the code. Then review the changes.
|
||
$ jj diff
|
||
$ # Give the fix a description.
|
||
$ jj describe -m 'address pr comments'
|
||
$ # Update the bookmark to point to the current commit.
|
||
$ jj bookmark move your-feature --to @
|
||
$ # Push it to your remote
|
||
$ jj git push
|
||
```
|
||
|
||
### Rewriting commits
|
||
|
||
If your project prefers that you keep commits clean, you can do that by doing
|
||
something like this:
|
||
|
||
```shell
|
||
$ # Create a new commit on top of the second-to-last commit in `your-feature`,
|
||
$ # as reviewers requested a fix there.
|
||
$ jj new your-feature- # NOTE: the trailing hyphen is not a typo!
|
||
$ # Address the comments by updating the code. Then review the changes.
|
||
$ jj diff
|
||
$ # Squash the changes into the parent commit
|
||
$ jj squash
|
||
$ # Push the updated bookmark to the remote. Jujutsu automatically makes it a
|
||
$ # force push
|
||
$ jj git push --bookmark your-feature
|
||
```
|
||
|
||
The hyphen after `your-feature` comes from the [revset](revsets.md) syntax.
|
||
|
||
## Working with other people's bookmarks
|
||
|
||
By default, `jj git clone` imports the default remote bookmark (which is usually
|
||
`main` or `master`), but `jj git fetch` doesn't import new remote bookmarks to
|
||
local bookmarks. This means that if you want to iterate or test another
|
||
contributor's bookmark, you'll need to do `jj new <bookmark>@<remote>` onto it.
|
||
|
||
If you want to import all remote bookmarks including inactive ones, set
|
||
`remotes.<name>.auto-track-bookmarks = "*"` in the config file. Then you can
|
||
specify a contributor's bookmark as `jj new <bookmark>` instead of `jj new
|
||
<bookmark>@<remote>`.
|
||
|
||
You can find more information on that setting [here][auto-bookmark].
|
||
|
||
## Using GitHub CLI
|
||
|
||
GitHub CLI will have trouble finding the proper Git repository path in jj repos
|
||
that aren't [colocated](./git-compatibility.md#colocated-jujutsugit-repos)
|
||
(see [issue #1008]). You can configure the `$GIT_DIR` environment variable to
|
||
point it to the right path:
|
||
|
||
```shell
|
||
$ GIT_DIR=$(jj git root) gh issue list
|
||
```
|
||
|
||
You can make that automatic by installing [direnv](https://direnv.net) and
|
||
defining hooks in a `.envrc` file in the repository root to configure `$GIT_DIR`.
|
||
Just add this line into `.envrc`:
|
||
|
||
```shell
|
||
export GIT_DIR=$(jj git root)
|
||
```
|
||
|
||
and run `direnv allow` to approve it for direnv to run. Then GitHub CLI will
|
||
work automatically even in workspaces that aren't colocated so you can execute
|
||
commands like `gh issue list` normally.
|
||
|
||
[issue #1008]: https://github.com/jj-vcs/jj/issues/1008
|
||
|
||
## Useful Revsets
|
||
|
||
Log all revisions across all local bookmarks that aren't on the main bookmark nor
|
||
on any remote:
|
||
|
||
```shell
|
||
$ jj log -r 'bookmarks() & ~(main | remote_bookmarks())'
|
||
```
|
||
|
||
Log all revisions that you authored, across all bookmarks that aren't on any
|
||
remote:
|
||
|
||
```shell
|
||
$ jj log -r 'mine() & bookmarks() & ~remote_bookmarks()'
|
||
```
|
||
|
||
Log all remote bookmarks that you authored or committed to:
|
||
|
||
```shell
|
||
$ jj log -r 'remote_bookmarks() & (mine() | committer(your@email.com))'
|
||
```
|
||
|
||
Log all ancestors of the current working copy that aren't on any remote:
|
||
|
||
```shell
|
||
$ jj log -r 'remote_bookmarks()..@'
|
||
```
|
||
|
||
## Merge conflicts
|
||
|
||
For a detailed overview, how Jujutsu handles conflicts, revisit
|
||
the [tutorial][tut].
|
||
|
||
[^1]:
|
||
This is a GitHub-style review, as GitHub currently only is able to compare
|
||
bookmarks.
|
||
|
||
[^2]:
|
||
If you're wondering why we prefer clean commits in this project, see
|
||
e.g. [this blog post][stacked]
|
||
|
||
[auto-bookmark]: config.md#automatic-tracking-of-bookmarks
|
||
[detached]: https://git-scm.com/docs/git-checkout#_detached_head
|
||
[tut]: tutorial.md#conflicts
|
||
[stacked]: https://jg.gg/2018/09/29/stacked-diffs-versus-pull-requests/
|
||
|
||
## Using several remotes
|
||
|
||
It is common to use several remotes when contributing to a shared repository.
|
||
For example, "upstream" can designate the remote where the changes will be
|
||
merged through a pull-request while "origin" is your private fork of the
|
||
project.
|
||
|
||
```shell
|
||
$ jj git clone --remote upstream https://github.com/upstream-org/repo
|
||
$ cd repo
|
||
$ jj git remote add origin git@github.com:your-org/your-repo-fork
|
||
```
|
||
|
||
This will automatically setup your repository to track the main
|
||
bookmark from the upstream repository, typically `main@upstream`
|
||
or `master@upstream`.
|
||
|
||
You might want to `jj git fetch` from "upstream" and to `jj git push`
|
||
to "origin". You can configure the default remotes to fetch from and
|
||
push to in your configuration file
|
||
(`jj config edit --user|repo|workspace`):
|
||
|
||
```toml
|
||
[git]
|
||
fetch = "upstream"
|
||
push = "origin"
|
||
```
|
||
|
||
The default for both `git.fetch` and `git.push` is "origin".
|
||
|
||
If you usually work on a project from several computers, you may
|
||
configure `jj` to fetch from both repositories by default, in order to
|
||
keep your own bookmarks synchronized through your `origin` repository:
|
||
|
||
```toml
|
||
[git]
|
||
fetch = ["upstream", "origin"]
|
||
push = "origin"
|
||
```
|
||
|
||
## Git push options
|
||
|
||
`jj git push` supports passing Git “push options” to the server via
|
||
`-o`/`--option`. These are forwarded to the remote and interpreted by the hosting
|
||
platform, if it supports them. You can repeat `-o` to send multiple options.
|
||
|
||
- Syntax: `jj git push -o <push_option>` or `jj git push --option <push_option>`
|
||
- Multiple options: `jj git push -o foo -o bar=val`
|
||
- Quoting: If the option’s value has spaces, wrap it in double quotes.
|
||
|
||
Support is server-dependent. GitLab supports push options for CI and merge
|
||
requests; other platforms may not. Refer to your host’s documentation for the
|
||
full list and behavior.
|
||
|
||
Examples with GitLab push options (see GitLab docs for details):
|
||
|
||
- Skip CI for a push:
|
||
```shell
|
||
jj git push -o ci.skip
|
||
```
|
||
- Pass CI variables to the created pipeline:
|
||
```shell
|
||
jj git push -o 'ci.variable=MAX_RETRIES=10' -o 'ci.variable=MAX_TIME=600'
|
||
```
|
||
- Create a merge request with metadata on push:
|
||
```shell
|
||
jj git push \
|
||
-o merge_request.create \
|
||
-o merge_request.target=main \
|
||
-o 'merge_request.title=Add feature X' \
|
||
-o 'merge_request.description=Implements X with tests' \
|
||
-o merge_request.draft
|
||
```
|
||
- Auto-merge when pipeline succeeds and remove the source branch:
|
||
```shell
|
||
jj git push \
|
||
-o merge_request.merge_when_pipeline_succeeds \
|
||
-o merge_request.remove_source_branch
|
||
```
|
||
- Add and remove labels:
|
||
```shell
|
||
jj git push \
|
||
-o 'merge_request.label=label1' \
|
||
-o 'merge_request.label=label2' \
|
||
-o 'merge_request.unlabel=label3'
|
||
```
|
||
- Assign and unassign users:
|
||
```shell
|
||
jj git push \
|
||
-o 'merge_request.assign=user1' \
|
||
-o 'merge_request.assign=user2' \
|
||
-o 'merge_request.unassign=user3'
|
||
```
|