# 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 @` onto it. If you want to import all remote bookmarks including inactive ones, set `remotes..auto-track-bookmarks = "*"` in the config file. Then you can specify a contributor's bookmark as `jj new ` instead of `jj new @`. 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 ` or `jj git 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' ```