My first git rebase --exec

, .

Today at work, I was working on two long-ish chains of patches. They’re split up into several commits, but those commits were frequently moved around and reorganized while I was working on them. I usually only run the tests on the last commit in a chain, and rely on CI to tell me if any of the intermediate commits is bad (for instance, one commit might rely on something added in a later commit). However, this time, I wanted to try running the tests locally, and catch any errors before pushing to Gerrit.

Of course, I could have done that by manually checking out each commit in the chain. Or I could have done a git rebase --interactive, replaced each pick with edit, and ran the tests each time Git dropped me back into the shell before running git rebase --continue. However, I decided to try out an option for git rebase which I had found in the manual a while ago: --exec. Its use is fairly simple:

git rebase --exec '
    # this doesn’t work, please see the correction post!
    cd ../.. &&
    php tests/phpunit/phpunit.php \
        extensions/WikibaseQualityConstraints/tests/phpunit/
' master

The argument to --exec is a shell command (/bin/sh – warning: not necessarily Bash), which is executed after each commit. If the command exits nonzero, the rebase pauses and returns to your shell, where you can --continue, --abort etc. it like in a normal interactive rebase. In my case, the tests failed on one commit (I had written two test cases which depended on a later commit), so I added two lines to temporarily skip those tests, amended the commit to add those two lines (and briefly mention them in the commit message), edited the rebase’s to-do list (git rebase --edit-todo) to edit the later commit, and --continued the rebase. When the later commit was reached, I amended it to remove the skipping lines again and then finished the rebase.

And that’s the story of my first git rebase --exec… but I said I was working on two chains, and as it happens, I found another use for git rebase --exec in the second chain. This time, I was less worried about test failures between the commits (they were more clearly separated – several “prepare for change” commits and then one “do the change” commit), but more worried about our automatic code style sniffer. It has a companion program to automatically fix most code style violations, but on a chain of commits it can still be annoying to find the right commit which needs to be fixed. So I used the following command:

git rebase --exec '
    # this doesn’t work, please see the correction post!
    composer fix
    GIT_PAGER=cat git diff
    if ! git diff-index --quiet HEAD --; then
        read -p "ok? " -r ok
        if [ "$ok" = ok ]; then
            git commit --all --amend --no-edit
        fi
    fi
' master

This is slightly more involved than the first command above (the diff-index part is copied from this StackOverflow answer, and I’ll admit that the command wasn’t as nicely formatted when I hacked it together in my shell), but what it does is fairly simple – run composer fix after each commit (fix code style violations), show me the diff, and if the diff is non-empty, commit the changes (after a confirmation prompt, just in case something went wrong). This took a while to run in the background (the fixer is annoyingly slow), but apart from that it worked flawlessly. After that, the chain was ready to push.

And that’s the story of my first two git rebase --execs! I like this option – I think I’ll find more uses for it in the future. And perhaps you’ll find it useful as well!

Oh, and if you actually want to see all these commits I’m talking about (not that they’re very interesting) – the first chain starts with change I95e2235eef, and the second one with change I64dc0a3200. One of the many nice things about working on free software is being able to post those links without any problems ☺