Skip to content

Instantly share code, notes, and snippets.

@nathanhinchey
Forked from canton7/0main.md
Last active December 8, 2015 19:19
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save nathanhinchey/20b6c6cae3cf5d2be3cc to your computer and use it in GitHub Desktop.
Save nathanhinchey/20b6c6cae3cf5d2be3cc to your computer and use it in GitHub Desktop.
Git Bisect and Feature Branches

Git Bisect and Feature Branches

There are people out there who claim that merge-based workflows (that is, workflows which contain non-fast-forward merges) are bad. They claim that git bisect gets confused by merge-based workflows, and instead advocate rebase-based workflows without explicit feature branches.

They're wrong.

Furthermore, the "advantages" of their workflows are in fact disadvantages. Let me show you.

We're first going to see that git bisect can, in fact, spot a fault commit in a merge-based workflow, and then we'll see why merge-based workflows offer better options for resolving the situation.

Spotting the faulty merge

We're going to create a simple test repo, with one file which exits with 1 or 0, signifying a broken and non-broken commit respectively (this will be our test, which we pass to git bisect run), and some unrelated files which we'll change to create some "padding" commits. See the transcript in the file below if you want the details.

I've replaced the commit hashes with letters for clarity.

     C--D-------- 
    /            \
A--B--E------M1--M2
       \    /
        F--G
        ^
  faulty commit

Now we've got this history, let's see what git bisect makes of it.

canton7@creek bisect_test (master) $ git bisect start M2 B
Bisecting: 2 revisions left to test after this (roughly 2 steps)
[M1] Merge branch 'feature_b'

canton7@creek bisect_test ((M1)|BISECTING) $ git bisect run ./test.sh
running ./test.sh
Bisecting: 1 revision left to test after this (roughly 1 step)
[F] F (break the test)
running ./test.sh
Bisecting: 0 revisions left to test after this (roughly 0 steps)
[E] E
running ./test.sh
F is the first bad commit
commit F
Author: canton7 <email>
Date:   Mon Sep 17 13:50:07 2012 +0100

    F (break the test)

:000000 100644 0000000000000000000000000000000000000000 5626abf0f72e58d7a153368ba57db4c673c0e171 A      feature_b
:100755 100755 eecf7fdfc798a861a5faea10be05dae6b721a19b 275d27c3f381aa5bf18c9263e420e93c80515fcb M      test.sh
bisect run success

# And reset the bisect

canton7@creek bisect_test ((7958de0...)|BISECTING) $ git bisect reset
Previous HEAD position was E... E
Switched to branch 'master'

Will you look at that? git bisect found the faulty commit, confusing merges and all! I've also tried it out on a number of other potentially confusing histories, and git bisect has nailed it each time.

Repairing the situation

Now that we've identified the faulty commit, we've got a few options:

  1. Simply create a new commit which fixes the problem with the faulty one
  2. Revert the faulty commit (assuming it doesn't break the feature it's on), then repair it later
  3. Revert the entire feature (assuming it doesn't break other features), then repair it later

Option 1. is the quick and dirty solution, although it means that the commits which are to do with your feature are no longer confined to the feature branch.
Option 2. is probably not a good idea: the rest of the feature is very likely to break, and you've still got the problem from option 1.
Option 3. allows all of the commits relating to the feature branch to remain in a branch (we'll do an example in a minute), although you need to know what you're doing (and to have read this).

Note that with a rebase-and-ff-only workflow, option 3. is probably not available: you don't know where the feature branch was, so you can't revert it.

Let's do an example of option 3.

We'll first revert M1, which brought the faulty commit into master. This will give us some breathing space to come up with a fix. We'll then resurrect the feature branch, rebase it (otherwise the changes from F and G won't be re-applied when we merge in the resurrected branch, see this document), fix it, and merge it back in.

There are other workflows available here, including reverting the revert. Again, this document is your bible right now.

canton7@creek bisect_test (master) $ git revert -m 1 M1
[master W1] Revert "Merge branch 'feature_b'"
 2 files changed, 1 insertion(+), 3 deletions(-)
 delete mode 100644 feature_b

canton7@creek bisect_test (master) $ git checkout -b feature_b_repair M1^2
Switched to a new branch 'feature_b_repair'

canton7@creek bisect_test (feature_b_repair) $ git rebase -f E
Current branch feature_b_repair is up to date, rebase forced.
First, rewinding head to replay your work on top of it...
Applying: F (break the test)
Applying: G

canton7@creek bisect_test (feature_b_repair) $ echo -e '#!/bin/bash' "\nexit 0" > test.sh
canton7@creek bisect_test (feature_b_repair *) $ git commit -am "H (fix test)"
[feature_b_repair H] H (fix test)
 1 file changed, 1 insertion(+), 1 deletion(-)

At this point, our history looks like this:

     C--D--------
    /            \
A--B--E------M1--M2--W1
      |\    /            
      | F--G           
       \               
        F'--G'--H

If we wanted, we could git rebase -i E and squash H info F', and maybe even rebase the whole thing onto W1. We'll skip that here for simplicity though.

At this point, all we need to do is to merge in the corrected feature branch:

canton7@creek bisect_test (feature_b_repair) $ git checkout master && git merge feature_b_repair
Switched to branch 'master'
Updating eba57aa..4c29869
Fast-forward
 feature_b | 2 ++
 1 file changed, 2 insertions(+)
 create mode 100644 feature_b

to get our finished history:

     C--D--------
    /            \
A--B--E------M1--M2--W1--M3
      |\    /            /
      | F--G            /
       \               /
        F'--G'--H------
canton7@creek ~ $ git init bisect_test && cd bisect_test
Initialized empty Git repository in /home/canton7/bisect_test/.git/
canton7@creek bisect_test (master #) $ git commit --allow-empty -m "A (initial commit)"
[master (root-commit) A] Initial Commit
canton7@creek bisect_test (master) $ echo -e '#!/bin/bash' "\nexit 0" > test.sh
canton7@creek bisect_test (master) $ chmod +x test.sh
canton7@creek bisect_test (master) $ echo one > master
canton7@creek bisect_test (master) $ git add master test.sh
canton7@creek bisect_test (master +) $ git commit -m "B (add working test)"
[master B] Add working test
2 files changed, 3 insertions(+)
create mode 100644 master
create mode 100755 test.sh
canton7@creek bisect_test (feature_a) $ echo one > feature_a
canton7@creek bisect_test (feature_a) $ git add feature_a && git commit -m "C"
[feature_a C] C
1 file changed, 1 insertion(+)
create mode 100644 feature_a
canton7@creek bisect_test (feature_a) $ echo two >> feature_a && git commit -am "D"
[feature_a D] D
1 file changed, 1 insertion(+)
canton7@creek bisect_test (master) $ git checkout -b feature_b
Switched to a new branch 'feature_b'
canton7@creek bisect_test (feature_b) $ echo one > feature_b
canton7@creek bisect_test (feature_b) $ echo -e '#!/bin/bash' "\nexit 1" > test.sh
canton7@creek bisect_test (feature_b *) $ git add feature_b && git commit -am "F (break the test)"
[feature_b F] F (break the test)
2 files changed, 2 insertions(+), 1 deletion(-)
create mode 100644 feature_b
canton7@creek bisect_test (feature_b) $ echo two >> feature_b && git commit -am "G"
[feature_b G] G
canton7@creek bisect_test (feature_b) $ git checkout master && git merge --no-ff feature_b && git branch -d feature_b
Switched to branch 'master'
Merge made by the 'recursive' strategy.
feature_b | 2 ++
test.sh | 2 +-
2 files changed, 3 insertions(+), 1 deletion(-)
create mode 100644 feature_b
Deleted branch feature_b (was G).
canton7@creek bisect_test (master) $ git merge feature_a && git branch -d feature_a
Merge made by the 'recursive' strategy.
feature_a | 2 ++
1 file changed, 2 insertions(+)
create mode 100644 feature_a
Deleted branch feature_a (was D).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment