Как я могу заставить свой C-код автоматически распечатывать хеш версии Git?

Есть ли простой способ написать C-код, который может получить доступ к хеш-версии версии Git?

Я написал программное обеспечение на C для сбора научных данных в лабораторных условиях. Мой код записывает данные, которые он собирает в файле .yaml для последующего анализа. Мои эксперименты меняются изо дня в день, и мне часто приходится изменять код. Чтобы отслеживать изменения, я использую репозиторий Git.

Я хотел бы иметь возможность включать хэш-версию Git в качестве комментария в мои файлы данных .yaml. Таким образом, я мог бы посмотреть файл .yaml и точно знать, какой код использовался для генерации данных, отображаемых в этом файле. Есть ли простой способ сделать это автоматически?

Ответ 1

В моей программе я держу номер версии 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 script, который выглядит следующим образом:

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.h и make_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, поэтому буду приветствовать комментарии, если есть лучший способ сделать это.

Ответ 2

Если вы используете сборку на основе make, вы можете поместить ее в Makefile:

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

(см. man git, опишите, как работают переключатели)

затем добавьте это к своим CFLAGS:

-DVERSION=\"$(GIT_VERSION)\"

Тогда вы можете просто ссылаться на версию прямо в программе, как если бы она была #define:

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

По умолчанию это просто печатает сокращенный идентификатор git commit, но при желании вы можете пометить определенные выпуски чем-то вроде:

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

тогда он распечатает:

Version: v1.1-2-g766d

что означает, 2 коммитов после v1.1, с идентификатором git commit, начинающимся с "766d".

Если в вашем дереве есть незафиксированные изменения, к нему добавится "-dirty".

Нет сканирования зависимостей, поэтому вы должны сделать явный make clean, чтобы принудительно обновить версию. Однако это можно решить.

Преимущества в том, что он прост и не требует никаких дополнительных сборочных зависимостей, таких как perl или awk. Я использовал этот подход с GNU automake и со сборками Android NDK.

Ответ 3

В итоге я использовал что-то очень похожее на @Kinopiko, но я использовал awk вместо perl. Это полезно, если вы застряли на машинах с Windows, у которых awk установлен по характеру mingw, но не perl. Вот как это работает.

В моем файле makefile есть строка, которая вызывает git, дату и awk для создания файла c:

$(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_ */

Остальная часть моего кода теперь может получить доступ к времени сборки и хешу git, просто включив заголовок version.h. Чтобы обернуть все это, я скажу git игнорировать version.c, добавив строку в мой файл .gitignore. Таким образом, git не постоянно дает мне слияние конфликтов. Надеюсь, это поможет!

Ответ 4

Ваша программа может отключиться до git describe, либо во время выполнения, либо как часть процесса сборки.

Ответ 5

Есть две вещи, которые вы можете сделать:

  • Вы можете сделать Git, чтобы вставить в файл некоторую информацию о версии в файле.

    Более простой способ - использовать ident attribute, что означает помещение (например)

    *.yaml    ident
    

    в файле .gitattributes и $Id$ в соответствующем месте. Он будет автоматически расшифрован до идентификатора SHA-1 содержимого файла (blob id): это НЕ версия файла или последняя фиксация.

    Git поддерживает ключевое слово $Id $таким образом, чтобы избежать касания файлов, которые не были изменены во время переключения ветвления, перематывания ветки и т.д. Если вы действительно хотите, чтобы Git помещал в файл идентификатор фиксации (версии) или описание, вы можете (ab) использовать атрибут filter, используя фильтр clean/smudge, чтобы развернуть ключевое слово (например, $Revision $) при проверке и очистить его для фиксации.

  • Вы можете сделать процесс сборки для этого, как это делает ядро ​​Linux или Git.

    Взгляните на GIT-VERSION-GEN script и его использование в Git Makefile или, например, как этот Makefile включает информацию о версии во время генерации/конфигурации файла gitweb/gitweb.cgi.

    GIT -VERSION-GEN использует Git описать, чтобы сгенерировать описание версии. Он должен работать лучше, чем вы отмечаете (используя подписанные/аннотированные теги) релизы/этапы вашего проекта.

Ответ 6

Когда мне нужно это сделать, я использую tag, например RELEASE_1_23. Я могу решить, что такое тег, не зная SHA-1. Затем я фиксирую тег. Вы можете сохранить этот тег в своей программе в любом случае, который вам нравится.

Ответ 7

Основываясь на ответе 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 - [email protected] || echo '$(GIT_VERSION) $(GIT_DATE)' > [email protected]
version.h: version~
    @touch [email protected]
    @echo Git version $(GIT_VERSION) $(GIT_DATE)

Ответ 8

Что вам нужно сделать, так это создать заголовочный файл (например, используя эхо из строки 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

Возможно, вам нужно немного сыграть с котировками и обратными косыми чертами, чтобы получить их для компиляции, но вы получите эту идею.

Ответ 9

Я также использую git для отслеживания изменений в моем научном коде. я не хотел использовать внешнюю программу, потому что она ограничивает переносимость кода (если кто-то захочет внести изменения в MSVS, например).

Мое решение состояло в том, чтобы использовать только основную ветвь для вычислений и сделать вывод времени сборки с использованием макросов препроцессора __DATE__ и __TIME__. таким образом я могу проверить его с журналом git и посмотреть, какую версию я использую. ref: http://gcc.gnu.org/onlinedocs/cpp/Standard-Predefined-Macros.html

еще один элегантный способ решить эту проблему - включить git в исполняемый файл. сделать объектный файл из журнала git и включить его в код. на этот раз единственная внешняя программа, которую вы используете, является objcopy, но есть меньше кодирования. ref: http://www.linuxjournal.com/content/embedding-file-executable-aka-hello-world-version-5967 и Вставить данные в программу на С++

Ответ 10

Еще одна вариация, основанная на 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 будет содержать одну строку, содержащую static const char * GIT_COMMIT_SHA = "";

От https://gist.github.com/larytet/898ec8814dd6b3ceee65532a9916d406

Ответ 11

Вы можете увидеть, как я сделал это для memcached в исходный коммит.

В принципе, иногда указывайте, и убедитесь, что вещь, которую вы доставляете, поступает от make dist или аналогичного.

Ответ 12

Это решение для проекта CMake, которое работает для Windows и Linux без необходимости установки каких-либо других программ (например, языков сценариев).

Хэш git записывается в файл .h скриптом, который является скриптом bash при компиляции в Linux или пакетным скриптом Windows при компиляции в Windows, а условие if в CMakeLists.txt выбирает скрипт, соответствующий платформе код компилируется на.

Следующие 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 может быть распечатан на терминале с помощью std::cout << "Software version: " << kGitHash << std::endl; или аналогичным образом записан в файл yaml (или любой другой).