views:

512

answers:

7

Is there an easy way to write C code that can access its Git version hash?

I wrote software in C to collect scientific data in a laboratory setting. My code records the data it collects in a .yaml file for later analysis. My experiments change from day-to-day and I often have to modify the code. To keep track of revisions, I use a git repository.

I would like to be able to include the Git revision hash as a comment in my .yaml data files. That way, I could look at the .yaml file and know exactly what code was used to generate the data shown in that file. Is there an easy way to do this automatically?

+3  A: 

Your program can shell out to git describe, either at runtime or as part of the build process.

bdonlan
From `git help describe`: "Show the most recent tag that is reachable from a commit" -- this is not what the question asks for. I agree with the rest of your answer, though. In order to be correct, the command should be `git rev-parse HEAD`.
Mike Mazur
@mikem, `git describe` is what most other projects use, because it includes human-readable tag information as well. If you're not exactly on a tag, it appends on the number of commits since the nearest tag, and the abbreviated revision hash.
bdonlan
A: 

What you need to do is to generate a header file (eg using echo from cmd line) something like this:

#define GIT_HASH \
"098709a0b098c098d0e"

To generate it use something like this:

echo #define GIT_HASH \ > file.h
echo " > file.h
echo git status <whatever cmd to get the hash> > file.h
echo " > file.h

Might need to play with the quotes and backslashes a bit to get it to compile, but you get the idea.

Igor Zevaka
Just wondering, wouldn't each time he does that and therefore changes file.h, and then commits the changes to the source, the git hash would change?
Jorge Israel Peña
@Blaenk.. thats what I was thinking too. But bdonlan's idea of having the program ask at runtime seems to get around this problem.
AndyL
Well, this file would have to be under .gitignore and generated every time you build the project.
Igor Zevaka
Alternatively you can include a basic version of this file and set `--assume-unchanged` flag on it (`git update-index --assume-unchanged`)
Igor Zevaka
+3  A: 

When I need to do this, I use a tag, like RELEASE_1_23. I can decide what the tag can be without knowing the SHA-1. I commit then tag. You can store that tag in your program anyway that you like.

brian d foy
Do the simplest thing that could possibly work.
Telemachus
A: 

You can see how I did it for memcached in the original commit.

Basically, tag occasionally, and make sure the thing you deliver comes from make dist or similar.

Dustin
+9  A: 

In my program, I hold the git version number and the date of the build in a separate file, called version.c, which looks like this:

#include "version.h"
const char * build_date = "2009-11-10 11:09";
const char * build_git_sha = "6b54ea36e92d4907aba8b3fade7f2d58a921b6cd";

There is also a header file, which looks like this:

#ifndef VERSION_H
#define VERSION_H
extern const char * build_date; /* 2009-11-10 11:09 */
extern const char * build_git_sha; /* 6b54ea36e92d4907aba8b3fade7f2d58a921b6cd */
#endif /* VERSION_H */

Both the header file and the C file are generated by a Perl script which looks like this:

my $git_sha = `git rev-parse HEAD`;
$git_sha =~ s/\s+//g;
# This contains all the build variables.
my %build;
$build{date} = make_date_time ();
$build{git_sha} = $git_sha;

hash_to_c_file ("version.c", \%build, "build_");

Here hash_to_c_file does all the work of creating version.c and version.h and make_date_time makes a string as shown.

In the main program, I have a routine

#include "version.h"

// The name of this program.
const char * program_name = "magikruiser";
// The version of this program.
const char * version = "0.010";

/* Print an ID stamp for the program. */

static void _program_id_stamp (FILE * output)
{
    fprintf (output, "%s / %s / %s / %s\n",
             program_name, version,
             build_date, build_git_sha);
}

I'm not that knowledgeable about git, so I'd welcome comments if there is a better way to do this.

Kinopiko
As long as there is a way to have the Perl script automatically run or you can always remember to run it, this looks like a really good/sophisticated answer.
Matthew
The Perl script is part of the build script, which is a "one step build" for everything.
Kinopiko
This is good as far as it goes, but do keep in mind that it will report the hash of the latest commit on the branch, not the hash of the code being compiled. If there are uncommitted changes, those will not be apparent.
Novelocrat
I use `git diff` and check the result for empty output. Do you have a better suggestion?
Kinopiko
git diff by default checks for differences between your workspace and the index. You may also want to try git diff --cached for differences between the the index and HEAD
Karl
All those 'const char *name = "value";' constructs could sensibly be changed to 'const char name[] = "value";', which saves 4 bytes per item on a 32-bit machine and 8 bytes per item on a 64-bit machine. Granted, in these days of GB of main memory, that's not a big problem, but it all helps. Note that none of the code using the names needs to change.
Jonathan Leffler
I changed them as you suggest. The size of my program with `const char []`: 319356 bytes (stripped). The size of my program with `const char *`: 319324 bytes (stripped). So your idea doesn't seem to save any bytes, but increase the total number by 32. I have no idea why. In the original "version.c" there are three strings, but one was omitted from the above answer. If you look at the first edit it is still there.
Kinopiko
Oh sorry it isn't there actually. But anyway there are three const char * strings.
Kinopiko
+2  A: 

There are two things that you can do:

  • You can make Git to embed some version information in the file for you.

    The simpler way is to use ident attribute, which means putting (for example)

    *.yaml    ident
    

    in .gitattributes file, and $Id$ in the appropriate place. It would be automatically expanded to SHA-1 identifier of the contents of the file (blob id): this is NOT file version, or the last commit.

    Git does support $Id$ keyword in this way to avoid touching files which were not changed during branch switching, rewinding branch etc. If you really want Git to put commit (version) identifier or description in the file, you can (ab)use filter attribute, using clean/ smudge filter to expand some keyword (e.g. $Revision$) on checkout, and clean it up for commit.

  • You can make build process to do that for you, like Linux kernel or Git itself does.

    Take a look at GIT-VERSION-GEN script and its use in Git Makefile, or for example how this Makefile embeds version information during generation / configuration of gitweb/gitweb.cgi file.

    GIT-VERSION-GEN uses git describe to generate version description. It needs to work better that you tag (using signed / annotated tags) releases / milestones of your project.

Jakub Narębski
+2  A: 

I ended up using something very similar to @Kinopiko's answer, but I used awk instead of perl. This is useful if your stuck on windows machines which have awk installed by nature of mingw, but not perl. Here's how it works.

My makefile has a line in it that invokes git, date, and awk to create a c file:

$(MyLibs)/version.c: FORCE 
    $(GIT) rev-parse HEAD | awk ' BEGIN {print "#include \"version.h\""} {print "const char * build_git_sha = \"" $$0"\";"} END {}' > $(MyLibs)/version.c
    date | awk 'BEGIN {} {print "const char * build_git_time = \""$$0"\";"} END {} ' >> $(MyLibs)/version.c

Everytime I compile my code, the awk command generates a version.c file that looks like this:

/* version.c */
#include "version.h"
const char * build_git_sha = "ac5bffc90f0034df9e091a7b3aa12d150df26a0e";
const char * build_git_time = "Thu Dec  3 18:03:58 EST 2009";

I have a static version.h file that looks like this:

/*version.h*/
#ifndef VERSION_H_
#define VERSION_H_

extern const char * build_git_time;
extern const char * build_git_sha;


#endif /* VERSION_H_ */

The rest of my code can now access the build-time and the git hash by simply including the version.h header. To wrap it all up, I tell git to ignore version.c by adding a line to my .gitignore file. This way git isn't constantly giving me merge conflicts. Hope this helps!

AndyL