Le Frite
Le Frite

Reputation: 641

Display local branches that have been "squashed & merged" into master

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?

Upvotes: 14

Views: 1759

Answers (3)

rampion
rampion

Reputation: 89053

You can use the gh command line tool for this:

$ gh pr list -s merged --json headRefName -q '.[].headRefName' |
  sort -k 1,1 |
  join <(git branch | sort -k 1,1) -

gh pr list ... will display the branch names used when a commit was merged; we can compare it with the local list of branch names from git branch using the join command, though we need to sort both lists first.

More conservatively, you could check which ones match based on commit object id, rather than on the name:

$ gh pr list -s merged --json headRefOid -q '.[].headRefOid' |
  sort -k1,1 |
  join <(git branch --format '%(objectname) %(refname:short)' | sort -k1,1) -

Upvotes: 1

Sebastien H.
Sebastien H.

Reputation: 7146

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

Upvotes: 0

Enrico Campidoglio
Enrico Campidoglio

Reputation: 59913

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.

Upvotes: 8

Related Questions