I would like to create Git hook(s) that will populate the commit id of the commit I am about to make into a file (basically variable substitution) in my source code. Is this possible with Git? Or is the fact that by resolving the variable to the git id, I am going to be changing the sha 1, thereby winding up with a "chicken or the egg" problem.
-
4It is not possible to have it in a committed set, due to sha1 being a checksum. It may be possible to achieve thru squashing commits. What are you trying to do exactly? Are you sure you are addressing the right problem on a bigger scale? – Ruslan Osipov May 13 '13 at 14:21
-
I thought that annotations to not alter the SHA1, so they would make for a perfect candidate for this kind of information. That is: if my assumption is true. – Grimace of Despair May 13 '13 at 15:16
-
1The reason this would be desirable: What I have stored in git is not code that gets compiled but rather ETL job configuration that gets deployed to an ETL server. I want traceability between what is deployed on the ETL server and what's in Git. So when logged into the ETL server, I want to be able to look at a variable I have created called "git_id" which would contain the commit id associated with the version of the job that is deployed on the ETL server. I can of course achieve traceability in other ways but this would be the simplest if its possible. – BestPractices May 13 '13 at 17:24
-
(If its not possible, that's fine, but worth investigating.) – BestPractices May 13 '13 at 17:24
-
Yes, you can trace what is *deployed* because not everything that gets deployed has to be under revision control (i.e. not hashed). Put the revision ID in some file that Git doesn't care about. See my answer below for more details. – Adrian Ratnapala May 13 '13 at 19:09
-
1possible duplicate of [In Git, how can I write the current commit hash to a file in the same commit](http://stackoverflow.com/questions/3442874/in-git-how-can-i-write-the-current-commit-hash-to-a-file-in-the-same-commit) – mpoisot May 12 '15 at 20:52
-
@RuslanOsipov [There is about a ~63.21% chance that you work around that.](http://stackoverflow.com/a/235910/1172541) – PyRulez Jan 10 '16 at 03:08
9 Answers
The solution I have used for a similar situation is this:
- Put the string
$Id$
somewhere in the file you want to have identified (e.g.test.html
), probably within a comment or other non-functional section of the file where it won't cause issues. - In your
.gitattributes
, flag the file in question with theident
keyword (e.g.*.html ident
).
The result of this is that when git checkout
copies the file out of the object database into your working directory, it expands the $Id$
string to read $Id: <sha-1 of file>$
, and git add
reverses that transformation when you want to check it in, so the versions of that file in your object database only ever contain $Id$
, not the expanded forms.
That's a start, but unfortunately, finding the commit that contains a file with a specific hash is not so easy, and not necessarily one-to-one either. So, in addition, I also tag those files with the export-subst
attribute (e.g. *.html ident export-subst
in .gitattributes
), and add an additional string, like $Format:%ci$ ($Format:%h$)
somewhere in the file as well.
git checkout
and git add
don't affect these tags, though, so the versions in my repository always have exactly that string. In order to get those tags expanded, you have to use git archive
to create a tar-ball (or .zip) of a specific version of your project, which you then use to deploy that version - you won't be able to just copy the files, or make install
or whatever, since git archive
is the only thing that will expand those tags.
The two tags I gave as an example expand to YYYY-MM-DD HH:MM:SS +TZOFFSET (HASH)
, where the HASH
in this case is the actual commit hash, so it's more useful.
You can find other potentially usefull $Format:$
specifiers in the git log
help page under the --pretty-format
specifiers.

- 59,951
- 11
- 89
- 84
-
4Official documentation: [Keyword expansion](https://git-scm.com/book/en/v2/Customizing-Git-Git-Attributes#_keyword_expansion) – Simon Woodside Jun 11 '19 at 02:48
It's impossible to do what you want: the commit's SHA-1 hash is calculated over the whole repository snapshot including each member file, so there's the chicken and egg problem — to calculate the commit's hash you need to know the contents of all the files which comprise it.

- 51,517
- 14
- 93
- 176
-
1It isn't really true, you can add this file in gitignore and write current commit hash in it. – Dmitry Malugin Apr 17 '17 at 13:32
You can do this with the post-commit
hook. Here's an excerpt from the git-scm website
After the entire commit process is completed, the post-commit hook runs. It doesn’t take any parameters, but you can easily get the last commit by running git log -1 HEAD. Generally, this script is used for notification or something similar.
It would be a case of getting the output of git log -1 HEAD
, then using a tool like sed
to replace variables in your file. However, this modifies your working directory, and unless you're going to throw those changes away then you'd end up with a permanently modified working directory.
If you just want to use the current commit hash in a variable somewhere in your code, you could just execute git log -1 HEAD
or cat .git/HEAD
and store the output in your variable
If you only want the id (hash) like in the question title, you can use the --format
flag. git log -1 HEAD --format=%H

- 505
- 1
- 6
- 18

- 11,783
- 4
- 39
- 66
-
5Such a `post-commit` hook would modify the working directory. Every time you commit, you'd store the SHA1 of the **previous** commit, and then modify the working directory to contain the SHA1 of the last commit. – Andomar May 13 '13 at 14:22
-
2@Andomar The hook could potentially modify the working directory, but if the modification is to a file that is `.gitignored` (i.e. for build purposes or something, not to be tracked by `git`), this could still be useful. It might help to know a little more about the exact use case from the questioner, though... – twalberg May 13 '13 at 15:15
-
Also: please don't edit to change the meaning of a post. Add comments or post your own answer instead. – Edward Falk Jun 28 '16 at 23:27
You can create a filter which does substitution on files on commit and checkout. These are called "smudge" and "clean" filters and their operation is controlled through .gitattributes
. For example:
*.c filter=yourfilter
This tells git to run the yourfilter
filter for all .c
files. You then have to tell git what yourfilter
means:
git config --global filter.yourfilter.clean script1
git config --global filter.yourfilter.smudge script2
You'd then write a script (sed, Perl, Python, or anything) to replace an expression like $LastSha$
with $LastSha: <sha>$
on checkout ("smudge"). The other script reverses the expansion before commit ("clean".)
Search the Pro Git book for "Keyword Expansion" for a detailed example.

- 232,371
- 49
- 380
- 404
-
This isn't what I'm looking to do -- this would allow you to resolve a variable on checkout and unresolve on checkin. However I need to actually modify the file on checkin specifically so that it contains the hash id of the file. – BestPractices May 13 '13 at 16:55
-
2@BestPractices That simply can't be done, see [kostix's answer](http://stackoverflow.com/a/16525425/1290731) – jthill May 13 '13 at 16:59
-
@BestPractices Git itself provides absolute traceability, if `git fsck` is happy then the sha `git rev-parse HEAD` supplies to the filter is for that exact and entire history. – jthill May 13 '13 at 17:49
-
@jthill: yes I know thate git has absolute traceability (that's why we're using it :). The trick is that I need to make a note of the git id of the "code" I'm deploying so that I can trace _back_ to the version of the code in the git repo. So I'm trying to use the stratgy mentioned in the OP (if possible). If not, I can certainly set up a manual process. – BestPractices May 13 '13 at 17:59
OK, inspired by Jon Cairns' answer, I came up with this little snippet you could put in your Makefile.
version.h:
git log -n 1 --format=format:"#define GIT_COMMIT \"%h\"%n" HEAD > $@
It's not a completely general solution, but it could come in handy. I know a place or two where I'll be using it.

- 9,991
- 11
- 77
- 112
-
BTW: I leave enhancements and refinements to the user. In particular, you probably want a FORCE tag or something else to ensure that it gets rebuilt any time the commit id changes. – Edward Falk Jun 28 '16 at 23:32
-
1See the [Git pretty-formats Docs](https://git-scm.com/docs/pretty-formats) on how to build that format string. – luckydonald Jul 25 '17 at 07:20
I was looking for an answer to this question. The Commit Id is already written to a file you just have to know where to look. After making a commit on the master branch, you can find the commit hash at
./.git/refs/heads/master
So in our continuous delivery solution (it downloads the .git folder along with the source code) we can simply
cat ./.git/refs/heads/${BRANCH}
in order to associate the current commit hash with our build

- 41
- 1
As others have mentioned, you can't put the SHA-1 of a commit itself into the file during the same commit. This would be of limited use anyway since looking at two files you wouldn't immediately be able to tell which is newer.
That being said, there is in-fact a way to put version tracking information into committed files automatically. I did this for my current project (FrauBSD; a fork of FreeBSD that I'm working on).
I achieved this not by using a git-attributes filter. While git-attributes filters make it easy to achieve the opposite (put the information into the file(s) on checkout), what I wanted was to expand certain keywords at the time-of-commit so the data makes it into the repository (e.g., after a "git push origin master", github shows expanded values in the committed file(s)). Achieving the latter proved exceedingly difficult with a git-attributes filter precisely because a simple "git diff" will invoke filter.clean attribute and, as was the issue in my case, if you are putting date/time information into the expansion, having the value change every time you perform "git diff" is undesired and unacceptable.
So I have developed a pre-commit hook and a commit-msg hook that, acting together, solve the problem of how to (specifically in the FrauBSD case) replace the following in committed files:
$FrauBSD$
With something similar to the following prior to check-in (expanded values go upstream for others to checkout):
$FrauBSD: filepath YYYY-MM-DD HH:MM:ZZ GMTOFFSET committer $
When anyone is browsing the file on github or performs a checkout or merge of the file(s), the expanded information goes along for the ride.
NOTE: The expanded value won't ever change unless there is another (unrelated) change accompanying, respectively.
For example, see the following commit wherein I simply remove a trailing newline of a file. The commit contains both the removal of the trailing newline as well as a bump to the date/time in the $FrauBSD$ keyword:
https://github.com/freebsdfrau/FrauBSD/commit/060d943d86bb6a79726065aad397723a9c704ea4
To produce that commit, I did what most [git] developers are familiar with:
- vi LICENSE
- Shift-G # go to end of file
- dd # delete current line
- ZZ # save file and exit
- git diff # diff shows removal of trailing newline NOTE: diff does not show a change to $FrauBSD$ value [yet]
- git add LICENSE
- git diff # nothing (no unstaged changes)
- git diff --cached # diff shows removal of trailing newline NOTE: diff [still] does not show a change to $FrauBSD$ value
- git status # shows modified LICENSE
- git commit # $EDITOR comes up
- Ctrl-Z # put $EDITOR in background so we can investigate
- git diff --cached # diff [now] shows $FrauBSD$ update as well as removal of trailing newline
- fg # resume $EDITOR
- :q! # quit Editor without changes NOTE: Because you aborted the commit, $FrauBSD$ was reverted
- git diff --cached # diff [again] shows only trailing newline removal
- git commit # this time we won't abort
- BumpZZ # Insert "Bump", save and exit
- File is committed as-is
NOTE: Nothing needs to be done to the file post-commit
That is because I have the following files in my project:
- .git/hooks/pre-commit (symbolic link to ../../.hooks/pre-commit)
- .git/hooks/commit-msg (symbolic link to ../../.hooks/commit-msg)
- .hooks/pre-commit
- .hooks/commit-msg
- .filters/fraubsd-keywords
The initial revisions of which you can get here:
"Add hooks/filters for pre-commit smudging"
https://github.com/freebsdfrau/FrauBSD/commit/63fa0edf40fe8f5936673cb9f3e3ed0514d33673
NOTE: The filters are used by the hooks (not used in git-attributes).
And an update here:
https-//github.com/freebsdfrau/FrauBSD/commit/b0a0a6c7b2686db2e8cdfb7253aba7e4d7617432
Or you can view the head revisions here:
https-//github.com/freebsdfrau/FrauBSD/tree/master/.filters
https-//github.com/freebsdfrau/FrauBSD/tree/master/.hooks
NOTE: colon changed to - in above URLs so I can post more than 2 links (since reputation is low)
Enjoy, FreeBSDFrau

- 51
- 4
-
*"you can't put the SHA-1 of a commit itself into the file during the same commit"* You "can", in the sense that it is technically *possible*...if you're willing to wait a long while for a process to figure out how. What you presumably *mean* to say is: "it is not feasible with current hardware or algorithms (at least available to the public in 2015-2019) to do this in a reasonable amount of time". – HostileFork says dont trust SE Apr 15 '19 at 16:42
The key here is to put your revision ID into some file that Git doesn't care about. Here is a fragment from one of my projects:
. . . AssemblyCS="Properties/AssemblyInfo.cs" rev="$(git log -n 1 --date=short --format=format:"rev.%ad.%h" HEAD)" sed "$AssemblyCS" . . .
This script is run as part of my build process (it could also be a post-commit hook). In this case Properties/AssemblyInfo.cs
is in .gitignore
whereas Properties/AssemblyInfo.cs.in
is under version control. The build uses the .cs
filewhich includes a revision ID that ends up in the deployed executable.

- 5,485
- 2
- 29
- 39
-
1We in fact do what you suggest here for our compiled code, but unfortunately this doesn't for our non compiled code. Basically we take whats in git and deploy it to the ETL server. There is nothing to compile or package. – BestPractices May 13 '13 at 22:06
-
1Compilation is not an essential part of this. Whatever files contains the commit ID can be `.gitignored` and generated as part of the deployment process. For example if the deployment step were just a "git pull" you could make it a `post-merge` hook. The same script could also write an MD5 manifest of the the deployed tree. Then given a manifest file you can (a) check the real tree matches and (b) know what revision it came from. – Adrian Ratnapala May 14 '13 at 04:37
I was looking for something similar, in that I wanted a unique variable that I could add to the end of resource files (like CSS/JS) in our front end, which would allow us to set very long cache times to reduce bandwidth and increase performance, but easily force them to be reloaded after any commit. Essentially file versioning but totally automated. I didn't care that it was the MOST recent, as long as it was unique, automated, and consistent across all of our app servers.
Our deployment script just uses 'git clone' to pull down a copy of the most recent code into our app servers, but we restrict access via .htaccess to those files and directories.
The /.git/
directory contains a file called ORIG_HEAD
which is updated after any merge (or any other dangerous operation) with the Commit ID of it's predecessor. Since we use git flow, this is perfect because it updates every time we push either a release
or a fix
to the master branch and deploy.
You can do this I'm assuming in any scripting language but in our case, PHP, I did it like this...
define("MY_VERSION",substr(file_get_contents(realpath(__DIR__.'/../.git/ORIG_HEAD')),0,3));
Your path would obviously have to be tweaked for your own purposes, but this results in a 3 char unique enough id for our purposes that gets appended to the end of our resource URL's now.
Hope that helps someone in the same situation.

- 4,211
- 2
- 37
- 53