如何获得我的C代码以自动打印出其Git版本哈希?


83

有没有简单的方法可以编写可以访问其Git版本哈希的C代码?

我用C语言编写了用于在实验室环境中收集科学数据的软件。我的代码将它收集的数据记录在.yaml文件中,以供以后分析。我的实验每天都在变化,因此我经常不得不修改代码。为了跟踪修订,我使用git存储库。

我希望能够在我的.yaml数据文件中包含Git修订哈希作为注释。这样,我可以查看.yaml文件,确切地知道使用什么代码生成该文件中显示的数据。有没有简单的方法可以自动执行此操作?


1
使用预提交挂钩(请参阅book.git-scm.com/5_git_hooks.html)将是执行此操作的另一种方法。
Yktula'4

Answers:


39

在我的程序中,我将git版本号和构建日期保存在一个名为的单独文件中version.c,该文件如下所示:

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

还有一个头文件,如下所示:

#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 */

头文件和C文件都是由Perl脚本生成的,如下所示:

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_");

这里hash_to_c_file确实创造了所有的工作version.c,并version.hmake_date_time使一个字符串,如图所示。

在主程序中,我有一个例程

#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);
}

我对git并不了解,所以如果有更好的方法可以发表评论。


1
Perl脚本是构建脚本的一部分,该构建脚本是所有内容的“一步构建”。

12
就目前而言,这是很好的,但是请记住,它将报告分支上最新提交的哈希,而不是正在编译的代码的哈希。如果有未提交的更改,则这些更改将不明显。
Phil Miller

1
git diff默认情况下检查您的工作空间和索引之间的差异。您可能还需要尝试git diff --cached来查找索引和HEAD之间的差异
卡尔

6
所有那些'const char * name =“ value”;' 可以将结构合理地更改为'const char name [] =“ value”;',这将在32位计算机上每项节省4个字节,在64位计算机上每项节省8个字节。诚然,在现今GB的主内存中,这不是什么大问题,但这一切都有帮助。请注意,使用名称的代码都不需要更改。
乔纳森·勒夫勒

1
我按照您的建议更改了它们。我的程序大小const char []:319356字节(已剥离)。我的程序大小const char *:319324字节(已剥离)。因此,您的想法似乎并没有节省任何字节,而是将总数增加了32。我不知道为什么。在原始的“ version.c”中,有三个字符串,但是上面的答案中省略了一个。如果您查看第一个编辑,它仍然存在。

162

如果使用的是基于make的构建,则可以将其放入Makefile中:

GIT_VERSION := "$(shell git describe --abbrev=4 --dirty --always --tags)"

(有关开关的功能,请参见man git describe

然后将其添加到您的CFLAGS中:

-DVERSION=\"$(GIT_VERSION)\"

然后,您可以直接在程序中引用该版本,就像它是#define一样:

printf("Version: %s\n", VERSION);

默认情况下,这仅会打印一个简短的git commit id,但是您可以选择使用以下方式标记特定的发行版:

git tag -a v1.1 -m "Release v1.1"

然后它将打印出:

Version: v1.1-2-g766d

这意味着,在v1.1之后有2次提交,其git commit id以“ 766d”开头。

如果树中有未提交的更改,它将附加“ -dirty”。

没有依赖项扫描,因此您必须执行显式操作make clean以强制更新版本。但是,这可以解决

优点是它很简单,不需要像perl或awk这样的额外构建依赖项。我在GNU automake和Android NDK构建中使用了这种方法。


6
+1就个人而言,我更喜欢让makefile生成一个包含的头文件,#define GIT_VERSION ...而不是将其放在带有-D选项的命令行上;它消除了依赖性问题。另外,为什么要加双下划线?从技术上讲,这是保留的标识符。
Dan Moulding

8
每个都有自己的特点-正如我所说的优点是,它几乎没有活动部件,而且可以理解。我已经对其进行了编辑,以删除下划线。
ndyer

应该添加的是,如果您使用gengetopt,可以将其直接添加到Makefile中的gengetopt中:gengetopt --set-version = $(GIT_VERSION)
Trygve

1
第一条语句应带有引号GIT_VERSION := "$(shell git describe --abbrev=4 --dirty --always --tags)",没有引号不能正常工作。
亚伯汤姆

11

我最终使用了与@Kinopiko的答案非常相似的东西,但是我使用了awk而不是perl。如果您卡在安装了mingw而不是perl的awk的Windows机器上,这很有用。运作方式如下。

我的makefile中有一行,它调用git,date和awk来创建ac文件:

$(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 

每次编译代码时,awk命令都会生成一个version.c文件,如下所示:

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

我有一个看起来像这样的静态version.h文件:

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

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


#endif /* VERSION_H_ */

现在,我的其余代码可以通过仅包含version.h标头来访问构建时和git哈希。总结一下,我告诉git通过在.gitignore文件中添加一行来忽略version.c。这样git不会一直给我合并冲突。希望这可以帮助!



1
我认为这FORCE不是一个好主意,因为将永远不会满足makefile(每次使您执行新的标头)。取而代之的是,您可以仅在Formula中将依赖项添加到相关的git文件中 $(MyLibs)/version.c : .git/COMMIT_EDITMSG .git/HEAD COMMIT_EDITMSG每次提交时文件都会HEAD更改,每次浏览历史记录时也会更改,因此,每次相关时文件都会更新。
卡米尔·S·亚伦

9

您的程序可以git describe在运行时或在构建过程中作为外壳程序使用。


4
发件人git help describe:“显示可通过提交访问的最新标签” —这不是问题所要的。不过,我同意您的其余回答。为了正确起见,命令应为git rev-parse HEAD
迈克·马祖

5
@mikemgit describe是大多数其他项目使用的,因为它也包含人类可读的标签信息。如果您不完全在标签上,则它会附加在距离最近的标签以来的提交次数和缩写修订哈希之后。
bdonlan

7

您可以做两件事:

  • 您可以使Git为您嵌入一​​些版本信息。

    更简单的方法是使用ident attribute,这意味着放置(例如)

    *.yaml    ident
    

    .gitattributes文件中,并$Id$在适当的位置。它将自动扩展为文件内容的SHA-1标识符(blob id):这不是文件版本,也不是最后一次提交。

    Git确实以这种方式支持$ Id $关键字,以避免触摸在分支切换,倒回分支等过程中未更改的文件。如果您确实希望Git在文件中放置提交(版本)标识符或描述,则可以(滥用)filter属性,使用干净/污迹过滤器在结帐时扩展某些关键字(例如$ Revision $),并对其进行清理以进行提交。

  • 您可以像Linux内核或Git本身那样,通过构建过程来做到这一点。

    查看GIT-VERSION-GEN脚本及其在Git Makefile中的用法,例如,该Makefile在gitweb/gitweb.cgi文件的生成/配置过程中如何嵌入版本信息。

    GIT-VERSION-GEN使用git describe生成版本描述。您需要更好地工作(使用签名/带注释的标签)标记项目的版本/里程碑。


4

当我需要执行此操作时,可以使用标记,例如RELEASE_1_23。我可以在不知道SHA-1的情况下决定标签的名称。我提交然后标记。您可以随时将其存储在程序中。


4

基于njd27的答案,我使用的是带有依赖项扫描的版本,以及带有默认值的version.h文件,用于以不同方式构建代码。包含version.h的所有文件将被重建。

它还包括修订日期作为单独的定义。

# Get git commit version and date
GIT_VERSION := $(shell git --no-pager describe --tags --always --dirty)
GIT_DATE := $(firstword $(shell git --no-pager show --date=short --format="%ad" --name-only))

# recompile version.h dependants when GIT_VERSION changes, uses temporary file version~
.PHONY: force
version~: force
    @echo '$(GIT_VERSION) $(GIT_DATE)' | cmp -s - $@ || echo '$(GIT_VERSION) $(GIT_DATE)' > $@
version.h: version~
    @touch $@
    @echo Git version $(GIT_VERSION) $(GIT_DATE)

1
我假设您通过CFLAGS传入了GIT_VERSION和GIT_DATE,因此version.h可以使用它们。凉!
Jesse Chisholm

2

我还使用git跟踪科学代码中的更改。我不想使用外部程序,因为它限制了代码的可移植性(例如,如果有人想在MSVS上进行更改)。

我的解决方案是仅使用主分支进行计算,并使用预处理器宏__DATE__和来输出构建时间__TIME__。这样我就可以用git log检查它,看看我正在使用哪个版本。参考:http : //gcc.gnu.org/onlinedocs/cpp/Standard-Predefined-Macros.html

解决问题的另一种优雅方法是将git log包含到可执行文件中。用git log制作目标文件,并将其包含到代码中。这次,您使用的唯一外部程序是objcopy,但是编码较少。参考:http : //www.linuxjournal.com/content/embedding-file-executable-aka-hello-world-version-5967并将数据嵌入C ++程序


1
预处理程序宏的使用非常聪明!谢谢。
2011年

4
但是,如果我签出一个较旧的版本,然后进行编译,它将引导我进行错误的提交。
塞巴斯蒂安·马赫2012年

2

您需要做的是生成一个头文件(例如,使用来自cmd行的回显),如下所示:

#define GIT_HASH \
"098709a0b098c098d0e"

要生成它,请使用如下代码:

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

可能需要使用引号和反斜杠来进行编译,但是您明白了。


只是想知道,难道不是每次他这样做都会更改file.h,然后将更改提交到源,那么git hash是否会更改?
豪尔赫·以色列·佩尼亚

@布兰克..那也是我在想什么。但是bdonlan让程序在运行时询问的想法似乎可以解决此问题。
AndyL,2009年

6
好吧,这个文件必须在.gitignore下,并且在每次构建项目时都会生成。
Igor Zevaka 09年

或者,您可以包括此文件的基本版本并--assume-unchanged在其上设置标记(git update-index --assume-unchanged
Igor Zevaka 09年

1

基于Makefile和Shell的另一个变体

GIT_COMMIT_FILE=git_commit_filename.h

$(GIT_COMMIT_FILE): phony
    $(eval GIT_COMMIT_SHA=$(shell git describe --abbrev=6 --always 2>/dev/null || echo 'Error'))
    @echo SHA=$(GIT_COMMIT_SHA)
    echo -n "static const char *GIT_COMMIT_SHA = \"$(GIT_COMMIT_SHA)\";" > $(GIT_COMMIT_FILE)

文件git_commit_filename.h将以包含静态const char * GIT_COMMIT_SHA =“”的一行结尾。

来自https://gist.github.com/larytet/898ec8814dd6b3ceee65532a9916d406


1

这是适用于Windows和Linux的CMake项目的解决方案,无需安装任何其他程序(例如,脚本语言)。

git哈希通过脚本写入.h文件,在Linux上编译时为bash脚本,在Windows上编译时为Windows批处理脚本,而CMakeLists.txt中的if子句则选择与平台对应的脚本。代码已编译。

以下2个脚本与CMakeLists.txt保存在同一目录中:

get_git_hash.sh:

#!/bin/bash
hash=$(git describe --dirty --always --tags)
echo "#ifndef GITHASH_H" > include/my_project/githash.h
echo "#define GITHASH_H" >> include/my_project/githash.h
echo "const std::string kGitHash = \"$hash\";" >> include/my_project/githash.h
echo "#endif // GITHASH_H" >> include/my_project/githash.h

get_git_hash.cmd:

@echo off
FOR /F "tokens=* USEBACKQ" %%F IN (`git describe --dirty --always --tags`) DO (
SET var=%%F
)
ECHO #ifndef GITHASH_H > include/my_project/githash.h
ECHO #define GITHASH_H >> include/my_project/githash.h
ECHO const std::string kGitHash = "%var%"; >> include/my_project/githash.h
ECHO #endif // GITHASH_H >> include/my_project/githash.h

在CMakeLists.txt中添加以下行

if(WIN32)
  add_custom_target(
    run ALL
    WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
    COMMAND get_git_hash.cmd
  )
else()
  add_custom_target(
    run ALL
    WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
    COMMAND ./get_git_hash.sh
  )
endif()

include_directories(include)

在代码中,生成的文件包含在其中,#include <my_project/githash.h>并且git hash可以用std::cout << "Software version: " << kGitHash << std::endl;或打印到终端,或以类似方式写入yaml(或任何)文件。


By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.