Gabriel
Gabriel

Reputation: 197

discovering merged git branches on repositories that use squash by default?

If i have a local git branch called feature1 with a couple commits. I create a github PR and merge it.

If the repository is not using the awful github's squash merge*, i am able to use after updating my master/main branch:

git branch --merged
* main
feature1

To easily see which of my feature branches are already merged in the up-to-date main/master branch.

Now, if the github repository is doing Squashed merges, how do I figure out programatically if a branch was merged into master/main or not?

Upvotes: 1

Views: 169

Answers (1)

torek
torek

Reputation: 488253

This isn't really possible in the full general case, but you may not care about that for two reasons:

  • GitHub do not provide the full general case.
  • Some of those full general cases amount to nitpicking anyway.

So this gives us two ways to handwave away the hard cases, with the first one probably being the most important. What you will get, once you write a program to do this, is a program that probably works well enough for all the cases you care about and will ever encounter.

There is not, however, any pre-written solution you can just use, as far as I know. You will have to write your own program—perhaps a simple shell script. (I started on one once, but I haven't ever gone back to fix it up. Some cases get tricky.)

When someone uses either the REBASE AND MERGE or SQUASH AND MERGE button on GitHub, they add, to their GitHub repository, either a single commit—SQUASH AND MERGE—or multiple commits with REBASE AND MERGE. There are some distinguishing features about these commits, so that after you git fetch their commits from their repository, you can compare these commits to your commits on your feature branch:

  • First, and perhaps most important, every one of these commits can be generated mechanically by Git itself, by running either git rebase or git merge --squash: such a command will run to completion, without merge conflicts. This means you can do the same thing in your own Git repository, and see if it works and produces the same source snapshot.

  • Second, if they've used REBASE AND MERGE, there will be a one-to-one correspondence between each of their commits and the commits that your own Git will copy when you run a no-interaction rebase in your own repository. If they've used SQUASH AND MERGE there won't be and you'll want to run a no-interaction git merge --squash in your own repository, to see if you get the same source snapshot without conflicts.

If we make some often-but-not-always true simplifying assumptions, we can do the checking without actually running git rebase or git merge --squash. This is particularly helpful for the rebase case because there are multiple different ways to run rebase, each of which has slightly different pitfalls, and it's not clear to me, at least, which of these methods GitHub use (or they may have cobbled together their own non-interactive conflict-detecting equivalent using git cherry-pick).

The biggest simplification is this one. If we have the same starting point that they do, then each copied commit, or the single squashed commit, will produce the same source snapshot we have in each of our commits, or at the tip of our feature branch. That is, suppose we make a PR when we have this state:

...--o--o--*   <-- remote/main
            \
             A--B--C   <-- our-feature

They use either of those two annoying modes that copies our commits, so that the hash IDs don't match, or squashes (same effect). We fetch their new commits into our own repository, and we now have either:

...--o--o--*--ABC   <-- remote/main
            \
             A--B--C   <-- our-feature

or:

...--o--o--*--A'-B'-C'  <-- remote/main
            \
             A--B--C   <-- our-feature

In both of these cases, the tip commit of their branch—our remote/main commit, which is either ABC or C'—has the same source snapshot as our commit C at the tip of our feature. So:

git rev-parse remote/main^{tree}

and:

git rev-parse feature^{tree}

will produce the same hash ID.

This particular simplification breaks down when they start from a commit that isn't the tip of ours, so that they have:

...--o--o--*--X--ABC   <-- main

in their repository for instance. Now their ABC squash commit doesn't match our C commit because of the extra X commit. It does hold, but becomes harder to test, if they do this:

...--o--o--*--ABC--X   <-- main

because now we have to find commit ABC by working backwards from the tip of our remote/main after we fetch the ABC-X chain and add it to our own repository.

For the "breaks down" case, we really have to reproduce the action that GitHub took so that we can test whether the source snapshot matches what Git can mechanically produce on its own (if anything). This requires enumerating all the new-to-us commits after a git fetch operation, so it gets messy.

Upvotes: 3

Related Questions