68

I would like to walk through a range of commits and perform a shell command on each. If the command fails, I would like the walk to stop, otherwise keep going. I have looked at filter-branch, but I don't want to re-write the commits, just check them out. for-each-ref does not seem to allow you to specify a range to act on.

My specific problem is that I created a bunch of commits and I would like to ensure that each commit is buildable. I would like to do something like:

git foreach origin/master..master 'git submodule update && make clean && make'

I could of course write a shell script to do this, but it seems like the sort of thing that git might have a nice way to do.

FazJaxton
  • 7,544
  • 6
  • 26
  • 32
  • Have you considered using Python? Maybe GitPython could be a solution. Check this post: http://stackoverflow.com/questions/1456269/python-git-module-experiences –  Nov 17 '14 at 23:19
  • Do you know that origin/master is good and master is bad (or the inverse), or are you just trying to test everything between them without knowing that there exists a failure at any specific place? – Charles Duffy Nov 18 '14 at 02:47
  • @CharlesDuffy, the latter. I started from a working point and made a bunch of changes that worked. Then I made several fine-grained commits from the pile of uncommitted working changes, and want to make sure that all of the in-between commits are buildable. – FazJaxton Nov 23 '14 at 15:54

3 Answers3

81

You can use interactive rebase with an exec option.

git rebase -i --exec <build command> <first sha you want to test>~

--exec Append "exec " after each line creating a commit in the final history. will be interpreted as one or more shell commands.

Reordering and editing commits usually creates untested intermediate steps. You may want to check that your history editing did not break anything by running a test, or at least recompiling at intermediate points in history by using the "exec" command (shortcut "x").

The interactive rebase will stop when a command fails (i.e. exits with non-0 status) to give you an opportunity to fix the problem.

Community
  • 1
  • 1
Andrew C
  • 13,845
  • 6
  • 50
  • 57
  • 1
    The version of git I am stuck with 1.7.10 doesn't have the --exec command line option so instead I manually inserted "x make" after each pick command and it worked a treat. Thanks. – Bowie Owens Sep 17 '15 at 05:02
  • 1
    You may need additionally the option `-p`&`--preserve-merges`, if the range of the rebase includes merges. – Unapiedra Mar 28 '17 at 08:29
  • 2
    It's like HEAD~1, it says to go back one further so you actually pick up the SHA you want to test. Standard git revision specification – Andrew C Aug 12 '17 at 04:21
  • 1
    As it is doing rebase SHA will change, so nice is to assign a `temp` branch to keep the old commits. Rebase on top of it we can get the addition to the old ones. – Vladimir Vukanac Feb 15 '19 at 13:38
  • Not exactly true. Just replaying the same commits preserves the original SHAs. If you do anything to the commits (modification, removal, reordering) then the subsequent SHAs will change. – Andrew C Aug 14 '22 at 21:57
31

You probably want rev-list.

#!/usr/bin/env bash
# test_commits.sh

while read -r rev; do
    git checkout "$rev"
    if ! git submodule update && make clean && make; then
        >&2 echo "Commit $rev failed"
        exit 1
    fi
done < <(git rev-list "$1")

Then you can use it with

./test_commits.sh origin/master..master
Justin Howard
  • 5,504
  • 1
  • 21
  • 48
  • 4
    Needs more quotes to avoid string-splitting and glob expansion. `"$1"`, `"$rev"`, etc. Collecting all the outputs from `git rev-list` before string-splitting and iterating them also means more up-front delay than would `while read -r rev; do ...; done < <(git rev-list "$1")`, which streams in the contents as they're read rather than collecting the entire stream and parsing it up-front. (This approach is also more memory-efficient with very large revision lists, for obvious reasons). – Charles Duffy Nov 18 '14 at 00:19
  • 2
    (That said, while there's room for improvement around shell best-practices, this is absolutely the best answer here, and has my +1). – Charles Duffy Nov 18 '14 at 00:23
  • @CharlesDuffy - I never knew about that way of streaming in contents of a list as they are read - thanks! – Chris Sherlock Apr 30 '16 at 06:50
9

Here's a cool one-liner using xargs.

git rev-list @{upstream}..HEAD | xargs -n1 -I{} sh -c 'git checkout {} && git submodule update && make clean && make'

You may also want to pass the --reverse option to rev-list so that the last checkout is HEAD.

Cody Allan Taylor
  • 3,062
  • 1
  • 13
  • 11
  • 1
    Depending on how far you want to go back it would be good to make a backup of `.git/logs/HEAD` before that if you don't want to have a trashed up reflog. – CDanU Nov 16 '17 at 21:34