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.
- 5It 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– Ruslan Osipov2013-05-13 14:21:00 +00:00Commented May 13, 2013 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– Grimace of Despair2013-05-13 15:16:53 +00:00Commented May 13, 2013 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– BestPractices2013-05-13 17:24:04 +00:00Commented May 13, 2013 at 17:24
- (If its not possible, that's fine, but worth investigating.)BestPractices– BestPractices2013-05-13 17:24:58 +00:00Commented May 13, 2013 at 17:24
- 1possible duplicate of In Git, how can I write the current commit hash to a file in the same commitmpoisot– mpoisot2015-05-12 20:52:17 +00:00Commented May 12, 2015 at 20:52
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 theidentkeyword (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.
1 Comment
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.
1 Comment
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
4 Comments
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..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...git describe better suited than finding only the last commit ID.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.
4 Comments
git fsck is happy then the sha git rev-parse HEAD supplies to the filter is for that exact and entire history.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
1 Comment
HEAD of a ${BRANCH}.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.
2 Comments
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
1 Comment
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 .csfilewhich includes a revision ID that ends up in the deployed executable.
2 Comments
.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.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.