Come posso fare in modo che il mio codice C stampi automaticamente l'hash della sua versione Git?


84

C'è un modo semplice per scrivere codice C che possa accedere all'hash della sua versione Git?

Ho scritto software in C per raccogliere dati scientifici in un ambiente di laboratorio. Il mio codice registra i dati che raccoglie in un file .yaml per un'analisi successiva. I miei esperimenti cambiano di giorno in giorno e spesso devo modificare il codice. Per tenere traccia delle revisioni, utilizzo un repository git.

Vorrei poter includere l'hash di revisione Git come commento nei miei file di dati .yaml. In questo modo, potrei guardare il file .yaml e sapere esattamente quale codice è stato utilizzato per generare i dati mostrati in quel file. C'è un modo semplice per farlo automaticamente?


1
L'uso di hook pre-commit (vedere book.git-scm.com/5_git_hooks.html ) sarebbe un altro modo per farlo.
Yktula

Risposte:


39

Nel mio programma, tengo il numero di versione di git e la data della build in un file separato, chiamato version.c, che assomiglia a questo:

C'è anche un file di intestazione, che assomiglia a questo:

Sia il file di intestazione che il file C sono generati da uno script Perl che assomiglia a questo:

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

Qui hash_to_c_filefa tutto il lavoro di creazione version.ce di version.he make_date_timerende una stringa come mostrato.

Nel programma principale ho una routine

Non sono molto informato su git, quindi gradirei commenti se esiste un modo migliore per farlo.


1
Lo script Perl fa parte dello script build, che è una "build one step" per tutto.

12
Questo va bene fin dove va, ma tieni presente che riporterà l'hash dell'ultimo commit sul ramo, non l'hash del codice che viene compilato. Se ci sono modifiche non confermate, quelle non saranno evidenti.
Phil Miller

1
git diff per impostazione predefinita controlla le differenze tra l'area di lavoro e l'indice. Potresti anche provare git diff --cached per le differenze tra l'indice e HEAD
Karl

6
Tutti quelli 'const char * name = "value";' i costrutti potrebbero essere sensatamente modificati in 'const char name [] = "value";', che salva 4 byte per elemento su una macchina a 32 bit e 8 byte per elemento su una macchina a 64 bit. Certo, in questi giorni di GB di memoria principale, non è un grosso problema, ma tutto aiuta. Notare che nessuno dei codici che utilizzano i nomi deve essere modificato.
Jonathan Leffler

1
Li ho cambiati come suggerisci. La dimensione del mio programma con const char []: 319356 byte (rimossi). La dimensione del mio programma con const char *: 319324 byte (rimossi). Quindi la tua idea non sembra salvare alcun byte, ma aumenta il numero totale di 32. Non ho idea del perché. Nella "version.c" originale ci sono tre stringhe, ma una è stata omessa dalla risposta precedente. Se guardi la prima modifica, è ancora lì.

163

Se stai usando una build basata sul make, puoi metterla nel Makefile:

(Vedi man git descrivere per cosa fanno gli interruttori)

quindi aggiungilo alle tue CFLAG:

Quindi puoi semplicemente fare riferimento alla versione direttamente nel programma come se fosse un #define:

Per impostazione predefinita, questo stampa solo un id git commit abbreviato, ma opzionalmente puoi taggare particolari rilasci con qualcosa come:

quindi stamperà:

il che significa, 2 commit dopo la v1.1, con un ID commit git che inizia con "766d".

Se ci sono modifiche non salvate nel tuo albero, verrà aggiunto "-dirty".

Non esiste una scansione delle dipendenze, quindi devi fare un esplicito make cleanper forzare l'aggiornamento della versione. Questo può essere risolto comunque.

I vantaggi sono che è semplice e non richiede dipendenze di compilazione aggiuntive come perl o awk. Ho usato questo approccio con GNU automake e con build Android NDK.


6
+1 Personalmente, preferisco che il makefile generi un file di intestazione che contiene #define GIT_VERSION ...invece di metterlo sulla riga di comando con l' -Dopzione; elimina il problema della dipendenza. Inoltre, perché il doppio trattino basso? Tecnicamente è un identificatore riservato.
Dan Moulding

8
Ognuno a proprio - come ho detto, i vantaggi sono che ha poche parti mobili e sono comprensibili. L'ho modificato per rimuovere i trattini bassi.
ndyer

Va aggiunto che se usi gengetopt puoi aggiungerlo direttamente a gengetopt nel Makefile: gengetopt --set-version = $ (GIT_VERSION)
Trygve

1
La prima istruzione dovrebbe essere tra virgolette GIT_VERSION := "$(shell git describe --abbrev=4 --dirty --always --tags)", non funziona senza virgolette.
Abel Tom

11

Ho finito per usare qualcosa di molto simile alla risposta di @ Kinopiko, ma ho usato awk invece di perl. Questo è utile se sei bloccato su macchine Windows su cui è installato awk per natura di mingw, ma non perl. Ecco come funziona.

Il mio makefile contiene una riga che richiama git, date e awk per creare un file ac:

Ogni volta che compilo il mio codice, il comando awk genera un file version.c simile a questo:

Ho un file version.h statico simile a questo:

Il resto del mio codice ora può accedere al tempo di compilazione e all'hash git semplicemente includendo l'intestazione version.h. Per concludere, dico a git di ignorare version.c aggiungendo una riga al mio file .gitignore. In questo modo git non mi dà costantemente conflitti di fusione. Spero che sia di aiuto!


Un addendum ... questo funzionerà in Matlab: mathworks.com/matlabcentral/fileexchange/32864-get-git-info
AndyL

1
Non credo FORCEsia una buona idea dato che makefile non sarà mai soddisfatto (ogni volta che ti fai fare una nuova intestazione). Invece, puoi semplicemente aggiungere la dipendenza ai file git pertinenti nella formula $(MyLibs)/version.c : .git/COMMIT_EDITMSG .git/HEAD . Il file COMMIT_EDITMSGcambia ogni volta che effettui un commit e HEADcambia ogni volta che navighi nella cronologia, quindi il tuo file viene aggiornato ogni volta che è rilevante.
Kamil S Jaron

9

Il tuo programma può eseguire la shell git describein fase di esecuzione o come parte del processo di compilazione.


4
Da git help describe: "Mostra il tag più recente raggiungibile da un commit" - questo non è ciò che la domanda chiede. Tuttavia, sono d'accordo con il resto della tua risposta. Per essere corretto, il comando dovrebbe essere git rev-parse HEAD.
Mike Mazur

5
@mikem, git describeè quello che usa la maggior parte degli altri progetti, perché include anche informazioni sui tag leggibili dall'uomo. Se non sei esattamente su un tag, viene aggiunto il numero di commit dal tag più vicino e l'hash di revisione abbreviato.
bdonlan

7

Ci sono due cose che puoi fare:

  • Puoi fare in modo che Git incorpori alcune informazioni sulla versione nel file per te.

    Il modo più semplice è usare l' ident attributo , che significa mettere (per esempio)

    in .gitattributesfile e $Id$nel luogo appropriato. Sarebbe automaticamente espanso all'identificatore SHA-1 del contenuto del file (blob id): questa NON è la versione del file o l'ultimo commit.

    Git supporta la parola chiave $ Id $ in questo modo per evitare di toccare i file che non sono stati modificati durante il cambio di ramo, il riavvolgimento del ramo ecc. Se vuoi veramente che Git metta l'identificatore o la descrizione del commit (versione) nel file, puoi (ab) usare filterattributo, utilizzando il filtro clean / smudge per espandere alcune parole chiave (ad esempio $ Revision $) al momento del checkout e ripulirlo per il commit.

  • Puoi fare in modo che il processo di compilazione lo faccia per te, come fanno il kernel Linux o lo stesso Git.

    Dai un'occhiata allo script GIT-VERSION-GEN e al suo utilizzo in Git Makefile , o ad esempio come questo Makefile incorpora le informazioni sulla versione durante la generazione / configurazione del gitweb/gitweb.cgifile.

    GIT-VERSION-GEN utilizza git description per generare la descrizione della versione. Deve funzionare meglio che tu tagghi (usando tag firmati / annotati) i rilasci / le pietre miliari del tuo progetto.


4

Quando ho bisogno di farlo, utilizzo un tag , come RELEASE_1_23. Posso decidere quale tag può essere senza conoscere SHA-1. Mi impegno e poi taggo. Puoi memorizzare quel tag nel tuo programma come preferisci.


4

Sulla base della risposta di njd27, sto usando la versione a con scansione delle dipendenze, in combinazione con un file version.h con valori predefiniti per quando il codice è costruito in un modo diverso. Tutti i file che includono version.h verranno ricostruiti.

Include anche la data di revisione come definizione separata.


1
Presumo che tu abbia GIT_VERSION e GIT_DATE passati tramite CFLAGS in modo che version.h possa usarli. Freddo!
Jesse Chisholm

2

Uso anche git per tenere traccia delle modifiche nel mio codice scientifico. Non volevo usare un programma esterno perché limita la portabilità del codice (se qualcuno volesse apportare modifiche su MSVS per esempio).

la mia soluzione è stata quella di utilizzare solo il ramo principale per i calcoli e fargli generare il tempo di compilazione utilizzando le macro del preprocessore __DATE__e __TIME__. in questo modo posso controllarlo con git log e vedere quale versione sto usando. rif: http://gcc.gnu.org/onlinedocs/cpp/Standard-Predefined-Macros.html

un altro modo elegante per risolvere il problema è includere git log nell'eseguibile. crea un file oggetto da git log e includilo nel codice. questa volta l'unico programma esterno che usi è objcopy ma c'è meno codice. rif: http://www.linuxjournal.com/content/embedding-file-executable-aka-hello-world-version-5967 e Incorpora dati in un programma C ++


1
L'uso delle macro del preprocessore è molto intelligente! Grazie.
AndyL

4
ma se eseguo il checkout di una versione precedente, quindi la compilo, mi guiderà al commit sbagliato.
Sebastian Mach

2

Quello che devi fare è generare un file di intestazione (ad esempio usando echo dalla riga cmd) qualcosa del genere:

Per generarlo usa qualcosa del genere:

Potrebbe essere necessario giocare un po 'con le virgolette e le barre rovesciate per farlo compilare, ma hai capito.


Mi chiedevo solo, ogni volta che lo fa e quindi cambia file.h, e poi esegue il commit delle modifiche al sorgente, il git hash cambierebbe?
Jorge Israel Peña

@ Blaenk .. questo è quello che stavo pensando anch'io. Ma l'idea di bdonlan di chiedere al programma di chiedere in fase di esecuzione sembra aggirare questo problema.
AndyL

6
Bene, questo file dovrebbe essere sotto .gitignore e generato ogni volta che si compila il progetto.
Igor Zevaka

In alternativa puoi includere una versione base di questo file e impostarci un --assume-unchangedflag ( git update-index --assume-unchanged)
Igor Zevaka

2

Questa è una soluzione per il progetto CMake che funziona per Windows e Linux, senza la necessità di installare altri programmi (es. Linguaggi di script).

L'hash git viene scritto in un file .h da uno script, che è uno script bash durante la compilazione su Linux o uno script batch di Windows durante la compilazione su Windows, e una clausola if in CMakeLists.txt seleziona lo script corrispondente alla piattaforma il codice è compilato.

I seguenti 2 script vengono salvati nella stessa directory di CMakeLists.txt:

get_git_hash.sh:

get_git_hash.cmd:

In CMakeLists.txt vengono aggiunte le seguenti righe

Nel codice il file generato è incluso #include <my_project/githash.h>e l'hash git può essere stampato sul terminale con std::cout << "Software version: " << kGitHash << std::endl;o scritto in un file yaml (o qualsiasi) in modo simile.


1

Ancora un'altra variazione basata su Makefile e shell

Il file git_commit_filename.h finirà con una singola riga contenente static const char * GIT_COMMIT_SHA = "";

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


0

Puoi vedere come l'ho fatto per memcached nel commit originale .

Fondamentalmente, tagga di tanto in tanto e assicurati che l'oggetto che consegni provenga make disto sia simile.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.