Як я можу отримати свій код C для автоматичного роздрукування його хеш-версії Git?


84

Чи є простий спосіб написати код C, який може отримати доступ до свого хешу версії Git?

Я написав програмне забезпечення на С для збору наукових даних у лабораторних умовах. Мій код записує дані, які він збирає, у файл .yaml для подальшого аналізу. Мої експерименти змінюються щодня, і мені часто доводиться модифікувати код. Для відстеження версій я використовую репозиторій git.

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


1
Використання гачків перед фіксацією (див. Book.git-scm.com/5_git_hooks.html ) було б ще одним способом зробити це.
Іктула,

Відповіді:


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.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, тому радий коментарям, якщо є кращий спосіб це зробити.


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:

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

(Див. Опис man git щодо того, що роблять комутатори)

потім додайте це до своїх CFLAGS:

-DVERSION=\"$(GIT_VERSION)\"

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

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

За замовчуванням це просто друкує скорочений ідентифікатор коміту git, але за бажання ви можете позначити певні випуски чимось на зразок:

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

тоді він роздрукує:

Version: v1.1-2-g766d

це означає, що 2 фіксуються після версії 1.1, з git комітом, що починається з "766d".

Якщо у вашому дереві є незмінені зміни, воно додасть "-dirty".

Немає сканування залежностей, тому вам потрібно зробити явний make cleanпримусовий варіант оновлення версії. Однак це можна вирішити .

Переваги в тому, що він простий і не вимагає додаткових залежностей збірки, таких як perl або awk. Я застосував цей підхід з автоматичним виготовленням GNU та збірками Android NDK.


6
+1 Особисто я вважаю за краще, щоб makefile генерував файл заголовка, який містить #define GIT_VERSION ...замість того, щоб розміщувати його в командному рядку з -Dопцією; це усуває проблему залежності. Крім того, чому подвійне підкреслення? Технічно це зарезервований ідентифікатор.
Dan Molding

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, але не perl. Ось як це працює.

У моєму файлі make є рядок, який викликає git, date та awk для створення файлу змінного струму:

$(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 не постійно дає мені конфлікти злиття. Сподіваюся, це допомагає!


Додаток ... це буде працювати в Matlab: mathworks.com/matlabcentral/fileexchange/32864-get-git-info
AndyL

1
Я не думаю, що FORCEце гарна ідея, оскільки makefile ніколи не буде задоволений (кожного разу, коли ви змушуєте робити новий заголовок). Натомість ви можете просто додати залежність до відповідних файлів git у формулі $(MyLibs)/version.c : .git/COMMIT_EDITMSG .git/HEAD . Файл COMMIT_EDITMSGзмінюється кожного разу, коли ви робите коміт, і HEADзмінюється кожного разу, коли ви переглядаєте історію, тому ваш файл оновлюється щоразу, коли це актуально.
Kamil S Jaron

9

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


4
From git help describe: "Показати найновіший тег, до якого можна дотягнутися з коміту" - це не те, про що запитує запитання. Однак я погоджуюсь з рештою вашої відповіді. Для того, щоб бути правильним, повинна бути команда git rev-parse HEAD.
Mike Mazur

5
@mikem - git describeце те, що використовують більшість інших проектів, оскільки воно також містить інформацію, що читається людиною. Якщо ви не точно відповідаєте тегу, він додається до кількості комітів з моменту найближчого тегу та скороченого хешу версії.
bdonlan

7

Ви можете зробити дві речі:

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

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

    *.yaml    ident
    

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

    Git підтримує ключове слово $ Id $ таким чином, щоб уникнути дотику до файлів, які не були змінені під час перемикання гілок, перемотування гілки тощо. Якщо ви дійсно хочете, щоб Git помістив у файл ідентифікатор або опис коміту (версії), ви можете (ab) використовувати filterатрибут, використовуючи фільтр очищення / розмиття, щоб розширити якесь ключове слово (наприклад, $ Revision $) при оформленні замовлення та очистити його для коміту.

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

    Погляньте на сценарій GIT-VERSION-GEN та його використання в Git Makefile , або, наприклад, як цей Makefile вбудовує інформацію про версію під час генерації / конфігурації gitweb/gitweb.cgiфайлу.

    GIT-VERSION-GEN використовує git opis для створення опису версії. Це має працювати ефективніше, щоб ви позначили (використовуючи підписані / анотовані теги) випуски / етапи вашого проекту.


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
Я припускаю, що ви передали GIT_VERSION і GIT_DATE через CFLAGS, тому version.h може їх використовувати. Класно!
Джессі Чисхолм,

2

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

моє рішення було використовувати для обчислень лише основну гілку та змусити її виводити час побудови за допомогою макросів препроцесора __DATE__та __TIME__. таким чином я можу перевірити це за допомогою git log і побачити, яку версію я використовую. посилання: 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 ++


1
Використання препроцесорних макросів дуже розумно! Дякую.
AndyL

4
але якщо я перевіряю стару версію, то скомпілюю її, це призведе мене до неправильного коміту.
Себастьян Мах

2

Що вам потрібно зробити, це створити файл заголовка (наприклад, використовуючи echo from cmd line) приблизно так:

#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?
Хорхе Ізраїль Пенья

@Blaenk .. ось про що я теж думав. Але ідея bdonlan про те, щоб програму запитувати під час виконання, здається, обійде цю проблему.
AndyL

6
Ну, цей файл повинен знаходитись під .gitignore і створюватися кожного разу, коли ви будуєте проект.
Ігор Зевака

Крім того, ви можете включити базову версію цього файлу та встановити --assume-unchangedна ньому прапор ( git update-index --assume-unchanged)
Ігор Зевака

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

Це рішення для проекту 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 (або будь-який інший) подібним чином.


0

Ви можете побачити, як я це зробив для memcached в оригінальному коміті .

По суті, час від часу позначайте теги та переконайтеся, що річ, яку ви доставляєте, походить make distабо схожа.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.