Gerrit & Jujutsu

Table of Contents

NOTE: This guide was taken verbatim from a document I wrote internally, the core loop is the same but you can ignore the handholding about how to sign-in to gerrit.

Gerrit

This guide assumes you know nothing about version control or Gerrit. It does not explain how anything works. Follow the steps exactly and you will be fine.

One rule to internalise before anything else: in Gerrit, one change = one code review. Everything below follows from that.

The recommended tool is jj (Jujutsu). A legacy git workflow is included at the end for those who prefer it.


Operation 0: Set up access and clone

You do this once per machine.

0.1 Sign in to Gerrit

Open and click Sign in in the top right corner. This takes you through our identity provider ([REDACTED]); use your normal [REDACTED] credentials.

You must actually click Sign in. If you skip this, Gerrit loads fine but shows no repositories and no reviews, and you will think something is broken. It isn't. You're just not signed in.

0.2 Check your Gerrit username

Avatar (top right) → SettingsProfile. Note your Username; you need it for SSH. If it's empty, set one now (it cannot be changed later).

0.3 Add your SSH key

We push over SSH.

If you don't already have an SSH key, generate one:

ssh-keygen -t ed25519 -C "you@[redacted]].com"

(Accept the defaults. A passphrase is recommended.)

Copy the contents of the public key, ~/.ssh/id_ed25519.pub, then in Gerrit: Settings → SSH Keys, paste it into the text box and click Add new SSH key.

Verify it works (replace USERNAME with the username from 0.2):

ssh -p 29418 USERNAME@<your-gerrit-url>

You should see a welcome banner saying you've successfully connected, then be disconnected. That's success.

0.4 Install jj and set your identity

Install jj: https://docs.jj-vcs.dev/latest/install-and-setup/ (Homebrew, winget, cargo, or a prebuilt binary; whatever suits your machine).

Then:

jj config set --user user.name "Firstname Lastname"
jj config set --user user.email "you@[REDACTED]].com"

Use the same email that Gerrit shows in your profile.

0.5 Clone the repository

Replace USERNAME and PROJECT (the project name is shown under Browse → Repositories in Gerrit):

jj git clone ssh://USERNAME@<your-gerrit-url>:29418/PROJECT
cd PROJECT

Common projects are:

  • salt - terraform/saltstack infrastructure-as-code owned by [REDACTED]
  • ops - a monorepo of loose utilities including lunch, data exports and so on.

Then set the Gerrit defaults once per repo:

jj config set --repo gerrit.default-remote origin
jj config set --repo gerrit.default-remote-branch main

Done. No commit-msg hook is needed with jj; it handles the Change-Id for you.


Operation 1: Submit a new, self-contained change

Use this every time you start a new piece of work. The key habit: always start a new change on top of upstream main, never on top of your previous work. That is what keeps reviews unrelated in Gerrit.

jj git fetch
jj new main@origin

Now edit files as normal. There is no staging and no commit step; your edits are automatically part of the current change.

When you're done, give it a message and upload:

jj describe -m "Short summary of what this does"
jj gerrit upload

The output contains a URL. That is your review. Open it and add reviewers.

Next unrelated piece of work? Run jj git fetch && jj new main@origin again. Fresh start on top of main, unrelated review.

If instead you run jj new on top of your previous change, Gerrit will see the second review as stacked on the first, and it cannot merge until the first one does.


Operation 2: Respond to review feedback (upload patch set 2)

The reviewer asked for changes. You do not create a new change. You edit the existing one; jj keeps the same Change-Id, so Gerrit adds a new patch set to the same review.

First, find your change and get back onto it:

jj log

Look for your change in the list; it has a short change ID (letters, e.g. qsxwmopn) at the start of the line. If you're not already on it (@ marks where you are):

jj edit qsxwmopn

Now edit files as normal, then upload again:

jj gerrit upload

That's it. Gerrit shows Patchset 2 on the existing review. Repeat as many times as the reviewer demands.

If you also want to reword the description: jj describe -m "New summary" before uploading.


Operation 3: Get back to a clean, consistent state

Use this when things feel broken, you want to park local mess, or you just want to be on whatever is currently in main:

jj git fetch
jj new main@origin

You are now on a fresh, empty change on top of the latest upstream main. Nothing is lost: anything half-finished stays behind as its own change and is visible in jj log. To permanently discard a change you no longer want:

jj abandon qsxwmopn

And if you ever genuinely mangle something, jj undo reverses the last jj operation. jj keeps a full operation log (jj op log), so almost nothing is unrecoverable.


One caveat on jj

jj gerrit upload is officially experimental. Uploading works well and is what we use; fetching or submitting changes from Gerrit via jj is not implemented, so approving and submitting happens in the web UI (as it should anyway). Keep jj reasonably up to date.

There are other approve tools (like gerrit for visual studio code) or the barely maintained gri these are fine too.



Appendix: Legacy git workflow

For people who prefer plain git. Same four operations, more ceremony.

Operation 0 (git): Setup and clone

Complete steps 0.1–0.3 above (sign in, username, SSH key), then:

git config --global user.name "Firstname Lastname"
git config --global user.email "you@[REDACTED].com"

Clone over SSH (replace USERNAME and PROJECT):

git clone ssh://USERNAME@<your-gerrit-url>:29418/PROJECT
cd PROJECT

Then install the commit-msg hook. Unlike jj, git needs this; it adds a Change-Id line to every commit message, and Gerrit refuses commits without one:

mkdir -p .git/hooks
scp -p -P 29418 USERNAME@<your-gerrit-url>:hooks/commit-msg .git/hooks/
chmod +x .git/hooks/commit-msg

(Alternatively, the exact clone-plus-hook command is shown in the Gerrit UI: project page → DownloadClone with commit-msg hookSSH tab.)

Operation 1 (git): New self-contained change

Always start from a fresh copy of main:

git fetch origin
git checkout -b my-fix origin/main

(my-fix is a throwaway local name; call it anything descriptive.)

Edit files, then commit everything as one commit:

git add -A
git commit

Write a short summary on the first line, save, close. Then send it for review:

git push origin HEAD:refs/for/main

The output contains the review URL.

Do not commit twice on the same branch. Two commits become two stacked, related reviews. New piece of work → new branch from origin/main.

Operation 2 (git): Respond to review feedback

Do not make a new commit. Amend the existing one; the Change-Id in the message ties it to the same review.

git checkout my-fix
# ... edit files ...
git add -A
git commit --amend --no-edit
git push origin HEAD:refs/for/main

Gerrit adds Patchset 2 to the existing review. (--no-edit keeps the message; drop it to reword, but never delete the Change-Id: line.)

If you've lost the branch: open the review in Gerrit, click Download, copy the Checkout command (SSH tab), run it, then git checkout -b my-fix.

Operation 3 (git): Back to a clean state

git checkout main
git fetch origin
git reset --hard origin/main

You are now exactly at the latest main; local edits on main are gone (that's the point). If untracked files are also in the way:

git clean -nd   # preview what would be deleted
git clean -fd   # actually delete: permanent, be sure

Review branches (my-fix etc.) survive this. Once a change is merged, tidy up with git branch -D my-fix.

Comments