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
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.
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
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.
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:
NOTE: Nothing needs to be done to the file post-commit
That is because I have the following files in my project:
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
The solution I have used for a similar situation is this:
$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..gitattributes
, flag the file in question with the ident
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.
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.