-
Notifications
You must be signed in to change notification settings - Fork 427
Gerrit
Many developers at the company use a tool called Gerrit (https://review.dev.storj.io/) to submit code and get reviews. There are some key differences between using Github PRs and Gerrit changesets. This document is intended to highlight some of these differences and debug common issues that may arise. It is also a quick source for getting set up with Gerrit for the first time.
The core difference between code reviews on Gerrit and Github is that Gerrit is "commit-based" while Github is "branch-based". Here is a short example of what that looks like in practice:
You are making a bugfix. You checkout the main branch of storj/storj on your local machine. You run git switch -c my-bug-fix to switch to a new branch called my-bug-fix.
You change the necessary code and add and commit the file:
git add file.go
git commit -m "fix bug in file.go"
Now, if you run git diff, you will see the following:
- fix bug in file.go
- the latest commit from your local
mainbranch - ...
You push to Github, with git push origin my-bug-fix, then open a PR and ask for reviews.
Someone reviews your change and asks you to fix a typo. You do so, and run
git add file.go
git commit -m "fix typo"
git push origin my-bug-fix
Now, if you look at your commit history with git diff, it will look like this:
- fix typo
- fix bug in file.go
- the latest commit from your local
mainbranch - ...
Two people approved your PR in Github, and build passed, but some new changes have been committed to the main branch since you originally created the PR, so you need to merge the main branch and update the PR.
git pull origin main
git push origin my-bug-fix
Now your commit history looks like this:
- Merge branch 'main' of github.com:storj/storj into my-bug-fix
- fix typo
- fix bug in file.go
- the latest commit from your local
mainbranch - ...
Build passes, and with your two approvals, you are now able to "squash and merge" into main. You do so, and make sure to update the commit message before merging to "file.go: fixed a bug". See the Git page for information about how your commit messages should look.
Now, the commit history of the main branch looks like this:
- file.go: fixed a bug
- the previous commit on the
mainbranch - ...
You are making a bugfix. You checkout the main branch of storj/storj on your local machine. You run git switch -c my-bug-fix to switch to a new branch called my-bug-fix.
You change the necessary code and add and commit the file:
git add file.go
git commit -m "file.go: fixed a bug"
Now, if you run git diff, you will see the following:
- file.go: fixed a bug
- the latest commit from your local
mainbranch - ...
You push to Gerrit, with git push gerrit HEAD:refs/for/main, then you ask for reviews.
Someone reviews your change and asks you to fix a typo. You do so, and run
git add file.go
git commit --amend --no-edit
git push gerrit HEAD:refs/for/main
Now, if you look at your commit history with git diff, it will look like this:
- file.go: fixed a bug
- the latest commit from your local
mainbranch - ...
Two people approved your PR in Github, and build passed, but some new changes have been committed to the main branch since you originally created the PR, so you need to merge the main branch and update the PR.
git pull --rebase origin main
git push gerrit HEAD:refs/for/main
Now your commit history looks like this:
- file.go: fixed a bug
- the latest commit from the remote
mainbranch - ...
Build passes, and with your two approvals, submit to main. You do so
Now, the commit history of the main branch looks like this:
- file.go: fixed a bug
- the previous commit on the
mainbranch - ...
Gerrit essentially requires that the commit you are submitting for review is exactly the commit being submitted to the main branch, including the commit message and parent commit. By contrast, Github only requires that the files changed are approved. This means that any change that is submitted to the main branch via Gerrit must have a commit history that looks exactly like:
- The commit for the change
- The latest commit in the
mainbranch
Which is conveniently exactly what the mainbranch will look like after the new change is submitted.
One of the things this gives us is the ability to review commit messages in Gerrit, which is important since we have some things to keep in mind regarding commit messages, and it is easy to make a mistake when merging a Github pull request.
Another advantage of this is it means that you can track multiple reviews on the same branch using Gerrit, which is not possible in Github.
In Github, if you want to get concurrent reviews on two changes, A and B, where B is dependent on A, then you will need to make two branches:
branch-a
- A commits
- main branch commits
branch-b
- B commits
- A commits
- main branch commits
meaning that a pull request to merge branch-b into main will also contain the changes from branch-a, making the review more complicated.
In Gerrit, you can have one branch with two commits:
- single commit for B
- single commit for A
- main branch commits
Now, when you push to Gerrit, a separate review will be created for A and B, and each review only contains the changes for a single commit. However, the B commit will still not be submittable until the A commit is submittable (or has already been submitted).
One of the most common issues people have with Gerrit is getting used to an unfamiliar git workflow. Once you get some practice with it, you will be able to decide whether or not you prefer it. This git workflow requires you to be very mindful of your git history and organize your code into sensible commits, which is at the same time its main advantage and disadvantage.
If you get an error submitting a change to Gerrit, the first thing you should check is that you have pulled the latest commits from the main branch:
git pull --rebase origin main
git push gerrit HEAD:refs/for/main
You may also see an error if you are pushing a change that is identical to one that has already been pushed to Gerrit. If you know this, and want to push anyway (e.g. go back to a previous version of the change), then simply run the following to update the commit's timestamp:
git commit --amend --no-edit
git push gerrit HEAD:refs/for/main
Sometimes, when you do git pull --rebase origin main, you will see a message about conflicting files that you will need to resolve. Getting specific about how to fix conflicts in git is out of the scope of this document, but one important thing to keep in mind is that pulling main with --rebase basically temporarily removes your commits, puts all the new commits from the main branch in your git history, then puts your commits back on top. This way, your commits always remain as the latest commits in the git history.
What this means is that the conflicts you see will look reversed from what you may be used to. If you are used to doing git pull origin main, your conflicts would look like this:
<<<<<<< HEAD
conflicting changes from main
=======
conflicting changes from your-local-branch
>>>>>>>
But with --rebase, the same conflict looks like:
<<<<<<< HEAD
conflicting changes from your-local-branch
=======
conflicting changes from main
>>>>>>>
Let's say you are working on a big change that requires three separate commits: A, B, and C. Your local branch might look like this:
- C commit
- B commit
- A commit
- latest commit on
main - ...
Now, someone reviews your B change, and asks you to update it. How do you do this? git commit --amend will update the latest commit, "C commit", but you need to keep your changes for B and C separate.
There are many ways to approach this problem, but one way is to do an interactive git rebase. This will look like:
git rebase -i HEAD~2
This will open up an editor - change the beginning of the "B commit" line from "pick" to "edit", then save and quit.
The rebase will pause when the commit history looks like:
- B commit
- A commit
- latest commit on
main - ...
Then, edit the necessary files, git add path/to/files, and do git commit --amend to update "B commit". Then,
git rebase --continue
Now, your commit history looks the same as before, and you can push to Gerrit, and the correct reviews will all be updated.
In this example, we are setting up Gerrit the main repository, https://github.com/storj/storj/. The steps should be almost identical for other repositories.
Before requesting Gerrit access in Slack, you will need to log in at https://review.dev.storj.io/ with your Github account. Once you have done this, share your Github email and username, and ask for review and submit permissions in the #gerrit Slack channel. When you have permissions, you can verify that you have submit access by going to any open changeset, and checking for a "CODE-REVIEW+2" button in the top right. If you don't see this, you still need to be added to the Reviewers group.
Next, set up your SSH key in Gerrit by going to your settings: https://review.dev.storj.io/settings/#SSHKeys. If you have already set up SSH with Github, this should be easy. Otherwise, see Github's docs for
- checking for existing ssh keys
- generating a new ssh key (if no existing ones)
- adding an ssh key to your account - the only difference here is that you are adding the ssh key in your Gerrit settings rather than your Github settings.
Run
curl -sSL storj.io/clone | sh
from inside your local storj/storj repository.
Run the following command from your local storj/storj repository in order to add a hook which executes whenever a commit message is written:
scp -p -P 29418 <yourusername>@review.dev.storj.io:hooks/commit-msg ".git/hooks/"
This will add a line that looks like Change-Id: I989b8a46c0e97c1278517e1de07fe42d5950cf54 to the end of each commit message you create, allowing Gerrit to track a single changeset, even as the commit hash, message, and files change.
Now, add the gerrit remote path, allowing you to push to gerrit:
git remote add gerrit ssh://<yourusername>@review.dev.storj.io:29418/storj/storj
You should be all set now! When you have a commit to push for review, use one of the following commands (remember to git pull --rebase origin main, or the change may not be pushed successfully:
git push gerrit HEAD:refs/for/main-
git push gerrit HEAD:refs/for/main%wip- if you do this, the changeset will be created in gerrit, but it will not trigger a build until you take it out of "wip" -
git push gerrit HEAD:refs/for/main%ready- this will unmark a change as "wip" and begin the build and review -
git push gerrit HEAD:refs/for/main%private- this will mark the change as "private", and only the reviewers of the change can view it. ALWAYS use this to submit security patches of private security bugs -
git push gerrit HEAD:refs/for/main%remove-private- this will unmark a change as "private"
You can use the same command to update an existing changeset. As long as the Change-Id in the commit message is the same, the corresponding review in Gerrit will be updated.