将git commit 和 git diff 转换成二进制字符串?

3
我是一名研究生,我的主要研究领域是软件模拟。我有一些C++代码用于生成结果,但我的一个大问题是,为了追求可重复性,我想在我的二进制文件中保存足够的元数据,以便我可以回到生成该二进制文件的确切源代码(主要是为了查看我发现的某个错误是否使我先前生成的某个结果无效)。
换句话说,当我产生一组输出文件时,我希望二进制文件同时转储当前版本的git提交和任何未解决的更改。这将允许我(理论上)检出该提交,应用保存的补丁,并返回创建二进制文件的确切源代码。
我知道我可以通过手动保存信息之类的方式来完成此操作,但为了确保完全一致性,我希望直接将信息嵌入到二进制文件中,以便每个二进制文件都能被追溯到其确切的源代码。
我熟悉诸如在makefile中设置#define标志以存储git提交SHA1之类的内容,但我认为我需要一种更聪明的方式将整个git差异作为字符串存储在二进制文件中。
所以我有几个问题:
1. 这是一个可怕的想法吗?有没有更好的方法来追溯二进制文件的源代码? 2. 实现这一点的最佳方法是什么?
谢谢。
编辑:我想我没有明确说明保存差异的原因是为了捕获当前HEAD之上的任何未提交更改。我可以存储哈希值,但如果我错误地使用了一个包含一些未提交内容的二进制文件,那么我就无法获取正确的源代码。
5个回答

3
在代码中保存git的“id”号(哈希)并不是个坏主意。保存diff是无意义的,因为哈希值(以及它来自哪个分支)应该可以让你回到原始代码。
只需确保你的构建和测试系统设置得不能使用未提交的内容,这样你就不会在构建中出现一些随机更改而没有提交。
编辑: 在你的机器上测试和在本地项目副本上测试以及使用检查所有东西的测试套件进行测试之间存在区别-这是你用来确认所有东西都正常工作的,对吧?请注意,在其他人拿到那份代码之前,你测试什么并不重要-不要让其他人在代码被提交之前看到你的代码,并且如果你没有提交所有内容,则不允许运行完整的测试套件,该测试套件保存测试结果用于发布说明等等 [或者最好有一个单独的目录/机器,只从中央仓库获取新代码-如果你这样做,那么你就不可能使用未提交的代码。
我曾经参与过几个按照这种方式工作的项目-你可以在本地目录中使用未提交的代码进行构建,但所有“官方构建”都是在不同的机器上完成的,代码总是直接来自仓库,没有本地更改。
如果你没有两台机器,也许可以使用一个“模拟独立机器”的虚拟机,或者只使用第二个目录[或不同的用户]来进行“官方测试”。
实际上,你可以简单地检查是否有差异,然后在“这是哈希”中加入额外的“-with-uncommitted-changes”或类似的内容。你可以使用git diff --exit-code为“没有更改”或“更改”提供0或1的退出代码。

那样做有点用,但我需要能够构建二进制文件来测试更改 - 如果我可以构建二进制文件,不可避免地会犯一个错误,即使用添加了未提交更改的二进制文件生成结果。 - KarateSnowMachine
@KarateSnowMachine:那么,只需设置您的构建脚本,以便在存在未提交更改时失败:-)。使用git,经常进行提交是一个好主意。 - sleske
@KarateSnowMachine:请查看编辑(内容有点长,无法全部写在评论中 - 如果您足够快,在编辑发布之前就可以看到)。 - Mats Petersson

2
  1. 您提出了两种想法:其中一种很糟糕。只需保存哈希值即可。您可以从中恢复所有其他元数据,包括差异。
  2. 只需存储哈希值(参见1)-您已经知道如何做到这一点。

保存差异的目的在于将当前 HEAD 之上的任何未提交更改保存下来。 - KarateSnowMachine
不要从未提交的源代码生成发布版本。此处git diff的输出也不足够,因为它会忽略你尚未添加的新文件。 - Useless

2

从技术角度来看,存储git SHA1 id就足够满足您的需求了。

未提交的更改?如果存在这些更改,请设置构建过程失败。
如果构建工程太难或者工作量太大,只需更加严格遵守纪律。 :)

编辑:
通过shell脚本进行构建。在构建之前检查更改,git diff --exit-code可能会有所帮助。

编辑2:
如果您需要调试许多版本的代码,则可以使用git help bisect


2
我认为最好的答案是“不要那样做”。如果您想要可重复的构建,请仅针对已提交更改进行构建,而不是使用脏工作目录。如果有必要,请在侧向分支上提交实验性内容,如果不起作用,则可以将其丢弃,或者将其留在您的存储库中(例如,用于重复构建的用例),但放弃该分支并在另一个新分支上工作。如果您只需要回到具体的某些点来进行构建,请考虑在这些特定提交上放置适当的标记。

1

version-from-git

将git信息嵌入到二进制文件中。

目标

考虑以下C++程序:

#include <iostream>
#include <format>

#include "gitversion.h"

int main()
{
    std::cout << std::format("Build Info: {}\n{}\n{}\n{}",
        FromGit::Branch, FromGit::Tag, FromGit::Commit, FromGit::Date) <<
        std::endl;
    return 0;
}

目标是拥有一个gitversion.h文件,引入该文件后可以访问存储库的分支、标签、提交和日期信息。
建议采用离线构建方法,即构建树位于源代码树之外。要通过命令行实现此目标:
mkdir ../build
cd ../build

如果你使用的是QtCreator,可以在IDE左侧的“Projects”选项卡中选择正确的“kit”,然后在构建选项卡中选择“Shadow build”。如果你使用的是Microsoft Visual Studio,则右键单击CMakeLists.txt并选择“CMake Settings for...”,然后将“Build root”更改为以下内容:
${projectDir}\..\build\${name}

如果您违反此建议并将构建目录放在源代码内部,则每次运行cmake或更改IDE中的项目设置时,git status可能会发生变化,并且不需要的更改将反映在您的二进制文件中。

CMakeLists 文件

以下是CMakeLists.txt的内容:

cmake_minimum_required(VERSION 3.20)

project(Tutorial)

# Where we get information from the git and put it into the gitversion.h file.
include(${PROJECT_SOURCE_DIR}/gitversion.cmake)

set(CMAKE_CXX_STANDARD 23)
set(CMAKE_CXX_STANDARD_REQUIRED True)

add_executable(Tutorial tutorial.cxx)

# Must include the build tree to get access to the gitversion.h file.
target_include_directories(Tutorial PUBLIC "${PROJECT_BINARY_DIR}")

.cmake文件

这是大部分工作发生的地方:

 # find_package(Git) -> finds the location of the git executable
 # git describe --always --abbrev=8 -> gives the abbreviated hash
 # git status --short -> checks for uncommitted  work
 # git describe --exact-match --tags -> gives the tag
 # git rev-parse --abbrev-ref HEAD -> gives current branch
 # git log -n 1 --pretty=%cd --pretty=%cI -> gives the time of the last commit
find_package(Git)
execute_process(COMMAND ${GIT_EXECUTABLE} describe --always --abbrev=8
                WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}
                OUTPUT_VARIABLE GIT_COMMIT)
execute_process(COMMAND ${GIT_EXECUTABLE} status --short
                WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}
                OUTPUT_VARIABLE GIT_STATUS)
if (("${GIT_COMMIT}" STREQUAL "") OR (NOT "${GIT_STATUS}" STREQUAL ""))
    set(GIT_COMMIT "N/A")
endif()
execute_process(COMMAND ${GIT_EXECUTABLE} describe --exact-match --tags
                WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}
                OUTPUT_VARIABLE GIT_TAG
                ERROR_QUIET)
if ("${GIT_TAG}" STREQUAL "")
    set(GIT_TAG "N/A")
endif()

execute_process(COMMAND ${GIT_EXECUTABLE} rev-parse --abbrev-ref HEAD
                WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}
                OUTPUT_VARIABLE GIT_BRANCH)
if ("${GIT_BRANCH}" STREQUAL "")
    set(GIT_BRANCH "N/A")
endif()

execute_process(COMMAND ${GIT_EXECUTABLE} log -n 1 --pretty=%cd --pretty=%cI
                WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}
                OUTPUT_VARIABLE GIT_DATE)
if ("${GIT_DATE}" STREQUAL "")
    set(GIT_DATE "N/A")
endif()

string(STRIP "${GIT_COMMIT}" GIT_COMMIT)
string(STRIP "${GIT_TAG}" GIT_TAG)
string(STRIP "${GIT_BRANCH}" GIT_BRANCH)
string(STRIP "${GIT_DATE}" GIT_DATE)

# replaces matching variabels in gitversion.h.in file and writes it to gitversion.h
configure_file(gitversion.h.in gitversion.h)

.in文件

这个文件实际上是一个包含与cmake相关的变量的.h文件,在cmake执行期间将被替换:

#pragma once
#include <string_view>
namespace FromGit
{
    const std::string_view Commit{"@GIT_COMMIT@"};
    const std::string_view Tag{"@GIT_TAG@"};
    const std::string_view Branch{"@GIT_BRANCH@"};
    const std::string_view Date{"@GIT_DATE@"};
}

最后一步

最后,在我们的构建目录中运行:

cmake ../Tutorial
cmake --build .

确保使用git初始化Tutorial目录。

此解决方案基于Matt Keeter的工作。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接