4

Suppose I suddenly see something is failing on my Git repo. But I know that it was working a few commits ago, I just don't remember which commit. Instead of trying to find a "good" commit to do a git bisect, I would like to ask Git (perhaps with a bash script), what is the most recent "good" commit?

Another reason not to just choose a commit from a while ago and use it as good is because in my repo there are many bad and good commits mixed together. It's not linear.

So how can I loop, starting with the most recent commit and going as long as necessary, executing a command that returns 1 if bad 0 if good?

Xu Wang
  • 10,199
  • 6
  • 44
  • 78
  • The only way I can see you getting git to tell you that the code was good is to keep pulling it down and running the unit tests. Ideally, don't commit until you run your tests, to prevent this type of problem. Otherwise, how does git know if the code is good or bad? – James Black May 25 '14 at 13:03
  • @JamesBlack and don't invent any new tests that prove old commits bad. – Kaz May 25 '14 at 13:07
  • 3
    What you have indicated is a linear search. How will this ever be better than bisect. You'd be better off picking off progressively larger groups of commits, using git bisect, and doing the same thing. – nsfyn55 May 25 '14 at 13:08
  • @nsfyn55 It will be far better than git bisect if you already know that the breakage is within the last handful of commits. – Kaz May 25 '14 at 13:11
  • @Kaz Firstly, even if what @xuwang indicates is true and his repo is littered with good and bad commits. There must be some distinguishing characteristic(a unique compilation error, a widget the doesn't respond, etc.) Secondly, if you don't know where the breakage occurred how do you know its in the last handful of commits? It might be then he could just sit there resetting `HEAD~1` till it works, but it might not be. Thats why you use `git bisect` because it yields the best worst case. – nsfyn55 May 25 '14 at 13:56
  • @JamesBlack I must have described poorly. I have a script that tells whether good or bad. Just like what you give to git bisect. – Xu Wang May 25 '14 at 14:00
  • @nsfyn55 I was recently asked to break up a big commit into smaller ones, which ended up being five. If the big commit broke something, then I would know in that case that it's one of the five. – Kaz May 25 '14 at 14:00
  • @nsfyn55 very good point. My term was bad. Yes it is linear. But it is a specific type of linear. – Xu Wang May 25 '14 at 14:00
  • @XuWant You didn't describe anything poorly. – Kaz May 25 '14 at 14:01
  • @nsfyn55 Regarding "Firstly", yes ther eis a distinguishing characteristic. I don't see what that means though. I only discovered this distinguishing characteristic now, not before (that's why there is a big mix of good and bad). Regarding "secondly", it is a memory issue. I am getting old :). I know I remember yesterday but not at what commit yesterday. – Xu Wang May 25 '14 at 14:03
  • @Kaz Ok you got me **ever** was a bad choice of word. Maybe I should have used likely. "This isn't likely to be better than `git bisect`" – nsfyn55 May 25 '14 at 14:12
  • @XuWang I mean if thats truly what you want to do then I guess @Kaz's suggestion will work. If I knew the breakage was in the last 3 commits I would just type `git reset --hard HEAD~1` in the command line till I isolated the busted commit. – nsfyn55 May 25 '14 at 14:19
  • @nsfyn55 That's silly because `reset --hard` is rewriting your branch's HEAD to the specified commit, so then you have to recover it from somewhere (reflog, or a tag or whatever). This is why I recommended `checkout`. You end up in a detached state, after which you can just `git checkout master` (or your branch name). Analogous to `git bisect reset`. – Kaz May 25 '14 at 14:32
  • 1
    @nsfyn55 Note that git bisect requires a last good commit. I've often run into situations where I didn't find one: the newly discovered breakage had in fact always been broken. git bisect is not helpful in this regard; you have to mark your broken HEAD `git bisect bad` and then manually `git checkout` and test various historic revisions in order to find commit which you can mark `git bisect good`, after which the bisection process can begin. `git bisect` will not work well if in the history, the particular test broke and worked multiple times, and your `bisect good` is too far back. – Kaz May 25 '14 at 14:36
  • I agree that `git reset --hard` would not be a good idea. reflog is mighty useful, but I do not think one should plan to rely on it. – Xu Wang May 25 '14 at 15:52
  • 1
    Before doing anything "dicey" with a git history, I save a tag: `git tag get-me-out-of-here`. In the worst case, I can `reset --hard get-me-out-of-here` to put things the way they were. – Kaz May 25 '14 at 16:29

1 Answers1

7
while ! good-or-bad-test-command ; do
   git checkout HEAD^
done

However, if the breakage is within just the last few commits somewhere, you can do this manually.

$ good-or-bad-test-command
# if it fails, then:
$ git checkout HEAD^ # pop to previous
$ good-or-bad-test-command # <-- recalled by hitting up arrow in bash
# still fails:
$ git checkout HEAD^ # <-- recalled with up arrow
$ good-or-bad-test-command # <-- recalled
...

thanks to history recall, it will take fewer keystrokes than banging out a loop.

git checkout avoids moving your branch HEAD, putting you in a "detached state" from which you can easily recover with git checkout <yourbranch>.

[Edit, March 2017]

But, the question is, why would you still use git bisect in this situation? You've linearly searched through the bad commits back to the good one; there is no need to do another binary search for the same info.

It may be fewer steps to just take a guess to find some commit that is still good, and get git bisect going.

If you suspect you recently broke something, just go back 16 commits. Or 32 or whatever. Go back to the last tagged release. The binary search will quickly zero in:

$ git bisect start
$ git bisect bad # HEAD known to bad; almost always the case
$ git checkout HEAD~8  # wild guess; almost certainly before breakage
$ good-or-bad-test-command # check: is it really good?
$ # if not, git checkout HEAD~8 # go back more, repeat test
$ git bisect good  # bisect begins

If we have a git with a very long history, and discover something had broken a long time ago (something previously untested which is now tested), we can probe backwards exponentially to find a good commit: git checkout HEAD~16; then if that's not good, git checkout HEAD~32; then git checkout HEAD~64.

That's the general strategy for binary searching through an unknown-range. Don't linearly scan to determine the range, because that makes the algorithm linear. If we exponentially extend the range, we keep it logarithmic.

Kaz
  • 55,781
  • 9
  • 100
  • 149
  • ... but why would we still use `git bisect` in this situation? How did this get six upvotes and accept? – Kaz Mar 25 '17 at 17:49