Làm cách nào để lấy mã C của tôi để tự động in ra mã băm phiên bản Git của nó?


84

Có cách nào dễ dàng để viết mã C có thể truy cập hàm băm phiên bản Git của nó không?

Tôi đã viết phần mềm bằng C để thu thập dữ liệu khoa học trong phòng thí nghiệm. Mã của tôi ghi lại dữ liệu mà nó thu thập trong tệp .yaml để phân tích sau này. Các thử nghiệm của tôi thay đổi hàng ngày và tôi thường phải sửa đổi mã. Để theo dõi các bản sửa đổi, tôi sử dụng kho lưu trữ git.

Tôi muốn có thể bao gồm băm sửa đổi Git dưới dạng nhận xét trong tệp dữ liệu .yaml của mình. Bằng cách đó, tôi có thể xem tệp .yaml và biết chính xác mã nào đã được sử dụng để tạo dữ liệu được hiển thị trong tệp đó. Có cách nào dễ dàng để làm điều này tự động không?


1
Sử dụng hooks pre-commit (xem book.git-scm.com/5_git_hooks.html ) sẽ là một cách khác để thực hiện việc này.
Yktula

Câu trả lời:


39

Trong chương trình của mình, tôi giữ số phiên bản git và ngày của bản dựng trong một tệp riêng biệt, được gọi là version.c, trông giống như sau:

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

Ngoài ra còn có một tệp tiêu đề, trông giống như sau:

#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ả tệp tiêu đề và tệp C đều được tạo bởi tập lệnh Perl trông giống như sau:

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

Ở đây hash_to_c_filethực hiện tất cả các công việc tạo version.cversion.hmake_date_timetạo một chuỗi như được hiển thị.

Trong chương trình chính, tôi có một thói quen

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

Tôi không am hiểu nhiều về git, vì vậy tôi hoan nghênh các ý kiến ​​đóng góp nếu có cách tốt hơn để làm điều này.


1
Tập lệnh Perl là một phần của tập lệnh xây dựng, là "một bước xây dựng" cho mọi thứ.

12
Điều này là tốt cho đến khi nó đi, nhưng hãy nhớ rằng nó sẽ báo cáo băm của cam kết mới nhất trên nhánh, không phải băm của mã đang được biên dịch. Nếu có những thay đổi chưa được cam kết, những thay đổi đó sẽ không rõ ràng.
Phil Miller

1
git diff theo mặc định sẽ kiểm tra sự khác biệt giữa không gian làm việc của bạn và chỉ mục. Bạn cũng có thể muốn thử git diff --cached cho sự khác biệt giữa các chỉ số và TRỤ
Karl

6
Tất cả những 'const char * name = "value";' cấu trúc có thể được thay đổi hợp lý thành 'const char name [] = "value";', tiết kiệm 4 byte cho mỗi mục trên máy 32 bit và 8 byte cho mỗi mục trên máy 64 bit. Phải nói rằng, trong những ngày này với bộ nhớ chính hàng GB, đó không phải là một vấn đề lớn, nhưng tất cả đều hữu ích. Lưu ý rằng không có mã nào sử dụng tên cần thay đổi.
Jonathan Leffler

1
Tôi đã thay đổi chúng như bạn đề nghị. Kích thước chương trình của tôi với const char []: 319356 byte (bị tước bỏ). Kích thước chương trình của tôi với const char *: 319324 byte (bị tước). Vì vậy, ý tưởng của bạn dường như không tiết kiệm bất kỳ byte nào, nhưng tăng tổng số lên 32. Tôi không biết tại sao. Trong "version.c" ban đầu có ba chuỗi, nhưng một chuỗi đã bị bỏ qua trong câu trả lời trên. Nếu bạn nhìn vào bản chỉnh sửa đầu tiên, nó vẫn ở đó.

163

Nếu bạn đang sử dụng một bản dựng dựa trên trang điểm, bạn có thể đặt cái này trong Makefile:

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

(Xem man git mô tả cho những gì các công tắc làm)

sau đó thêm cái này vào CFLAGS của bạn:

-DVERSION=\"$(GIT_VERSION)\"

Sau đó, bạn chỉ có thể tham chiếu phiên bản trực tiếp trong chương trình như thể nó là #define:

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

Theo mặc định, điều này chỉ in một id cam kết git viết tắt, nhưng tùy ý bạn có thể gắn thẻ các bản phát hành cụ thể với một cái gì đó như:

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

sau đó nó sẽ in ra:

Version: v1.1-2-g766d

có nghĩa là, 2 lần cam kết trước v1.1, với id cam kết git bắt đầu bằng "766d".

Nếu có những thay đổi chưa được cam kết trong cây của bạn, nó sẽ thêm "-dirty".

Không có quá trình quét phụ thuộc, vì vậy bạn phải thực hiện một cách rõ ràng make cleanđể buộc cập nhật phiên bản. Điều này có thể được giải quyết tuy nhiên.

Ưu điểm là nó đơn giản và không yêu cầu bất kỳ phụ thuộc xây dựng nào như perl hoặc awk. Tôi đã sử dụng phương pháp này với tự động hóa GNU và với các bản dựng Android NDK.


6
+1 Cá nhân tôi muốn makefile tạo tệp tiêu đề có chứa #define GIT_VERSION ...thay vì đặt nó trên dòng lệnh với -Dtùy chọn; nó giúp loại bỏ vấn đề phụ thuộc. Ngoài ra, tại sao dấu gạch dưới kép? Về mặt kỹ thuật, đó là một định danh dành riêng.
Dan Molding

8
Mỗi cái đều riêng - như tôi đã nói ưu điểm là nó có ít bộ phận chuyển động và chúng dễ hiểu. Tôi đã chỉnh sửa nó để loại bỏ dấu gạch dưới.
ndyer

Cần phải nói thêm rằng nếu bạn sử dụng gengetopt, người ta có thể thêm nó trực tiếp vào gengetopt trong Makefile: gengetopt --set-version = $ (GIT_VERSION)
Trygve

1
Câu lệnh đầu tiên phải có dấu ngoặc kép GIT_VERSION := "$(shell git describe --abbrev=4 --dirty --always --tags)", không hoạt động nếu không có dấu ngoặc kép.
Abel Tom

11

Cuối cùng tôi đã sử dụng một cái gì đó rất giống với câu trả lời của @ Kinopiko, nhưng tôi đã sử dụng awk thay vì perl. Điều này rất hữu ích nếu bạn bị kẹt trên các máy windows đã được cài đặt awk theo bản chất của mingw, nhưng không phải perl. Đây là cách nó hoạt động.

Makefile của tôi có một dòng trong đó gọi git, date và awk để tạo tệp 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 

Mỗi khi tôi biên dịch mã của mình, lệnh awk tạo một tệp version.c trông giống như sau:

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

Tôi có một tệp version.h tĩnh trông giống như sau:

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

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


#endif /* VERSION_H_ */

Phần còn lại của mã của tôi bây giờ có thể truy cập thời gian xây dựng và băm git bằng cách chỉ cần bao gồm tiêu đề version.h. Để kết thúc tất cả, tôi yêu cầu git bỏ qua version.c bằng cách thêm một dòng vào tệp .gitignore của tôi. Bằng cách này, git không liên tục tạo cho tôi các xung đột hợp nhất. Hi vọng điêu nay co ich!


Một phụ lục ... điều này sẽ làm việc trong Matlab: mathworks.com/matlabcentral/fileexchange/32864-get-git-info
AndyL

1
Tôi không nghĩ đó FORCElà một ý kiến ​​hay vì makefile sẽ không bao giờ hài lòng (mỗi khi bạn bắt bạn làm một tiêu đề mới). Thay vào đó, bạn chỉ có thể thêm phần phụ thuộc vào các tệp git có liên quan trong công thức $(MyLibs)/version.c : .git/COMMIT_EDITMSG .git/HEAD . Tệp COMMIT_EDITMSGthay đổi mỗi khi bạn thực hiện cam kết và HEADthay đổi mỗi khi bạn duyệt lịch sử, do đó tệp của bạn luôn được cập nhật khi có liên quan.
Kamil S Jaron

9

Chương trình của bạn có thể chuyển sang git describe, trong thời gian chạy hoặc như một phần của quá trình xây dựng.


4
Từ git help describe: "Hiển thị thẻ gần đây nhất có thể truy cập được từ một cam kết" - đây không phải là những gì câu hỏi yêu cầu. Tuy nhiên, tôi đồng ý với phần còn lại của câu trả lời của bạn. Để chính xác, lệnh phải là git rev-parse HEAD.
Mike Mazur

5
@mikem, git describelà thứ mà hầu hết các dự án khác sử dụng, vì nó cũng bao gồm thông tin thẻ mà con người có thể đọc được. Nếu bạn không chính xác trên một thẻ, nó sẽ phụ thuộc vào số lần cam kết kể từ thẻ gần nhất và băm sửa đổi viết tắt.
bdonlan

7

Có hai điều bạn có thể làm:

  • Bạn có thể tạo Git để nhúng một số thông tin phiên bản vào tệp cho bạn.

    Cách đơn giản hơn là sử dụng ident thuộc tính , có nghĩa là đặt (ví dụ)

    *.yaml    ident
    

    trong .gitattributeshồ sơ và $Id$ở nơi thích hợp. Nó sẽ được tự động mở rộng thành mã định danh SHA-1 của nội dung tệp (blob id): đây KHÔNG phải là phiên bản tệp hoặc bản cam kết cuối cùng.

    Git hỗ trợ từ khóa $ Id $ theo cách này để tránh chạm vào các tệp không được thay đổi trong quá trình chuyển nhánh, tua lại nhánh, v.v. Nếu bạn thực sự muốn Git đưa mã định danh hoặc mô tả cam kết (phiên bản) vào tệp, bạn có thể (ab) sử dụng filter, sử dụng bộ lọc clean / smudge để mở rộng một số từ khóa (ví dụ: $ Revision $) khi thanh toán và xóa nó cho cam kết.

  • Bạn có thể thực hiện quá trình xây dựng để làm điều đó cho bạn, giống như nhân Linux hoặc chính Git.

    Hãy xem tập lệnh GIT-VERSION-GEN và việc sử dụng nó trong Git Makefile , hoặc ví dụ: cách Makefile này nhúng thông tin phiên bản trong quá trình tạo / cấu hình gitweb/gitweb.cgitệp.

    GIT-VERSION-GEN sử dụng git description để tạo mô tả phiên bản. Nó cần hoạt động tốt hơn khi bạn gắn thẻ (sử dụng thẻ có chữ ký / chú thích) các bản phát hành / cột mốc của dự án của bạn.


4

Khi tôi cần làm điều này, tôi sử dụng một thẻ , như RELEASE_1_23. Tôi có thể quyết định thẻ có thể là gì mà không cần biết SHA-1. Tôi cam kết sau đó gắn thẻ. Bạn có thể lưu trữ thẻ đó trong chương trình của mình mà bạn muốn.


4

Dựa trên câu trả lời của njd27, tôi đang sử dụng phiên bản có tính năng quét phụ thuộc, kết hợp với tệp version.h có các giá trị mặc định cho thời điểm mã được tạo theo một cách khác. Tất cả các tệp bao gồm version.h sẽ được xây dựng lại.

Nó cũng bao gồm ngày sửa đổi như một định nghĩa riêng biệt.

# 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
Tôi cho rằng bạn đã chuyển GIT_VERSION và GIT_DATE qua CFLAGS để phiên bản.h có thể sử dụng chúng. Mát mẻ!
Jesse Chisholm

2

Tôi cũng sử dụng git để theo dõi những thay đổi trong mã khoa học của mình. tôi không muốn sử dụng một chương trình bên ngoài vì nó hạn chế tính di động của mã (ví dụ: nếu ai đó muốn thực hiện thay đổi trên MSVS).

giải pháp của tôi là chỉ sử dụng nhánh chính cho các tính toán và làm cho nó xuất thời gian xây dựng bằng cách sử dụng macro bộ xử lý trước __DATE____TIME__. bằng cách đó, tôi có thể kiểm tra nó bằng git log và xem phiên bản tôi đang sử dụng. ref: http://gcc.gnu.org/onlineocs/cpp/Standard-Predefined-Macros.html

một cách thanh lịch khác để giải quyết vấn đề là đưa git log vào tệp thực thi. tạo một tệp đối tượng từ nhật ký git và đưa nó vào mã. lần này chương trình bên ngoài duy nhất bạn sử dụng là objcopy nhưng có ít mã hóa hơn. ref: http://www.linuxjournal.com/content/embedding-file-executable-aka-hello-world-version-5967Nhúng dữ liệu vào chương trình C ++


1
Việc sử dụng macro bộ xử lý trước rất thông minh! Cảm ơn bạn.
AndyL

4
nhưng nếu tôi kiểm tra một phiên bản cũ hơn, sau đó biên dịch nó, nó sẽ hướng dẫn tôi đến cam kết sai.
Sebastian Mach

2

Những gì bạn cần làm là tạo một tệp tiêu đề (ví dụ: sử dụng echo từ dòng cmd) như sau:

#define GIT_HASH \
"098709a0b098c098d0e"

Để tạo nó, hãy sử dụng một cái gì đó như sau:

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

Có thể cần phải xử lý các dấu ngoặc kép và dấu gạch chéo ngược một chút để biên dịch nó, nhưng bạn có ý tưởng.


Chỉ tự hỏi, không phải mỗi lần anh ta làm điều đó và do đó thay đổi tệp.h, và sau đó cam kết các thay đổi đối với nguồn, băm git sẽ thay đổi?
Jorge Israel Peña

@Blaenk .. đó là những gì tôi cũng đang nghĩ. Nhưng ý tưởng của bdonlan về việc yêu cầu chương trình trong thời gian chạy dường như giải quyết được vấn đề này.
AndyL

6
Chà, tệp này sẽ phải ở dưới .gitignore và được tạo mỗi khi bạn xây dựng dự án.
Igor Zevaka

Hoặc bạn có thể bao gồm một phiên bản cơ bản của tập tin này và thiết lập --assume-unchangedcờ trên đó ( git update-index --assume-unchanged)
Igor Zevaka

1

Tuy nhiên, một biến thể khác dựa trên Makefile và 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)

Tập tin git_commit_filename.h sẽ kết thúc bằng một dòng duy nhất chứa static const char * GIT_COMMIT_SHA = "";

Từ https://gist.github.com/ampletet/898ec8814dd6b3ceee65532a9916d406


1

Đây là một giải pháp cho dự án CMake hoạt động cho Windows và Linux mà không cần cài đặt bất kỳ chương trình nào khác (ví dụ: ngôn ngữ script).

Hàm băm git được ghi vào tệp .h bởi một tập lệnh, là tập lệnh bash khi biên dịch trên Linux hoặc tập lệnh hàng loạt của Windows khi biên dịch trên Windows và mệnh đề if trong CMakeLists.txt chọn tập lệnh tương ứng với nền tảng mã được biên dịch trên.

2 script sau được lưu trong cùng một thư mục với 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

Trong CMakeLists.txt, các dòng follwoing được thêm vào

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)

Trong mã, tệp được tạo được bao gồm #include <my_project/githash.h>và băm git có thể được in ra thiết bị đầu cuối với std::cout << "Software version: " << kGitHash << std::endl;hoặc được ghi vào tệp yaml (hoặc bất kỳ) theo cách tương tự.


0

Bạn có thể thấy cách tôi đã làm điều đó cho memcached trong cam kết ban đầu .

Về cơ bản, thỉnh thoảng gắn thẻ và đảm bảo thứ bạn phân phối đến từ make disthoặc tương tự.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.