One of the pains with gerrit is that of rebasing. Once a change has gone through its review process, it is common to find that the tree has moved on since it was written, and the change requires updating before it can be applied. On many occasions, all that is required is a trivial rebase – just moving the patch to the latest version of the tree, without actually changing any of the code. However, whenever a new patch is uploaded, gerrit forgets the existing review and verification scores. In many cases, this is a good thing – you don’t want your “This looks great, submit it!” review attached to a piece of code that is substantially different from what you reviewed. With a trivial rebase, things are different – the code hasn’t changed, it probably doesn’t need re-reviewing, and dropping the existing review and verification information just means that information is lost when the commit is eventually applied.
So, Nasser Grainawi wrote a trivial rebase hook, which uses gerrit’s hook and command line features to identify these trivial rebases and reapply all of the review scores when it identifies one. I’ve enabled this hook for OpenAFS’s gerrit instance. It turns out that doing so isn’t quite as simple as it might appear, so I thought I’d document the hoops I jumped through.
1: Create an SSH key
The script will run as the gerrit user, so we need to create a ssh public key pair for it to use as that user. Just run ssh-keygen as normal, and put the key pair somewhere sensible.
2: Create a User
A role user needs to be created for the script to use when interrogating gerrit. You need this, despite the fact that the script also uses the super-user functionality obtained from the server’s private key when reapplying existing scores. The development version of gerrit has a nice simple command to create new role accounts, but we’re runnign the stable series, which hasn’t yet got this feature. So, we get to play directly with the SQL database.
insert into accounts (registered_on, account_id, full_name, preferred_email) VALUES (now(), nextval('account_id'), 'Automated Hook Processor', '<email>');
insert into account_ssh_keys (valid, account_id, seq, ssh_public_key) VALUES ('Y', currval('account_id'), 1, '<ssh public key>');
insert into account_external_ids (account_id, external_id) VALUES (currval('account_id'), 'username:hooks');
… substituting <email> with an email address that the hook processor can be attributed with, and <ssh public key> with the public portion of the key we generated earlier.
3: Configure the SSH client
Let the ssh client know how to talk to gerrit. The script uses ‘localhost’ as the hostname to talk to, so we need to do something like:
Host localhost IdentityFile ~/.ssh/id_rsa User hooks
4: Connect to Gerrit
In order to populate the known hosts file, we need to connect to gerrit, and accept the host key. Something like
ssh -p 29418 localhost
will accomplish this.
5: Make the new user an admistrator
Go into the gerrit front end, and make the user that we’ve just created an administrator
6: Test the rebase script
Testing the rebase script independently from gerrit will let you know if everything above has worked. You can run it from the command line, as the gerrit user:
GIT_DIR=<gitRepository> \ ./trivial_rebase.py --change <changeId> \ --project <projectName> \ --commit <commitSHA1> \ --patchset <patchSetNumber> \ --private-key-path=$site_path/etc/ssh_host_dsa_key
- gitRepository is the path to the bare git repository that gerrit saves its changes into.
- changeId is the full changeID (something like I97e07a6e730df8ac480d295b4cf30b0695ace511)
- commitSHA1 is the git SHA1 for the patchset that’s just bene uploaded
- patchset is gerrit’s patchset number
The best way to test this is to pick a change that has been trivially rebased, but which isn’t marked as such. Go to its page in the web interface, and get the changeID, the SHA1 of the newest patchest, and its number, from that page, and plug them in to this command. All being well, the change will be marked as a trivial rebase. If that doesn’t happen, then the logs in $site_path/logs/error_log will give an idea of what’s going on.
7: Write a wrapper
The trivial rebase script can’t be run directly from a gerrit hook, so we need to provide a wrapper script to fill in some of the gaps. The script OpenAFS is currently using is:
#!/usr/bin/perl use strict; use warnings; use Getopt::Long; my $gerritRoot="/srv/gerrit/cfg"; my $hookDir="/srv/gerrit/gerrit-hooks"; my $change; my $project; my $branch; my $commit; my $patchset; my $result = GetOptions("change=s" => \$change, "project=s" => \$project, "branch=s" => \$branch, "commit=s" => \$commit, "patchset=s" => \$patchset); system($hookDir."/trivial_rebase.py", "--change", $change, "--project", $project, "--commit", $commit, "--patchset", $patchset, "--private-key-path", $gerritRoot."/etc/ssh_host_dsa_key");
This script needs to go into the hooks directory below your gerrit site path as a file called patchset-created. In order for gerrit to run the hook, it needs to be executable.