1

I have created a repository hook on Bitbucket that automatically creates a pull request from one branch to another (and merge it) when some specific conditions are fulfilled. I would like to detect the case where the result of the merge is that no files will be modified (empty merge).

What I tried so far:

  1. To check if there is commits on source branch that are not on target branch :

    CommitsBetweenRequest commitsBetweenRequest = new CommitsBetweenRequest
         .Builder(repository)
         .include(sourceBranch)
         .exclude(targetBranch)
         .build();
    
    Boolean anyChanges = commitService
         .getCommitsBetween(commitsBetweenRequest, new PageRequestImpl(0, 1))
         .stream()
         .anyMatch();
    

This works great for simple cases. However, some "empty merges" are still undetected. This occurs when files modified on source branch have also been modified on target branch the same way, but in different commits.

               FB1 -- FB2 (source)
              /          
             /            
---- A ---- B ---- C (target)

getCommitsBetween() will return FB1 and FB2. However, if files modified in FB1 and FB2 have been modified in C as well (the same way) the result of the merge is no changes at all.

  1. Checking the difference between branches in terms of file changes :

    //I have tried to invert sourceBranch and targetBranch, without success
    ChangesetsRequest changesRequest = new ChangesetsRequest
             .Builder(request.getRepository(), sourceBranch)
             .sinceId(targetBranch) 
             .build();
    commitService.getChangesets(changesRequest, new PageRequestImpl(0, 1));
    

It does not give expected results. It give me the list of files modified in both source and target branch (not the files modified as the result of the merge).

tigrou
  • 4,236
  • 5
  • 33
  • 59
  • 1
    No changed files mean that both `tree`s of the branch heads must be identical already. Compare the trees, if they are identical, no changes. If they are different, very likely to have changes. – knittl Oct 24 '22 at 18:05
  • Do you mean the ref of each branch being the same ? This is something already detected by bitbucket itself (it gives a `EmptyPullRequestException` during pull request creation attempt). – tigrou Oct 24 '22 at 18:08
  • 1
    No, not the refs, but the trees. Refs point to commits. Commits point to trees. You want to compare the trees. – knittl Oct 24 '22 at 18:12
  • Can you check the graphical example I give ? – tigrou Oct 24 '22 at 18:18
  • 2
    Find the tree id. With Git on the CLI this would be `git rev-parse FB2^{tree}` and `git rev-parse C^{tree}`. If both ids are identical, then both branches already contain the same source code and the merge will be "empty". You can also merge, then compare tree ids or diff the merge commit against C. – knittl Oct 24 '22 at 18:21
  • @knittl I don't think the trees need to be identical, for example if the target branch has more (new) changes than the source branch since the merge-base, and all of the source branch changes are already in the target branch. (Cherry-picked or re-implemented the same way.) – TTT Oct 24 '22 at 19:28
  • @TTT but if the changes have been cherry-picked or re-implemented in the same way, won't that give exactly the same tree? – knittl Oct 24 '22 at 19:29
  • @knittl not if there are also other changes too on the target branch that aren't on the source branch? – TTT Oct 24 '22 at 19:31
  • 1
    I agree with @knittl's comment that maybe the easiest way is just do the merge temporarily and diff before and after. – TTT Oct 24 '22 at 19:33
  • 3
    @TTT ah, I see now. Yes, you are right. The target branch could contain new(er) changes, so both branches would have different trees. The only way is probably to do a temporary merge and then compare trees of the merge commit and the first parent – knittl Oct 24 '22 at 19:34

1 Answers1

1

This question can be even more general:

How can you determine what changes will occur as a result of a merge?

Perhaps the quickest and easiest method, is to simply perform the merge and see what changed:

git switch target-branch --detach
git merge source-branch --no-ff
git diff @~1 @

Here we are checking out the target branch using --detach so you move off of the branch and don't actually modify it, and we merge with --no-ff to make sure the diff command works even in the case where the source branch could be fast-forwarded. Commits are cheap in Git and get cleaned up with garbage collection, so it shouldn't ever be a problem to make a commit just for testing. Behind the scenes this is probably what Git SCM tools are doing to let you view a Pull/Merge Request before it's completed.

Note, in the case of your specific question, if the above diff command has no output, then you have confirmed your query regarding "no changes".

But do we really have to actually do the merge; is it possible to determine it?

I don't know for sure, but for now I'm going with you probably have to do the merge. Usually you can get pretty darned close by checking the diff of just the source branch from the target branch, for example:

git diff target-branch...source-branch
# note the 3 dots is shorthand for starting from the merge-base, so this is like:
git diff $(git merge-base source-branch target-branch) source-branch

but, as you point out in the question, that's not always perfect if some of the same changes were made on both the source and target branches. You could inspect the changes the other way too and compare them piece by piece, but it's likely that the logic to determine it is complicated enough that your best bet is to just do the merge and look at it. I wouldn't bother trying to re-create the merge logic if you don't have to. Besides, there are different merge strategies that can be used, and even the default merge strategy is subject to change; in fact it did recently.

TTT
  • 22,611
  • 8
  • 63
  • 69
  • Thanks for the answer. Btw, are you aware that I have to use Bitbucket API (https://docs.atlassian.com/bitbucket-server/javadoc/4.14.4/api/reference/packages.html) to implement the hook and so I cannot really run those low level git commands ? I know there is some repository hooks implemented with shell scripts but this is not what I use. – tigrou Oct 24 '22 at 22:16
  • @tigrou I think I knew that when I read your question, and forgot about that by the time I answered it... So my vote would be figure out a way to test the merge first, otherwise #3 is probably the most accurate. Out of curiosity, is this a problem that is coming up frequently? My assumption is it should be fairly uncommon unless you have heavy use of cherry-picking in your workflow. – TTT Oct 24 '22 at 22:49