13

In our team's repository, we work by submitting PRs and we only merge them into master using github's squash & merge functionality. The problem for me is that I can then no longer see branches merged into master with git branch --merged, as this will not show branches that have been squashed & merged. My local branches are piling up and it's a pain to review them one by one to see which I can remove. Is there another way to list them?

Le Frite
  • 604
  • 7
  • 16
  • if my or any other answer solved your problem please mark it as accepted so that dev can figure that out and don't waste their time on solved problems. – Vijay Rajpurohit Sep 03 '19 at 11:06

2 Answers2

8

tl;dr; Unfortunately, no. You'll have to check the status of your PR on GitHub and, once it has been merged, forcibly delete your local branches with git branch -D.

"Squash and merge" Illustrated

The GitHub Squash and merge operation doesn't actually merge your topic branch ― instead, it squashes all the commits from that branch into a single one, and then rebases that commit on top of the target branch.

From the documentation:

When you select the Squash and merge option on a pull request on GitHub, the pull request's commits are squashed into a single commit.

And the most important part:

Pull requests with squashed commits are merged using the fast-forward option.

A fast-forward merge doesn't create a merge commit ― it simply moves the target branch reference forward so that it points to the same commit as the source branch. This can only be done if the source branch points to a commit that is a descendant of the target branch.

So, if your topic branch looks like this in your local repository:

         master
         v
o--o--o--o
    \
     A--B--C  <- This is the branch you want to merge with a PR
           ^
           topic

When you use Squash and merge, GitHub will rewrite the history of your topic branch so that it looks like this:

         master
         v
o--o--o--o
    \
     S  <- This is A, B and C squashed together
     ^
     topic

Then, GitHub will rebase topic on top of master. Notice that this will result in a commit that's different from the original squashed commit S because of the different parent; to distinguish it, we call the rebased commit S' (S prime):

         master
         v
o--o--o--o
          \
           S'
           ^
           topic

Finally, the topic branch is incorporated into master with a fast-forward merge:

            master
            v
o--o--o--o--S'
            ^
            topic

This all happens inside the repository that's hosted on GitHub's servers. Meanwhile, on your machine the repository still looks like this:

         master
         v
o--o--o--o
    \
     A--B--C
           ^
           topic

After you've done git pull on master, you'll receive the squashed and rebased commit S':

            master
            v
o--o--o--o--S'
    \
     A--B--C
           ^
           topic

Git has no way of knowing that commit S' contains the combined changes from A, B and C. That's why it will still report topic as unmerged.

A Practical Consideration

Linus Torvalds once wrote:

People can (and probably should) rebase their private trees (their own work). That's a cleanup. But never other peoples code. That's a "destroy history".

He then continues:

You must never EVER destroy other peoples history. You must not rebase commits other people did. Basically, if it doesn't have your sign-off on it, it's off limits: you can't rebase it, because it's not yours.

It should be obvious that GitHub's Squash and merge feature destroys other people's history by completely rewriting their PR branches.

I won't sit here and tell you that Squash and merge is intrinsically evil ― I'm sure it has its uses.

However, if your team often finds itself having to deal with stale local branches (like you described), you might want to consider switching to a merge workflow that incorporates the history of your PRs as-is instead of destroying it.

Enrico Campidoglio
  • 56,676
  • 12
  • 126
  • 154
  • 1
    Technically the squash-and-merge button uses the same algorithm as Git's `git merge --squash`: it doesn't build `S` atop the original branch, but rather directly atop the target, so no final rebase is required. The effect is the same, though. – torek Aug 28 '19 at 18:23
0

I came accross the same problem using PRs and squash merges and made a small command to cleanup the undesired branches, based on the ones that had an upstream branch.

It also avoids main branches deletion like master, develop, and release :

git branch -d $(git branch --format "%(refname:short) %(upstream)" | awk '{if ($2) print $1;}' | grep -vE "release|develop|master") -f

I made it using the answer to this question : List all local branches without a remote

smottt
  • 3,272
  • 11
  • 37
  • 44
Sebastien H.
  • 6,818
  • 2
  • 28
  • 36