1. 24

  2. 26

    If you use squash, you don’t have to literally squash your whole changeset into a single commit. You can use squash in conjunction with the interactive rebase to maintain meaningful history without spammy commits like ‘fix typo’, ‘fix compile error’, etc.

    E.g. you wrote a feature, committed, then wrote a test, commited. you end up with

    -1 feature (changes to src/code.py)
     0 test    (changes to tests/test.py) (HEAD)

    Then, say, you run a linter/code formatting tool/whatever – you ended up with changes to both files. What’s the right thing to do?

    Personally, I’d use interactive staging (add -p) to stage the changes to src/code.py and tests/test.py separately and commit separately too:

    -3 feature (changes to src/code.py)
    -2 test    (changes to tests/test.py)
    -1 fix src/code.py
     0 fix tests/test.py (HEAD)

    , then I’d use interactive rebase to reorder as -3, -1, -2, 0 and squash together -3 + -1; -2 + 0, which results in a nice atomic history:

    -1 feature (changes to src/code.py)
     0 test    (changes to tests/test.py) (HEAD)

    Another neat rebase trick you could use is to reorder the test commit and feature commit:

    -1 test    (changes to tests/test.py)
     0 feature (changes to src/code.py) (HEAD)

    , and checkout HEAD~1 and run the test to ensure it actually fails without the feature commit.

    1. 3

      I used to do it just as you describe, and then I discovered git commit --fixup <SHA>. With your example this becomes:

      git add -p src/code.py
      git commit --fixup <SHA of -3>
      git add -p tests/test.py
      git commit --fixup <SHA of -2>
      git rebase --autosquash -i <SHA of -3>^

      This saves me having to put the commits to squash in the right order in the interactive rebase.

      1. 1

        You may enjoy using git-absorb which automatically finds the SHA hash for --fixup commits.

        1. 1

          Seems interesting, but the description doesn’t make it clear how it works so it seems a bit too “magic”. What does it do if you have multiple commits touching the same file(s)?

          1. 1

            It doesn’t just look at the file, but actual changes (since you first git add [-p] all the changes you want to absorb). I believe it just picks the most recent commit touching the same change set. I haven’t needed to use it when the commit I’m wanting to fixup is multiple commits back on the same change set (which I believe would conflict and need a manual rebase anyways).

    2. 5

      The utility of git bisect is exactly why I usually don’t squash. I like to make committed changesets small, but not so small that the commits don’t compile. If I make a work-in-progress commit, I’ll probably amend that one as I go until it does build.

      1. 5

        I tend to not squash, but one advantage of squashing/rewording cleanly is that bisecting will usually land you on commits with a “working system”. People who go all in on microcommits sometimes end up in places where your system doesn’t boot up on half the working tree (exaggerating a bit but…)

        1. 4

          I don’t think this is a good argument against squashing commits using the GitHub model. It lets you merge a PR by combining all commits on the branch into a single commit, with a reference back to the original PR. Then you can “delete” the branch, which really just archives it so you can always get it back later. I believe GitLab / Bitbucket also let you do this.

          Where I work, we squash all PRs when merging into master. PRs are usually very small, and we deploy after each PR is merged. If a problem arises in stage or canary, we can just rollback the entire PR. Then we bring back the original branch, and can bisect individual commits. This lets us keep a clean history and be able to look at all the commits used to create a feature.

          When working locally, I never rewrite history. It just seems clearer to me to continually commit new code to a branch and squash later when I want to merge to master.

          1. 2

            It isn’t rational, but I tend to see it as a small personal defeat if I have to use the debugger.

            This is one of my gotos next to console logs. I am primarily front end, and tests aren’t exactly pervasive here. The problem is often easier to understand when you have lots of asynchronous stuff.

            1. 1

              Nice read! I think another angle on this is to introduce CD into your workflow, so you could have found the bug sooner rather than after 150 commits.

              1. 2


                There are bugs and behaviors that CI won’t catch…if there is some emergent business property that changed but is still within tolerance, bisecting can help spot it even if there was no test for it.