<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Modern Git Academy</title>
    <description>The latest articles on DEV Community by Modern Git Academy (moderngitacademy).</description>
    <link>https://dev.to/moderngitacademy</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Forganization%2Fprofile_image%2F14721%2F235d0598-18bf-486d-8264-ae448aeab269.PNG</url>
      <title>DEV Community: Modern Git Academy</title>
      <link>https://dev.to/moderngitacademy</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/moderngitacademy"/>
    <language>en</language>
    <item>
      <title>Twelve Commits, One Regression, Four Test Runs: git bisect run</title>
      <dc:creator>James Joyner</dc:creator>
      <pubDate>Sun, 20 Sep 2026 01:02:55 +0000</pubDate>
      <link>https://dev.to/moderngitacademy/twelve-commits-one-regression-four-test-runs-git-bisect-run-ahn</link>
      <guid>https://dev.to/moderngitacademy/twelve-commits-one-regression-four-test-runs-git-bisect-run-ahn</guid>
      <description>&lt;p&gt;The test fails on &lt;code&gt;main&lt;/code&gt;. It passed a week ago. Twelve commits landed in between, most of them documentation, and nobody remembers which one touched the code. You could read all twelve diffs. Or you could let Git binary-search them in four steps.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;git bisect&lt;/code&gt; is the tool. It is older than most of the people who avoid it, and it needs three things: a commit you know is bad, a commit you know is good, and a way to tell the two apart. Everything below was run in a disposable repository (git 2.43.0); the output is real.&lt;/p&gt;

&lt;h2&gt;
  
  
  The setup: a calculator that stopped handling negatives
&lt;/h2&gt;

&lt;p&gt;A shell script that sums its arguments, a test that checks &lt;code&gt;2 3 → 5&lt;/code&gt; and &lt;code&gt;10 -4 → 6&lt;/code&gt;, and twelve commits — ten of which only append to &lt;code&gt;NOTES.md&lt;/code&gt;. One "performance" commit changed a line in the script.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;./test.sh &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;echo &lt;/span&gt;PASS &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;echo &lt;/span&gt;FAIL
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;FAIL
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git log &lt;span class="nt"&gt;--oneline&lt;/span&gt; | &lt;span class="nb"&gt;head&lt;/span&gt; &lt;span class="nt"&gt;-3&lt;/span&gt;
git rev-list &lt;span class="nt"&gt;--count&lt;/span&gt; HEAD
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;13e3f4d Docs: note 10
de66fcd Docs: note 9
ee74893 Docs: note 8
12
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Manual bisect: the first step, by hand
&lt;/h2&gt;

&lt;p&gt;Tell Git the two ends. &lt;code&gt;HEAD&lt;/code&gt; is bad; the first commit is known good (the test was written with it):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git bisect start
git bisect bad
git bisect good c7c12bd
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;status: waiting for both good and bad commits
status: waiting for good commit(s), bad commit known
Bisecting: 5 revisions left to test after this (roughly 3 steps)
[ab66c706055338cda0ca242c3deda07a5b8f5257] Docs: note 5
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Git checked out the middle commit. Run the test there and report:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;./test.sh &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;echo &lt;/span&gt;PASS &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;echo &lt;/span&gt;FAIL
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;PASS
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So &lt;code&gt;git bisect good&lt;/code&gt;, and Git halves the remaining range again. "Roughly 3 steps" is log₂ of twelve — the same three or four steps whether the culprit is the second commit or the eleventh. At each stop you run the test and say &lt;code&gt;good&lt;/code&gt; or &lt;code&gt;bad&lt;/code&gt;; &lt;code&gt;git bisect reset&lt;/code&gt; returns you to where you started.&lt;/p&gt;

&lt;h2&gt;
  
  
  Automated bisect: hand Git the test
&lt;/h2&gt;

&lt;p&gt;If the test is a command that exits 0 on pass and non-zero on fail — which &lt;code&gt;test.sh&lt;/code&gt; is — you do not need to be in the loop at all:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git bisect start HEAD c7c12bd
git bisect run ./test.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;running './test.sh'
Bisecting: 2 revisions left to test after this (roughly 2 steps)
running './test.sh'
Bisecting: 0 revisions left to test after this (roughly 1 step)
running './test.sh'
Bisecting: 0 revisions left to test after this (roughly 0 steps)
running './test.sh'
ee25549ef0e8991131dcb8fe06a5dfa8398eb3d9 is the first bad commit
commit ee25549ef0e8991131dcb8fe06a5dfa8398eb3d9
Author: Lab User &amp;lt;lab@example.com&amp;gt;

    Perf: simplify addition loop

 calc.sh | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
bisect found first bad commit
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Four test runs. Then:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git bisect reset
git show ee25549 &lt;span class="nt"&gt;--&lt;/span&gt; calc.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;-total=0; for n in "$@"; do total=$((total + n)); done; echo "$total"
+total=0; for n in "$@"; do total=$((total + ${n#-})); done; echo "$total"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;${n#-}&lt;/code&gt; strips a leading minus sign. Someone "simplified" the loop and turned every negative into a positive. The commit message says "Perf"; the diff says otherwise. That is the whole value of bisect: it takes you to the diff instead of the story.&lt;/p&gt;

&lt;h2&gt;
  
  
  The three things that make it work
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;A test that can run at any commit.&lt;/strong&gt; &lt;code&gt;bisect run&lt;/code&gt; checks out old commits, so the test must not depend on files that arrived later. Keep the test script &lt;em&gt;outside&lt;/em&gt; the range if it was added recently — put it in a temp directory and call it by absolute path — or accept that the range starts where the test does.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Exit codes, not output.&lt;/strong&gt; &lt;code&gt;run&lt;/code&gt; reads the exit status: 0 means good, 1–127 means bad, except that &lt;strong&gt;125 means "cannot test this commit, skip it"&lt;/strong&gt; — use it for commits that do not build. Any other code aborts the bisect (the manual's words).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Honest endpoints.&lt;/strong&gt; If the "good" commit is not actually good, bisect converges on the wrong answer with the same confidence. Run the test at both ends first; it costs two runs and saves an afternoon.&lt;/p&gt;

&lt;h2&gt;
  
  
  Beyond the toy
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Test a specific thing, not the whole suite:&lt;/strong&gt; &lt;code&gt;git bisect run npm test -- --grep "negative"&lt;/code&gt;. Faster, and it fails for the right reason.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Skip commits by hand:&lt;/strong&gt; &lt;code&gt;git bisect skip&lt;/code&gt; when a commit cannot be tested; Git works around it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reuse a session:&lt;/strong&gt; &lt;code&gt;git bisect log &amp;gt; bisect.txt&lt;/code&gt;, later &lt;code&gt;git bisect replay bisect.txt&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Merges:&lt;/strong&gt; bisect walks the DAG correctly; the first bad commit can be a merge, and that is a real answer.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The lab has the same repository, a second regression to find without &lt;code&gt;run&lt;/code&gt;, and the reflog step that recovers you if you bisect on a dirty tree: &lt;a href="https://moderngitacademy.com/labs/bisect-regression/" rel="noopener noreferrer"&gt;Find a regression with git bisect&lt;/a&gt;. It is one of twenty labs, each in a disposable repository with real output.&lt;/p&gt;

</description>
      <category>git</category>
      <category>debugging</category>
      <category>testing</category>
      <category>devops</category>
    </item>
  </channel>
</rss>
