Matt R. Wilson
Matt R. Wilson

Reputation: 7575

Git: how to tell if changes in branch exist in another branch

The git model my team uses is short-lived feature branches based off master that will get merged into a long-lived dev branch for further testing/QA before the pull-request for the feature is merged to master.

I'm trying to write a small script that takes an action based on if the changeset in a feature branch is already in the dev branch or not. The issue I've been having is that I don't care about specific commits. If the feature was merged into dev then rebased/squashed/etc, the changeset as a whole is in dev. And of course the dev branch will be getting other features added over time that should be ignored (unless they conflict).

I think the simplest way I can explain the request is; if I were to merge a feature branch into the dev branch, would the commit be empty?

I've tried various forms of git diff, branch, merge, and log, but nothing has been quite right. The most accurate working example I have is to checkout dev, merge --no-commit my-feature, check if any files are in the index, then abort the merge. But with all the options git offers I refuse to believe there isn't a simple way to do this.

Upvotes: 2

Views: 2817

Answers (2)

A.H.
A.H.

Reputation: 66283

The git cherry command can be used the check which commits in a branch are textually identical to commits in another branch. The first part of the doc is hard to grasp though, therefore take a hard look to the examples section.

Upvotes: 1

torek
torek

Reputation: 489748

I think the simplest way I can explain the request is; if I were to merge a feature branch into the dev branch, would the commit be empty?

If that's a truly accurate way to measure what you want—and I think it won't be 100% but will be as close as you can get in any sort of automated way—then the way to test this out is to do it:

The most accurate working example I have is to checkout dev, merge --no-commit my-feature, check if any files are in the index, then abort the merge. But with all the options git offers I refuse to believe there isn't a simple way to do this.

There's a technical glitch in the above description: after the merge, there are lots of files in the index. What you want to check for is whether there are any nonzero stage files, indicating a merge conflict, and if not, whether the files in the index match those in the current commit.

That is, in my opinion, the second-best method. It works, but it makes it hard to distinguish some potentially-important cases.

The best is to use git checkout --detach to do the merge, then do the merge without --no-commit, but optionally add --no-ff (described only in the text below):

git checkout --detach dev
git merge <target>         # target can be a hash ID, or a name

One of four things can happen at this point:

  • The merge fails with a conflict or other issue ("would overwrite untracked files", that sort of thing).

    The exit status of git merge is nonzero, which tells you that this is what occurred. (You could try to inspect text generated by git merge but there's no real point.) Use git merge --abort or git reset --merge—these two commands do exactly the same thing; in fact, one of them runs the other one—to end the merge.

  • The merge says "Already up to date" and does nothing.

    The exit status of git merge is zero. The hash ID in HEAD is unchanged from what it was before git merge.

  • The merge says "fast forward" and checks out target.

    The exit status of git merge is zero. The hash ID in HEAD is the hash ID of target (if you used a hash ID as your target, testing this is extra-easy.)

    Eliminate this option entirely by adding --no-ff if you like; in that case you'll get instead the last, fourth option:

  • The merge completes OK, creating a new merge commit.

    The exit status of git merge is zero. The hash ID in HEAD is the hash ID of a new merge commit. Its first parent is the hash ID of commit dev and its second parent is the hash ID of target.

You now must check which of these four (or three if using --no-ff) cases actually occurred:

  1. The first one tells you that the merge is potentially possible but has conflicts, and does require a followup git merge --abort (or git reset --merge) command.

    This is the case that causes the "won't be 100%": for instance, it happens if your target was squash-merged, but dev has since advanced in an incompatible way. You can lodge an alert of some sort since this is the test that fails automation.

  2. The second tells you that the merge base commit of dev / HEAD and target is HEAD, i.e., the commit identified by target is an ancestor of that identified by dev ("is the same commit" is considered to be "is ancestor").

    This case means that it's safe, in some sense, to just delete the target branch.

  3. The third tells you that the commit identified by dev is an ancestor of that identified by target. You may want transform this into case 4 using --no-ff so as to be able to use the final test below. However, this tells you that target is strictly ahead of dev (if they were equal, case 2 would have occurred first).

  4. The fourth tells you that a clean and simple merge is possible. You can now check whether the resulting commit matches the dev commit using:

    test $(git rev-parse HEAD^{tree}) = $(git rev-parse HEAD^^{tree})
    

    If these don't match, merging target would take some change(s) from it. If they do match, merging target has no effect on the tree.

Because your HEAD is detached during this testing process, all you have to do to clean up—other than in case 1 where you must abort the in-progress merge, that is—is git checkout dev to re-attach your HEAD. Any commit actually made is now abandoned, and Git will eventually—after 30 days by default—remove it for real.

Upvotes: 2

Related Questions