In Git, how can I write the current commit hash to a file in the same commit

Viewed 62157

I'm trying to do a fancy stuff here with Git hooks, but I don't really know how to do it (or if it's possible).

What I need to do is: in every commit I want to take its hash and then update a file in the commit with this hash.

Any ideas?

7 Answers

It's impossible to write the current commit hash: if you manage to pre-calculate the future commit hash — it will change as soon as you modify any file.

However, there're three options:

  1. Use a script to increment 'commit id' and include it somewhere. Ugly
  2. .gitignore the file you're going to store the hash into. Not very handy
  3. In pre-commit, store the previous commit hash :) You don't modify/insert commits in 99.99% cases, so, this WILL work. In the worst case you still can identify the source revision.

I'm working on a hook script, will post it here 'when it's done', but still — earlier than Duke Nukem Forever is released :))

Update: code for .git/hooks/pre-commit:

#!/usr/bin/env bash
set -e

#=== 'prev-commit' solution by o_O Tync
#commit_hash=$(git rev-parse --verify HEAD)
commit=$(git log -1 --pretty="%H%n%ci") # hash \n date
commit_hash=$(echo "$commit" | head -1)
commit_date=$(echo "$commit" | head -2 | tail -1) # 2010-12-28 05:16:23 +0300

branch_name=$(git symbolic-ref -q HEAD) # http://stackoverflow.com/questions/1593051/#1593487
branch_name=${branch_name##refs/heads/}
branch_name=${branch_name:-HEAD} # 'HEAD' indicates detached HEAD situation

# Write it
echo -e "prev_commit='$commit_hash'\ndate='$commit_date'\nbranch='$branch'\n" > gitcommit.py

Now the only thing we need is a tool that converts prev_commit,branch pair to a real commit hash :)

I don't know whether this approach can tell merging commits apart. Will check it out soon

Think outside of the commit box!

pop this into the file hooks/post-checkout

#!/bin/sh
git describe --all --long > config/git-commit-version.txt

The version will be available everywhere you use it.

Let me explore why this is a challenging problem using the git internals. You can get the sha1 of the current commit by

#!/bin/bash
commit=$(git cat-file commit HEAD) #
sha1=($((printf "commit %s\0" $(echo "$commit" | wc -c); echo "$commit") | sha1sum))
echo ${sha1[0]}

Essentially you run a sha1 checksum on the message returned by git cat-file commit HEAD. Two things immediately jump out as a problem when you examine this message. One is the tree sha1 and the second is the commit time.

Now the commit time is easily taken care of by altering the message and guessing how long it takes to make a commit or scheduling to commit at a specific time. The true issue is the tree sha1, which you can get from git ls-tree $(git write-tree) | git mktree. Essentially you are doing a sha1 checksum on the message from ls-tree, which is a list of all the files and their sha1 checksum.

Therefore your commit sha1 checksum depends on your tree sha1 checksum, which directly depends on the files sha1 checksum, which completes the circle and depends on the commit sha1. Thus you have a circular problem with techniques available to myself.

With less secure checksums, it has been shown possible to write the checksum of the file into the file itself through brute force; however, I do not know of any work that accomplished that task with sha1. This is not impossible, but next to impossible with our current understanding (but who knows maybe in a couple years it will be trivial). However, still this is even harder to brute force since you have to write the (commit) checksum of a (tree) checksum of a (blob) checksum into the file.

Related