将git-commit和git-diff作为字符串放入二进制文件中

Putting git commit and git diff into binary as strings?

本文关键字:二进制文件 字符串 git-commit git-diff      更新时间:2023-10-16

我是一名研究生,主要从事软件模拟研究。我有一些C++代码用来生成结果,但我的大问题是,为了争取可重复性,我想在二进制文件中保存足够的元数据,这样我就可以回到生成该二进制文件的确切源代码(主要是看看我发现的一些错误是否使我之前生成的一些结果无效)。

换句话说,当我生成一组输出文件时,我希望二进制文件转储当前修订的git提交以及任何未完成的更改。这将允许我(理论上)检查提交,应用保存的补丁,并返回到创建二进制文件的确切源代码。

我知道我可以通过手动保存信息或其他方式在带外完成这项工作,但为了确保完全一致性,我想直接将信息烘焙到二进制文件中,这样每个二进制文件都可以追溯到其确切的来源。

我熟悉在makefile中设置#define标志来存储类似gitcommitSHA1的东西,但我认为我需要一种更聪明的方法来将整个gitdiff存储为二进制文件中的字符串。

所以我有几个问题:

  1. 这是个糟糕的主意吗?有没有更好的方法可以将二进制文件追溯到源代码
  2. 实现这一目标的最佳方式是什么

谢谢。

编辑:我想我没有明确表示,我想保存差异的原因是为了在当前HEAD上捕获任何未提交的更改。我可以存储散列,但如果我错误地使用了一个二进制文件,其中包含一些未提交的内容,那么我就无法返回正确的源。

在代码中保存git"id"数字(哈希)不是一个坏主意。保存diff是毫无意义的,因为散列(以及它来自哪个分支)应该可以让你回到原始代码。

只需确保您的构建和测试系统设置为不能使用尚未提交的内容,这样您就不能在构建中进行一些未提交的随机更改。

编辑:在您的机器上,在项目的本地副本中进行测试,和使用检查所有内容的测试套件进行测试是有区别的——这是您用来确认一切正常的方法,对吧?请注意,在其他人获得该代码的副本之前,你测试什么并不重要——在提交代码之前,不要让其他人看到你的代码,也不要允许保存测试结果的完整测试套件用于发布说明等,如果你还没有提交所有的代码,那么就运行它[或者更好的是,有一个单独的目录/机器,它只从中央回购中获得新的代码-如果你这样做了,那么你就不可能使用未提交的代码。

我参与过几个这样工作的项目——你可以在本地目录中使用未提交的代码进行构建,但所有的"官方构建"都是在不同的机器上完成的,代码总是直接来自repo,没有本地更改。

如果你没有两台机器,也许有一台"充当独立机器"的虚拟机,或者只使用第二个目录[或不同的用户?]进行"官方测试"。

实际上,你可以简单地检查是否有一些差异,然后在"这就是哈希"的同时,如果有任何差异,添加一个额外的"-带有未提交的更改"或类似的内容。您可以使用git diff --exit-code为"无更改"或"更改"提供0或1退出代码。

  1. 你描述了两个想法:只有一个是可怕的。只需保存散列即可。您可以从中恢复所有其他元数据,包括diff
  2. 只需存储散列(请参阅1)-您已经知道如何执行此操作

我认为最好的答案是"不要那样做"。如果您想要可重复的构建,请仅针对提交的更改进行构建,而不是使用脏的工作目录。如果需要,可以在一个分支上提交实验性的东西,如果它不起作用,你可以把它扔掉,或者可能把它留在你的存储库中(例如,对于你重复构建的用例),但放弃那个分支,转而使用另一个新分支。如果您只想返回到特定的构建点,请考虑在这些特定的提交上删除适当的标记。

从技术角度来看,存储git SHA1 id对于您想要实现的目标来说是足够公平的。

未提交的更改如果存在构建过程,则将其设置为失败
如果建造工程太难/工作量太大,就表现出更多的纪律。:)

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

第2版:
如果您必须调试许多版本的代码,git help bisect会派上用场。

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选项卡,选择正确的工具包并在构建选项卡上选择阴影构建

如果您正在使用Microsoft Visual Studio,请右键单击CMakeLists.txt并选择CMake Settings for,然后将构建根更改为如下内容:

${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文件

这个文件实际上是一个.h文件,它加载了要在cmake执行期间替换的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 .

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

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