1

In the following graph, time flows from bottom to top:

| |
| |
3 | this is a commit on master, myfile.txt was modified by this commit
| |
| 2 this is a commit on branch mybranch, myfile.txt was modified by this commit
| |
|/
|
1   this is a commit on master, myfile.txt was modified by this commit

Now I need to merge mybranch into master, if I understood Git properly, myfile.txt now needs a file-level merge (see https://git-scm.com/docs/gitattributes#_performing_a_three_way_merge) because myfile.txt was modified in both the branches involved in the merge.

Regarding a custom merge driver, the help says that:

The merge.*.driver variable's value is used to construct a command to run to merge ancestor’s version (%O), current version (%A) and the other branches' version (%B). These three tokens are replaced with the names of temporary files that hold the contents of these versions when the command line is built.

My comprehension is that %O will be the name of a temporary file with the content of myfile.txt at commit 1, %A will be the name of a temporary file with the content of myfile.txt at commit 3 and %B will be the name of a temporary file with the content of at myfile.txt at commit 2.

Does the name of the temporary files embed in some way the commit id (the SHA-1)?

Is there any way to get the commit id of 1, 2 and 3?

Alessandro Jacopson
  • 18,047
  • 15
  • 98
  • 153

2 Answers2

1

My [understanding] is that %O will be the name of a temporary file with the content of myfile.txt at commit 1, %A will be the name of a temporary file with the content of myfile.txt at commit 3 and %B will be the name of a temporary file with the content of at myfile.txt at commit 2.

That's correct.

Does the name of the temporary files embed in some way the commit id (the SHA-1)?

No.

Is there any way to get the commit id of 1, 2 and 3?

Not reliably, no. You can usually get pretty close by finding the actual git merge command and parsing its arguments to find the name or hash ID supplied for commit 3. Commit 2 is usually HEAD. Commit 1 is thus the result of git merge-base -all HEAD <3>, as long as that's just one hash ID.

The problem here comes in when there is more than one merge base. In this case, the ID of all three commits can depend on whether this is a recursive merge. If the merge was run with git merge -s resolve, the merge base is one of the N merge bases, selected at (apparent) random. If the merge was run with git merge -s recursive (or the default), the merge will run multiple times, once for each merge base, then again with a commit made by merging the merge bases, possibly recursively.

Your best bet is not to need this information, since reconstructing it is messy and unreliable.

torek
  • 448,244
  • 59
  • 642
  • 775
  • Thank you. Usually my merge command, being `master` my current branchs, is `git merge mybranch --no-commit`. – Alessandro Jacopson Oct 18 '19 at 19:19
  • That one invokes the default recursive strategy. I should add that if you have a merge driver, you can control whether your merge driver is used for any inner (recursive) merges, or not. But even if it's not—if you have the default driver do any inner merges—you now have the problem that the merge base is the resulting merge of the merged merge bases, which isn't in the commit graph at all! – torek Oct 18 '19 at 20:20
1

Is there any way to get the commit id of 1, 2 and 3?

Yes, but not necessarily always.

$ git init
$ touch .gitignore
$ git add .gitignore
$ git ci -m .gitignore
$ echo 1 > myfile.txt
$ git add myfile.txt
$ git ci -m 'this is a commit on master, myfile.txt was modified by this commit'
$ git co -b mybranch
$ echo 2 >> myfile.txt 
$ git ci -am "this is a commit on branch mybranch, myfile.txt was modified by this commit"
$ git co master 
$ echo 3 >> myfile.txt 
$ git ci -am "this is a commit on master, myfile.txt was modified by this commit"
$ git log --graph --all --oneline
* 2d0b94b (HEAD -> master) this is a commit on master, myfile.txt was modified by this commit
| * d223e74 (mybranch) this is a commit on branch mybranch, myfile.txt was modified by this commit
|/  
* 0bf67f0 this is a commit on master, myfile.txt was modified by this commit
* b6c7c02 .gitignore
$ git merge mybranch 
Auto-merging myfile.txt
CONFLICT (content): Merge conflict in myfile.txt
Automatic merge failed; fix conflicts and then commit the result.
$

At this point you can get use git ls-files -u to easily get the blobs for 1, 2 and 3. And from the blob you can find the corresponding commit:

git ls-files -u
100644 d00491fd7e5bb6fa28c517a0bb32b8b506539d4d 1       myfile.txt
100644 2b2f2e1b9261c50c3816610eb3eb140fabf1745a 2       myfile.txt
100644 1191247b6d9a206f6ba3d8ac79e26d041dd86941 3       myfile.txt
$ ./what-commit-contains-file-blob.sh myfile.txt d00491fd7e5bb6fa28c517a0bb32b8b506539d4d
0bf67f0 this is a commit on master, myfile.txt was modified by this commit
$ ./what-commit-contains-file-blob.sh myfile.txt 2b2f2e1b9261c50c3816610eb3eb140fabf1745a
2d0b94b (HEAD -> master) this is a commit on master, myfile.txt was modified by this commit
$ ./what-commit-contains-file-blob.sh myfile.txt 1191247b6d9a206f6ba3d8ac79e26d041dd86941
d223e74 (mybranch) this is a commit on branch mybranch, myfile.txt was modified by this commit
$ cat what-commit-contains-file-blob.sh 
#!/bin/sh

# https://stackoverflow.com/questions/223678/which-commit-has-this-blob
# https://stackoverflow.com/a/32611564/23118

file=${1:?missing file name argument}
blob=${2:?missing blob argument}

git log --all --pretty=format:%H -- $file \
        | xargs -n1 -I% sh -c "git ls-tree % -- $file | grep -q $blob && echo %" \
        | xargs -n 1 git log -n 1 --oneline
$

So for this simple example repo this solution worked perfectly, but a blob can be part of multiple commits so I am not sure if this always will give a unique result.

hlovdal
  • 26,565
  • 10
  • 94
  • 165