8

I'm writing a custom merge driver that needs to be aware of the names of the branches it is merging. I managed to retrieve the name of the branch being merged into (destination) with git symbolic-ref HEAD and the name of the branch being merged in (source) from the GITHEAD_<SHA> environment variable.

# retrieve merged branch name from an env var GITHEAD_<sha>=<branchName> 
# we cannot use a sym ref of MERGE_HEAD, as it doesn't yet exist 
gitHead=$(env | grep GITHEAD) # e.g. GITHEAD_<sha>=release/1.43 
# cut out everything up to the last "=" sign 
source="${gitHead##*=}"

# retrieve base branch name from a sym ref of HEAD 
branch=$(git symbolic-ref HEAD) # e.g. refs/heads/master 
# cut out "refs/heads"
destination="${branch#refs/heads/}"

echo "Merging from $source into $destination"

Is this the right way of doing this? Particularly, retrieving the source name from an environment variable seems flaky. Note that MERGE_HEAD is not present at this point, so I cannot use the same approach as with HEAD.

vanjan
  • 181
  • 4

2 Answers2

1

Yes, this is as close as you can get in a merge driver.

A merge strategy gets the correct name by looking up each GITHEAD_%s where each %s argument is filled in from the argument(s) given to the merge strategy, one per "remote head". See my answer to a related question for details. This is not documented anywhere, it's just from the source. Unfortunately, by the time you get down to a merge driver, this information is lost: there are no % directives that retrieve those names, even though there could be. (Adding some % directives should be easy: just increase the size of the dictionary and add appropriate directives and strbuf objects.)

If you're handling a normal two-heads-and-a-base merge, there's only one GITHEAD_* variable and it's the other head, but if you are handling an octopus merge, you are out of luck.

torek
  • 448,244
  • 59
  • 642
  • 775
1

EDIT: AS @Daniel Martin pointed in a comment, the script here written partially relies on a side effect that git stash had until git 2.22-rc0. As such, using it as is with versions after 2.22 might not behave as intended. Some changes might be necessary in that case (feel free to modify as required and reshare the edited version either editing this question or adding a new answer).


I have been handling this task for a few weeks and I have found a few corner cases that are not correctly handled by this script. Searching around and taking a look at Git sources I have arrived at this script:

#!/bin/bash 
# ${1} is the base common file 
# ${2} is the file as modified by the base branch, and where the results must be 
# ${3} is the file as modified by the incoming branch / stash
# ${4} is the path of the file being merged

# does not support octopus merge.

branch=$(git symbolic-ref --short HEAD || git name-rev --name-only HEAD)
githeadNum=$(env | grep GITHEAD | wc -l) # pathces do not create any GITHEAD var


# get a GITHEAD with message 'Stashed changes'
# if we are applying a stash, it must exist.
# see https://github.com/git/git/blob/2d08f3deb9feb73dc8d21d75bfd367839fc1322c/git-stash.sh#L616
stash=$(env | grep -E 'GITHEAD_[0-9a-f]{40}\b*=\b*Stashed changes' | wc -l) # e.g. GITHEAD_<sha>=Stashed changes 

if [ "$stash" -eq "1" ]
then
    # we are in a stash 
else
    if [ "$githeadNum" -eq "0" ]
    then
        # we are in a patch
    else
        # normal merge
 
        # only one GITHEAD, merging
        gitHeadName=$(env | grep GITHEAD)
        source="${gitHeadName##*=}"
    fi
fi

exit 1 

I recover the destination branch as

$(git symbolic-ref --short HEAD || git name-rev --name-only HEAD)

I found the first part failing for rebase, so in case of a rebase the second part should work.

For the source, I parse the GITHEAD env variable but check for patches or stash application, since I want to handle those cases differently (and patches do not leave GITHEAD)

For stash apply:

stash=$(env | grep -E 'GITHEAD_[0-9a-f]{40}\b*=\b*Stashed changes' | wc -l)

$stash will be 1 in case we are in a stash, 0 otherwhise

For patches I count number of GITHEAD, which must be zero:

githeadNum=$(env | grep GITHEAD | wc -l)
bracco23
  • 2,181
  • 10
  • 28
  • I do not see this behavior around `stash apply` with my git (version 2.32.0); instead, when doing a "git stash apply" that isn't applied to the same tree I did the "git stash" on, I see the behavior you label as "patch": No `GITHEAD` env variables. – Daniel Martin Sep 21 '21 at 13:53
  • When I developed this I had restrictions on the version of git. IIRC I was working with git 2.17 or around that. Seems like from git 2.22-rc0 (see commit https://github.com/git/git/commit/8a0fc8d19dff0c0ed09ce6656e353daf06c21892) stash has been converted to built-in and this behaviour has changed. I'll add a disclaimer for this. – bracco23 Sep 22 '21 at 14:09
  • @DanielMartin I added a disclaimer to the answer. Taking a second look at the script those changes shouldn't be breaking, but it is good to have it in mind if someone else needs it. Thanks. – bracco23 Sep 22 '21 at 14:15