Alex
Alex

Reputation: 103

GIT: Get hash of unmerged files

I'm writing a tool which helps with resolving git merge conflicts. When a merge conflict happens via git pull (or fetch + merge) there will be a special ref called MERGE_HEAD created and I can use it to retrieve the commit message for a particular file: git log HEAD..MERGE_HEAD somefile

Now, if I previously git stashed something and then git stash pop gives me a merge conflict, were' not merging anything from the git point of view, so there's no MERGE_HEAD. And this is why I'm asking for help:

How to get the changelog of an unmerged file to present to the user and offer them to choose which one to keep?

git ls-files -u lists the files along with some hashes but they are somewhat weird and I can't make any use of them with any git command?

Upvotes: 3

Views: 167

Answers (3)

Alex
Alex

Reputation: 103

The above answers were helpful to understand git better, however I only needed to get the latest commit message of a file in, this turned out to be pretty easy:

git log -n 1 HEAD file.

Thank you.

Upvotes: 1

LeGEC
LeGEC

Reputation: 51780

@Rodrigo gave a correct way to list the different blobs (:{1,2,3}:file), here is some more context on how git stash works :

git stash actually does create a commit, and stores this commit in a reference named refs/stash.

You can view this by inspecting this ref's history :

$ git log --oneline --graph stash
# or equivalently :
$ git log --oneline --graph stash@{0}
*   f8ac2b6 (refs/stash) WIP on sidetrack: e330a4e edited on sidetrack
|\
| * e20e1a8 index on sidetrack: e330a4e edited on sidetrack
|/
* e330a4e (HEAD -> sidetrack) edited on sidetrack
* 78db1ef add thefile.txt
...

(side note : if you have several stashes, they are actually listed in the reflog of this refs/stash reference : git reflog stash, and you can access older versions of a ref using stash@{1}, stash@{2}, etc ... )

Applying a stash is roughly equivalent to running git cherry-pick stash.


So, if you have a conflict on file that/file.txt when running git stash pop or git stash apply, you will find :

  • ours version in HEAD (as usual) : git show HEAD:that/file.txt
  • theirs version in stash or stash@{0} : git show stash:that/file.txt
  • base version in the parent of stash : git show stash^:that/file.txt

As you can see above the stash is actually a merge commit, it stores the worktree version of the files (refs/stash), and the indexed version of the files (its second parent refs/stash^2), which can be different if you had anything added and not committed yet.

If you apply git stash apply --index or git stash pop --index (and git stash tries to restore the content of the index), you have to look closer at what step triggered the conflict : restoring the worktree or the index.

Upvotes: 1

rodrigo
rodrigo

Reputation: 98338

When you have a conflict like this you do not have a commit to refer to, because... well a stash-pop does not create a commit in the first place.

But anyway, when a file is in conflict you can get the different versions of the file quite easily, if you know the right spell. From man gitrevisions:

  :[<n>:]<path>, e.g. :0:README, :README
      A colon, optionally followed by a stage number (0 to 3) and a colon,
      followed by a path, names a blob object in the index at the given 
      path. A missing stage number (and the colon that follows it) names
      a stage 0 entry.
      During a merge, stage 1 is the common ancestor, stage 2 is the target
      branch’s version (typically the current branch), and stage 3 is the
      version from the branch which is being merged.

In the case of a stash-pop conflict, you can get the original version with git show :1:file, the stashed file is git show :2:file, and the working dir version is git show :3:file.

About the hashes you get with git ls-files -u, they are something like:

100644 a2d3a1c45e24173a1531524034bca8bc29f39dc0 1   file
100644 6a70e21770bce9fbf4440755ff20f8f00534861b 2   file
100644 8749ddd3de43774220449a6033de9c052356788b 3   file

These are the hashes of the different stages of that file, the blob itself, not of any commit, that's why most git commands won't work on it. You can get their contents with git cat-file:

$ git cat-file -t a2d3a1c45e24173a1531524034bca8bc29f39dc0
blob
$ git cat-file -p a2d3a1c45e24173a1531524034bca8bc29f39dc0
<actual contents of the file>

Upvotes: 6

Related Questions