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

84

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

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

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

ЭндиЛ
источник
1
Использование хуков предварительной фиксации (см. Book.git-scm.com/5_git_hooks.html ) было бы другим способом сделать это.
Yktula

Ответы:

39

В моей программе я храню номер версии git и дату сборки в отдельном файле с именем version.c, который выглядит так:

Также есть заголовочный файл, который выглядит так:

И заголовочный файл, и файл 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.hи make_date_timeделает строку , как показано на рисунке.

В основной программе у меня рутина

Я не очень разбираюсь в git, поэтому буду приветствовать комментарии, если есть лучший способ сделать это.

Стефан Маевский
источник
1
Сценарий Perl является частью сценария сборки, который представляет собой «сборку за один шаг» для всего.
12
Насколько это хорошо, но имейте в виду, что он будет сообщать хэш последней фиксации в ветке, а не хеш компилируемого кода. Если есть незафиксированные изменения, они не будут видны.
Фил Миллер
1
git diff по умолчанию проверяет различия между вашей рабочей областью и индексом. Вы также можете попробовать git diff --cached, чтобы узнать о различиях между индексом и HEAD
Карл
6
Все эти 'const char * name = "value";' конструкции можно разумно изменить на 'const char name [] = "value";', что экономит 4 байта на элемент на 32-битной машине и 8 байтов на элемент на 64-битной машине. Конечно, в наши дни с ГБ оперативной памяти это не большая проблема, но все помогает. Обратите внимание, что ни один код, использующий имена, менять не нужно.
Джонатан Леффлер,
1
Я изменил их, как вы предлагаете. Размер моей программы const char []: 319356 байт (без вырезок). Размер моей программы const char *: 319324 байта (без вырезок). Итак, ваша идея, похоже, не сохраняет никаких байтов, а увеличивает общее количество на 32. Я не знаю почему. В исходном файле "version.c" есть три строки, но одна из приведенных выше ответов не указана. Если вы посмотрите на первую правку, она все еще там.
163

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

(См. Man git, чтобы узнать, что делают переключатели)

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

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

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

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

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

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

Сканирования зависимостей нет, поэтому вам нужно явно make cleanвыполнить обновление версии. Этот можно решить .

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

ndyer
источник
6
+1 Лично я предпочитаю, чтобы make-файл генерировал заголовочный файл, который содержит, #define GIT_VERSION ...а не помещал его в командную строку с -Dопцией; это устраняет проблему зависимости. Кроме того, почему двойное подчеркивание? Технически это зарезервированный идентификатор.
Дэн Молдинг
8
У каждого свое - как я сказал, преимущества в том, что у него мало движущихся частей, и они понятны. Я отредактировал его, чтобы убрать подчеркивание.
ndyer
Следует добавить, что если вы используете gengetopt, его можно добавить прямо в gengetopt в Makefile: gengetopt --set-version = $ (GIT_VERSION)
Trygve
1
Первая инструкция должна быть в кавычках GIT_VERSION := "$(shell git describe --abbrev=4 --dirty --always --tags)", без кавычек не работает.
Абель Том
11

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

В моем make-файле есть строка, которая вызывает git, date и awk для создания файла ac:

Каждый раз, когда я компилирую свой код, команда awk генерирует файл version.c, который выглядит следующим образом:

У меня есть статический файл version.h, который выглядит так:

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

ЭндиЛ
источник
Дополнение ... это будет работать в Matlab: mathworks.com/matlabcentral/fileexchange/32864-get-git-info
AndyL
1
Я не думаю, что FORCEэто хорошая идея, поскольку make-файл никогда не будет удовлетворен (каждый раз, когда вы создаете новый заголовок). Вместо этого вы можете просто добавить зависимость для соответствующих файлов git в формулу $(MyLibs)/version.c : .git/COMMIT_EDITMSG .git/HEAD . Файл COMMIT_EDITMSGизменяется каждый раз, когда вы делаете коммит, и HEADизменяется каждый раз, когда вы просматриваете историю, поэтому ваш файл обновляется каждый раз, когда это необходимо.
Камил С.
9

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

бдонлан
источник
4
From git help describe: «Показать самый последний тег, доступный после фиксации» - это не то, о чем спрашивает вопрос. Однако я согласен с остальной частью вашего ответа. Для верности команда должна быть git rev-parse HEAD.
Майк Мазур
5
@mikem git describe- это то , что используют большинство других проектов, потому что он также включает в себя удобочитаемую информацию тегов. Если вы не совсем на теге, он добавляет количество коммитов с момента ближайшего тега и сокращенный хэш редакции.
bdonlan
7

Вы можете сделать две вещи:

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

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

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

    Git поддерживает ключевое слово $ Id $ таким образом, чтобы не касаться файлов, которые не были изменены во время переключения ветвей, перемотки ветки и т. Д. Если вы действительно хотите, чтобы Git поместил идентификатор или описание фиксации (версии) в файл, вы можете (ab) использовать filterатрибут, используя фильтр clean / smudge, чтобы раскрыть некоторые ключевые слова (например, $ 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, будут перестроены.

Он также включает дату пересмотра как отдельное определение.

PTT
источник
1
Я предполагаю, что у вас есть GIT_VERSION и GIT_DATE, переданные через CFLAGS, поэтому version.h может их использовать. Круто!
Джесси Чизхолм
2

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

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

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

kirill_igum
источник
1
Макросы препроцессора очень умны! Спасибо.
AndyL
4
но если я проверю старую версию, а затем скомпилирую ее, это приведет меня к неправильной фиксации.
Себастьян Мах
2

Что вам нужно сделать, так это сгенерировать файл заголовка (например, используя echo из строки cmd) примерно так:

Для его создания используйте что-то вроде этого:

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

Игорь Зевака
источник
Просто интересно, не будет ли каждый раз, когда он это делает и, следовательно, изменяет file.h, а затем фиксирует изменения в источнике, хеш git будет меняться?
Хорхе Исраэль Пенья
@Blaenk ... я тоже думал об этом. Но идея bdonlan о том, чтобы программа запрашивала во время выполнения, кажется, решает эту проблему.
AndyL
6
Что ж, этот файл должен находиться в .gitignore и генерироваться каждый раз при сборке проекта.
Игорь Зевака
В качестве альтернативы вы можете включить базовую версию этого файла и установить --assume-unchangedдля нее флаг ( git update-index --assume-unchanged)
Игорь Зевака
2

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

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

Следующие 2 сценария сохраняются в том же каталоге, что и CMakeLists.txt:

get_git_hash.sh:

get_git_hash.cmd:

В CMakeLists.txt добавлены следующие строки

В код включается сгенерированный файл, #include <my_project/githash.h>а хеш git может быть напечатан на терминале std::cout << "Software version: " << kGitHash << std::endl;или записан в файл yaml (или любой другой) аналогичным образом.

Адриан
источник
1

Еще одна вариация на основе Makefile и оболочки

В файле git_commit_filename.h будет одна строка, содержащая static const char * GIT_COMMIT_SHA = "";

С https://gist.github.com/larytet/898ec8814dd6b3ceee65532a9916d406

Ларитет
источник
0

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

В основном, время от времени помечайте теги и убедитесь, что то, что вы доставляете, make distили что-то подобное.

Дастин
источник