Sometimes, when we submitted a pull request, we might realize that the changes in the pull request failed some tests. In some cases, the changes from the pull request can be grouped and each group should not fail any test in principle, we will have to identify which exact group of changes caused the failure. Ideally, each group of changes should become a separate commit, so we can use the bisect feature of Git, i.e., git bisect to find which commit introduced the failure. However, in practice, this is usually not the case, and we will still have to identify the problematic changes from one commit.
In this blog post, I would like to discuss how to use Git stash, i.e., git stash, to help us perform bisection to root cause the problematic changes.
Git Stash Bisection
Suppose we have a commit that introduces changes in five files, including 0.cpp, 1.cpp, 2.cpp, 3.cpp, and 4.cpp. The change of each file is independent from the others and we want to identify which file’s change caused the test failure.
diff --git a/0.cpp b/0.cpp new file mode 100644 index 0000000..e69de29 diff --git a/1.cpp b/1.cpp new file mode 100644 index 0000000..e69de29 diff --git a/2.cpp b/2.cpp new file mode 100644 index 0000000..e69de29 diff --git a/3.cpp b/3.cpp new file mode 100644 index 0000000..e69de29 diff --git a/4.cpp b/4.cpp new file mode 100644 index 0000000..e69de29
commit 37d3ac9cce852faaf6d6a850b946bbcc97e1c9f9 (make_changes, changes) Author: Lei Mao <dukeleimao@gmail.com> Date: Thu Aug 14 18:57:08 2025 -0700
Initial commit
diff --git a/README.md b/README.md new file mode 100644 index 0000000..c5e3993 --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +# Test-Repo \ No newline at end of file
Git stash git stash allows us to temporarily save changes that are not ready to be committed and remove them from the Git repository. In our case, we have already pushed the commit of the changes. So we will have to revert the commit locally for debugging.
1 2 3 4 5 6 7 8 9 10 11 12 13
$ git reset --soft HEAD~1 $ git status On branch main Your branch is behind 'origin/main' by 1 commit, and can be fast-forwarded. (use "git pull" to update your local branch)
Changes to be committed: (use "git restore --staged <file>..." to unstage) new file: 0.cpp new file: 1.cpp new file: 2.cpp new file: 3.cpp new file: 4.cpp
We could list the name of changed files using git diff.
After stashing half of the changed files, we will run unit tests to see if the unit tests will pass. If the unit tests fail, the culprit changes must be in the remaining files. Otherwise the culprit changes must be in the stashed files, and we will pop the stash to get the changes back and stash the files that have passed the unit tests. We will continue counting the number of changed files and half of the changed files and stashing half of them until only one file is left, which contains the culprit change.
$ git status On branch main Your branch is behind 'origin/main' by 1 commit, and can be fast-forwarded. (use "git pull" to update your local branch)
Changes to be committed: (use "git restore --staged <file>..." to unstage) new file: 0.cpp new file: 1.cpp new file: 2.cpp new file: 3.cpp new file: 4.cpp
$ git stash list $ count=$(git diff --name-only --cached | wc -l) $ first_half=$(( (count + 1) / 2 )) $ second_half=$(( count - first_half )) $ git stash -m "bisection_stashed" -- $(git diff --name-only --cached | head -n $first_half) Saved working directory and index state On main: bisection_stashed $ git status On branch main Your branch is behind 'origin/main' by 1 commit, and can be fast-forwarded. (use "git pull" to update your local branch)
Changes to be committed: (use "git restore --staged <file>..." to unstage) new file: 3.cpp new file: 4.cpp $ git stash list stash@{0}: On main: bisection_stashed $ # Run unit test to test the second half of the changed files. $ git stash pop stash@{0} On branch main Your branch is behind 'origin/main' by 1 commit, and can be fast-forwarded. (use "git pull" to update your local branch)
Changes to be committed: (use "git restore --staged <file>..." to unstage) new file: 0.cpp new file: 1.cpp new file: 2.cpp new file: 3.cpp new file: 4.cpp
Dropped stash@{0} (e2344fe3b43a8bd2b0dfbb82416e375ac3b5d730) $ git stash -m "bisection_stashed" -- $(git diff --name-only --cached | tail -n $second_half) Saved working directory and index state On main: bisection_stashed $ git status On branch main Your branch is behind 'origin/main' by 1 commit, and can be fast-forwarded. (use "git pull" to update your local branch)
Changes to be committed: (use "git restore --staged <file>..." to unstage) new file: 0.cpp new file: 1.cpp new file: 2.cpp
$ git stash list stash@{0}: On main: bisection_stashed $ # Run unit test to test the first half of the changed files. $ git stash pop stash@{0} On branch main Your branch is behind 'origin/main' by 1 commit, and can be fast-forwarded. (use "git pull" to update your local branch)
Changes to be committed: (use "git restore --staged <file>..." to unstage) new file: 0.cpp new file: 1.cpp new file: 2.cpp new file: 3.cpp new file: 4.cpp
$ group_0=$(ls 0.cpp 1.cpp 2.cpp) $ group_1=$(ls 3.cpp 4.cpp) $ git status On branch main Your branch is behind 'origin/main' by 1 commit, and can be fast-forwarded. (use "git pull" to update your local branch)
Changes to be committed: (use "git restore --staged <file>..." to unstage) new file: 0.cpp new file: 1.cpp new file: 2.cpp new file: 3.cpp new file: 4.cpp
$ git stash -m "bisection_stashed" -- $group_0 Saved working directory and index state On main: bisection_stashed $ git status On branch main Your branch is behind 'origin/main' by 1 commit, and can be fast-forwarded. (use "git pull" to update your local branch)
Changes to be committed: (use "git restore --staged <file>..." to unstage) new file: 3.cpp new file: 4.cpp
$ git stash list stash@{0}: On main: bisection_stashed $ git stash pop stash@{0} On branch main Your branch is behind 'origin/main' by 1 commit, and can be fast-forwarded. (use "git pull" to update your local branch)
Changes to be committed: (use "git restore --staged <file>..." to unstage) new file: 0.cpp new file: 1.cpp new file: 2.cpp new file: 3.cpp new file: 4.cpp
Dropped stash@{0} (286fda539a46c10ffb4e9bcc34246a90e8af7b45) $ git stash -m "bisection_stashed" -- $group_1 Saved working directory and index state On main: bisection_stashed $ git status On branch main Your branch is behind 'origin/main' by 1 commit, and can be fast-forwarded. (use "git pull" to update your local branch)
Changes to be committed: (use "git restore --staged <file>..." to unstage) new file: 0.cpp new file: 1.cpp new file: 2.cpp
$ git stash list stash@{0}: On main: bisection_stashed $ git stash pop stash@{0} On branch main Your branch is behind 'origin/main' by 1 commit, and can be fast-forwarded. (use "git pull" to update your local branch)
Changes to be committed: (use "git restore --staged <file>..." to unstage) new file: 0.cpp new file: 1.cpp new file: 2.cpp new file: 3.cpp new file: 4.cpp