在 .Net dll 中嵌入 git commit hash

Posted

技术标签:

【中文标题】在 .Net dll 中嵌入 git commit hash【英文标题】:Embed git commit hash in a .Net dll 【发布时间】:2022-01-13 00:58:44 【问题描述】:

我正在构建一个 C# 应用程序,使用 Git 作为我的版本控制。

有没有办法在我构建应用程序时自动将最后一次提交哈希嵌入到可执行文件中?

例如,将提交哈希打印到控制台看起来像:

class PrintCommitHash

    private String lastCommitHash = ?? // What do I put here?
    static void Main(string[] args)
    
        // Display the version number:
        System.Console.WriteLine(lastCommitHash );
    

请注意,这必须在 build 时完成,而不是 runtime,因为我部署的可执行文件将无法访问 git 存储库。

可以在here 找到与 C++ 相关的问题。 编辑 根据@mattanja 的要求,我发布了我在项目中使用的 git hook 脚本。设置:

hook 是 linux shell 脚本,位于:path_to_project\.git\hooks 如果您使用msysgit,hooks 文件夹已经包含一些示例脚本。为了让 git 调用它们,请从脚本名称中删除“.sample”扩展名。 挂钩脚本的名称与调用它们的事件相匹配。就我而言,我修改了 post-commitpost-merge。 我的 AssemblyInfo.cs 文件直接位于项目路径下(与 .git 文件夹处于同一级别)。它包含 23 行,我使用 git 生成第 24 行。

由于我的 linux-shelling 有点生疏,脚本只是将 AssemblyInfo.cs 的前 23 行读取到一个临时文件,将 git 哈希回显到最后一行,然后重命名文件返回到 AssemblyInfo.cs。我确信有更好的方法来做到这一点:

#!/bin/sh
cmt=$(git rev-list --max-count=1 HEAD)
head -23 AssemblyInfo.cs > AssemblyInfo.cs.tmp
echo [assembly: AssemblyFileVersion\(\"$cmt\"\)] >> AssemblyInfo.cs.tmp
mv AssemblyInfo.cs.tmp AssemblyInfo.cs

希望这会有所帮助。

【问题讨论】:

【参考方案1】:

您可以将 version.txt 文件嵌入到可执行文件中,然后读取 version.txt的可执行文件。要创建 version.txt 文件,请使用 git describe --long

步骤如下:

使用构建事件调用 git

右键单击项目并选择属性

在 Build Events 中,添加 Pre-Build 事件包含(注意引号):

"C:\Program Files\Git\bin\git.exe" 描述 --long > "$(ProjectDir)\version.txt"

这将在您的项目目录中创建一个 version.txt 文件。

在可执行文件中嵌入 version.txt

右键单击项目并选择添加现有项 添加 version.txt 文件(更改文件选择器过滤器以让您查看所有文件) version.txt 添加后,在解决方案资源管理器中右键单击它并选择属性 将构建操作更改为嵌入式资源 将复制到输出目录更改为始终复制 将 version.txt 添加到您的 .gitignore 文件中

读取嵌入的文本文件版本字符串

下面是一些读取嵌入文本文件版本字符串的示例代码:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Reflection;

namespace TryGitDescribe

    class Program
    
        static void Main(string[] args)
        
            string gitVersion= String.Empty;
            using (Stream stream = Assembly.GetExecutingAssembly()
                    .GetManifestResourceStream("TryGitDescribe." + "version.txt"))
            using (StreamReader reader = new StreamReader(stream))
            
                gitVersion= reader.ReadToEnd();
            

            Console.WriteLine("Version: 0", gitVersion);
            Console.WriteLine("Hit any key to continue");
            Console.ReadKey();
        
    

【讨论】:

这种方法效果很好。不过,我使用了“git rev-parse --short HEAD”。 啊,很好。我使用“git describe”是因为(对我而言)有标签时它真的很有趣;版本信息包含标签加上标签应用后的提交次数;以前从未在 SCM 中看到过类似的东西。 我使用git describe --dirty,它在开发人员使用脏工作树时添加一个标志。 @TamásSzelei 项目命名空间是 TryGitDescribe。将 version.txt 文件嵌入到可执行/程序集工件中后,您需要预先添加命名空间以将其取出。 感谢您提供完整的解决方案。就我而言,我使用GetEntryAssembly 来组装。在任何情况下,您都可以致电 GetName().Name 以避免硬编码名称。【参考方案2】:

更新:

自从我最初回答这个问题以来,事情已经发生了变化。 Microsoft.NET.Sdk(意味着您必须使用 sdk 样式的项目)现在支持将提交哈希添加到程序集信息版本以及 nuget 包元数据(如果满足某些条件):

    必须定义<SourceRevisionId> 属性。这可以通过添加这样的目标来完成:
<Target Name="SetSourceRevisionId" BeforeTargets="InitializeSourceControlInformation">
    <Exec 
      Command="git describe --long --always --dirty --exclude=* --abbrev=8"
      ConsoleToMSBuild="True"
      IgnoreExitCode="False"
      >
      <Output PropertyName="SourceRevisionId" TaskParameter="ConsoleOutput"/>
    </Exec>
  </Target>

此目标执行一个命令,将SourceRevisionId 设置为缩写(8 个字符)哈希。 BeforeTargets 导致它在创建程序集信息版本之前运行。

    要在 nuget 包元数据中包含哈希,还必须定义 &lt;RepositoryUrl&gt;

    &lt;SourceControlInformationFeatureSupported&gt; 属性必须是 true,这会导致 nuget pack 任务也获取 SourceRevisionId。

我会引导人们远离使用 MSBuildGitHash 包,因为这种新技术更简洁且最一致。

原文:

我创建了一个简单的 nuget 包,您可以将其包含在您的项目中,它会为您解决这个问题:https://www.nuget.org/packages/MSBuildGitHash/

这个 nuget 包实现了一个“纯”的 MSBuild 解决方案。如果您不想依赖 nuget 包,您可以简单地将这些目标复制到您的 csproj 文件中,它应该包含 git 哈希作为自定义程序集属性:

<Target Name="GetGitHash" BeforeTargets="WriteGitHash" Condition="'$(BuildHash)' == ''">
  <PropertyGroup>
    <!-- temp file for the git version (lives in "obj" folder)-->
    <VerFile>$(IntermediateOutputPath)gitver</VerFile>
  </PropertyGroup>

  <!-- write the hash to the temp file.-->
  <Exec Command="git -C $(ProjectDir) describe --long --always --dirty &gt; $(VerFile)" />

  <!-- read the version into the GitVersion itemGroup-->
  <ReadLinesFromFile File="$(VerFile)">
    <Output TaskParameter="Lines" ItemName="GitVersion" />
  </ReadLinesFromFile>
  <!-- Set the BuildHash property to contain the GitVersion, if it wasn't already set.-->
  <PropertyGroup>
    <BuildHash>@(GitVersion)</BuildHash>
  </PropertyGroup>    
</Target>

<Target Name="WriteGitHash" BeforeTargets="CoreCompile">
  <!-- names the obj/.../CustomAssemblyInfo.cs file -->
  <PropertyGroup>
    <CustomAssemblyInfoFile>$(IntermediateOutputPath)CustomAssemblyInfo.cs</CustomAssemblyInfoFile>
  </PropertyGroup>
  <!-- includes the CustomAssemblyInfo for compilation into your project -->
  <ItemGroup>
    <Compile Include="$(CustomAssemblyInfoFile)" />
  </ItemGroup>
  <!-- defines the AssemblyMetadata attribute that will be written -->
  <ItemGroup>
    <AssemblyAttributes Include="AssemblyMetadata">
      <_Parameter1>GitHash</_Parameter1>
      <_Parameter2>$(BuildHash)</_Parameter2>
    </AssemblyAttributes>
  </ItemGroup>
  <!-- writes the attribute to the customAssemblyInfo file -->
  <WriteCodeFragment Language="C#" OutputFile="$(CustomAssemblyInfoFile)" AssemblyAttributes="@(AssemblyAttributes)" />
</Target>

这里有两个目标。第一个,“GetGitHash”,将 git 哈希加载到名为 BuildHash 的 MSBuild 属性中,它在尚未定义 BuildHash 时执行此操作。如果您愿意,这允许您在命令行上将其传递给 MSBuild。您可以像这样将它传递给 MSBuild:

MSBuild.exe myproj.csproj /p:BuildHash=MYHASHVAL

第二个目标“WriteGitHash”会将哈希值写入名为“CustomAssemblyInfo.cs”的临时“obj”文件夹中的文件。该文件将包含如下所示的一行:

[assembly: AssemblyMetadata("GitHash", "MYHASHVAL")]

此 CustomAssemblyInfo.cs 文件将编译到您的程序集中,因此您可以使用反射在运行时查找 AssemblyMetadata。以下代码显示了当 AssemblyInfo 类包含在同一程序集中时如何做到这一点。

using System.Linq;
using System.Reflection;

public static class AssemblyInfo

    /// <summary> Gets the git hash value from the assembly
    /// or null if it cannot be found. </summary>
    public static string GetGitHash()
    
        var asm = typeof(AssemblyInfo).Assembly;
        var attrs = asm.GetCustomAttributes<AssemblyMetadataAttribute>();
        return attrs.FirstOrDefault(a => a.Key == "GitHash")?.Value;
    

这种设计的一些好处是它不会触及项目文件夹中的任何文件,所有变异的文件都在“obj”文件夹下。您的项目也将在 Visual Studio 中或从命令行以相同方式构建。它还可以轻松地为您的项目定制,并将与您的 csproj 文件一起进行源代码控制。

【讨论】:

这非常有效。我安装了 nuget 包,并能够使用 Assembly.GetExecutingAssembly() 提取 git 哈希,然后检查程序集 CustomAttributes @danmiser 我不知道“UseMerge/SingleAssemblyName”是什么,所以我帮不了你。在github.com/MarkPflug/MSBuildGitHash 创建一个问题,我可能会看一下(这不是承诺)。 感谢现代的回答 - 非常干净的方式来做到这一点。对于任何有相同问题的未来读者:为了让这个工作(对于 SDK 项目方法),我还需要 IncludeSourceRevisionInInformationalVersion 属性,并且我还必须在 SourceRevisionId 中未设置InitializeSourceControlInformation 目标,但在使用 BeforeTargets="InitializeSourceControlInformation" 声明的单独目标中。 对于像我一样努力应用新(更新)答案的人:您需要将带有Target 的部分添加到csproj 或Directory.Build.targets。同样出于某种原因,名称InitializeSourceControlInformation 最初对我不起作用。您也可以在Target 标签中添加Condition 属性,即。 e.仅将此目标限制为非调试配置Condition="'$(ConfigurationName)' != 'Debug'" 这是一个工作示例: 0.0.2.0 $(Version)$(SourceRevisionId)【参考方案3】:

我们在 git 中使用标签来跟踪版本。

git tag -a v13.3.1 -m "version 13.3.1"

您可以通过以下方式从 git 获取带有哈希的版本:

git describe --long

我们的构建过程将 git 哈希放在 AssemblyInfo.cs 文件的 AssemblyInformationalVersion 属性中:

[assembly: AssemblyInformationalVersion("13.3.1.74-g5224f3b")]

编译后,您可以从 windows 资源管理器中查看版本:

您也可以通过以下方式以编程方式获取它:

var build = ((AssemblyInformationalVersionAttribute)Assembly
  .GetAssembly(typeof(YOURTYPE))
  .GetCustomAttributes(typeof(AssemblyInformationalVersionAttribute), false)[0])
  .InformationalVersion;

其中 YOURTYPE 是程序集中具有 AssemblyInformationalVersion 属性的任何类型。

【讨论】:

嗨,一个月前我想问,但是,我没有足够的代表发表评论。当您说“我们的构建过程将 git 哈希放入 AssemblyInfo.cs 的 AssemblyInformationalVersion 属性中”时,究竟发生了什么?你只是在做一个 Visual Studio 构建,还是在使用类似 NAnt 或其他工具之类的工具? 我们使用 ruby​​ (rake) 来自动化我们的构建。我们的 rake 构建任务之一更新了解决方案中所有项目使用的 CommonAssemblyInfo.cs 文件。该任务使用 albacore 生成 CommonAssemblyInfo.cs 文件 - github.com/derickbailey/Albacore 任务设置的 AssemblyInfo 值之一是 AssemblyInformationalVersion。 @John Jesus - 正如 Lazy Badger 所建议的那样,您还可以在提交/合并等之后使用 git 挂钩来更改 AssemblyInfo.cs(这就是我最终要做的)。见kernel.org/pub/software/scm/git/docs/githooks.html 仅供参考,Albacore 已搬到新的中心组织:github.com/Albacore/albacore 以下项目 https://github.com/jeromerg/NGitVersion 提供了在 C# 和 C++ 项目的编译时生成 GlobalAssemblyInfo.* 文件的完整解决方案:默认情况下,生成的程序集版本包含:提交哈希,表示本地更改的标志,以及从存储库根到当前提交的提交量的增量。【参考方案4】:

另一种方法是使用 NetRevisionTool 和一些 On-Board Visual Studio 魔法。我将在这里为 Visual Studio 2013 专业版展示这一点,但这也适用于其他版本。

所以首先下载 NetRevisionTool。 您将 NetRevisionTool.exe 包含在您的 PATH 中或将其签入您的存储库并创建一个 Visual Studio 预构建和一个构建后操作并更改您的 AssemblyInfo.cs。

将您的 git-hash 添加到您的 AssemblyInformationVersion 的示例如下: 在您的项目设置中:

在项目的 AssemblyInfo.cs 中更改/添加以下行:

[程序集:AssemblyInformationalVersion("1.1.dmin:2015.chash:6!-branch")]

在显示的屏幕截图中,我检查了 External/bin 文件夹中的 NetRevisionTool.exe

构建后,如果您然后右键单击二进制文件并转到属性,那么您应该会看到如下内容:

希望这对那里的人有所帮助

【讨论】:

提交哈希对我来说总是以 00000 结尾。我认为这是因为我有未提交的更改但仍然相同。知道为什么吗? 问题是 NetRevision 没有找到我的 git 可执行文件。原因是因为我们使用的是 SourceTree,而 git 嵌入了它。解决方案是将 git.exe 和 libiconv-2.dll 从 %USERPROFILE%\AppData\Local\Atlassian\SourceTree\git_local\bin 复制到包含 NetRevision.exe 的文件夹中。我还必须像这样修改事件: Pre-build event: cd $(ProjectDir)Libraries NetRevisionTool.exe /patch $(ProjectDir) Post-build event: cd $(ProjectDir)Libraries NetRevisionTool.exe /restore $(ProjectDir) 仅供参考,项目repo URL前段时间已更改为github.com/ygoe/NetRevisionTool。更多信息也可通过unclassified.software/apps/netrevisiontool 获得。【参考方案5】:

我认为这个问题值得一步一步给出完整的答案。这里的策略是从预构建事件中运行一个 powershell 脚本,该脚本接受一个模板文件并生成一个 AssemblyInfo.cs 文件,其中包含 git 标记 + 提交计数信息。

第 1 步:在 Project\Properties 文件夹中创建一个 AssemblyInfo_template.cs 文件,基于您原来的 AssemblyInfo.cs,但包含:

[assembly: AssemblyVersion("$FILEVERSION$")]
[assembly: AssemblyFileVersion("$FILEVERSION$")]
[assembly: AssemblyInformationalVersion("$INFOVERSION$")]

第 2 步: 创建一个名为 InjectGitVersion.ps1 的 powershell 脚本,其来源为:

# InjectGitVersion.ps1
#
# Set the version in the projects AssemblyInfo.cs file
#


# Get version info from Git. example 1.2.3-45-g6789abc
$gitVersion = git describe --long --always;

# Parse Git version info into semantic pieces
$gitVersion -match '(.*)-(\d+)-[g](\w+)$';
$gitTag = $Matches[1];
$gitCount = $Matches[2];
$gitSHA1 = $Matches[3];

# Define file variables
$assemblyFile = $args[0] + "\Properties\AssemblyInfo.cs";
$templateFile =  $args[0] + "\Properties\AssemblyInfo_template.cs";

# Read template file, overwrite place holders with git version info
$newAssemblyContent = Get-Content $templateFile |
    %$_ -replace '\$FILEVERSION\$', ($gitTag + "." + $gitCount)  |
    %$_ -replace '\$INFOVERSION\$', ($gitTag + "." + $gitCount + "-" + $gitSHA1) ;

# Write AssemblyInfo.cs file only if there are changes
If (-not (Test-Path $assemblyFile) -or ((Compare-Object (Get-Content $assemblyFile) $newAssemblyContent))) 
    echo "Injecting Git Version Info to AssemblyInfo.cs"
    $newAssemblyContent > $assemblyFile;       

第 3 步:将 InjectGitVersion.ps1 文件保存到 BuildScripts 文件夹中的解决方案目录中

第 4 步:将以下行添加到项目的 Pre-Build 事件中

powershell -ExecutionPolicy ByPass -File  $(SolutionDir)\BuildScripts\InjectGitVersion.ps1 $(ProjectDir)

第 5 步:构建您的项目。

第 6 步:(可选)将 AssemblyInfo.cs 添加到您的 git 忽略文件中

【讨论】:

并且记得让你的 git 标签与文件版本兼容:比如 1.2.3。如果你有更复杂的标签,你将不得不解析出兼容的部分 而不是使用模板和 gitignoring 真正的 AssemblyInfo.cs 可以修改 AssemblyInfo.cs 到位,构建,然后 git 重置 AssemblyInfo.cs 到最后提交的版本。所以在 repo 中总会有AssemblyInfo.cs$..$ 只替换构建时间。 这很好用。我最终使用git describe --match "v[0-9]*" --long --always --dirty 过滤某些标签(那些包含版本号的标签)并指示工作树是否干净。 您还必须在 PS 脚本中修改您的 RegEx:$gitVersion -match '[v](.*)-(\d+)-[g](.+)$';【参考方案6】:

现在使用 .NET Revision Task for MSBuild 并使用 Visual Studio 2019 非常容易。

只需安装NuGet 包Unclassified.NetRevisionTask,然后按照GitHub documentation 中的说明在AssemblyInfo.cs 文件中配置您想要的信息。

如果你只想要最后一次提交的哈希(长度=8):

[assembly: AssemblyInformationalVersion("1.0-chash:8")]

构建您的项目/解决方案,您将拥有如下内容:

【讨论】:

要在 NET.core 应用程序中配置格式,请将 PropertyGroup 添加到 .csproj 文件中,如 README github.com/ygoe/NetRevisionTask/blob/master/README.md 中所示【参考方案7】:

由于另一个答案已经提到了 git 位,一旦你有了 SHA,你就可以考虑在预构建挂钩中生成项目的 AssemblyInfo.cs 文件。

一种方法是创建一个AssemblyInfo.cs.tmpl 模板文件,在 $$GITSHA$$ 中为您的 SHA 提供一个占位符,例如

[assembly: AssemblyDescription("$$GITSHA$$")]

然后,您的预构建挂钩必须替换此占位符并输出 AssemblyInfo.cs 文件以供 C# 编译器拾取。

要了解如何使用 SubWCRev for SVN 完成此操作,请参阅this answer。为 git 做类似的事情应该不难。

如前所述,其他方式将是“制作阶段”,即编写执行类似操作的 MSBuild 任务。另一种方法可能是以某种方式对 DLL 进行后处理(ildasm+ilasm 说),但我认为上面提到的选项可能是最简单的。

【讨论】:

@Wint 不,不要将生成的 AssemblyInfo.cs 添加到 git。如果你这样做,就不可能进行非脏构建:P “为 git 做类似的事情应该不难。” TortoiseGit 的 GitWCRev 呢? ;-) tortoisegit.org/docs/tortoisegit/tgit-gitwcrev.html【参考方案8】:

对于完全自动化和灵活的方法检查https://github.com/Fody/Stamp。我们已经成功地将它用于我们的 Git 项目(以及用于 SVN 项目的 this version)

更新:由于 Stamp.Fody 不再维护,因此已过时

【讨论】:

在 Stamp.Fody 的 github 页面上说:“这个项目不再维护。”。将它包含在我的项目中引发了 CA0052 和 CA0055【参考方案9】:

您可以使用 powershell one-liner 来使用提交哈希更新所有 assemblyinfo 文件。

$hash = git describe --long --always;gci **/AssemblyInfo.* -recurse | foreach  $content = (gc $_) -replace "\[assembly: Guid?.*", "$&`n[assembly: AssemblyMetadata(`"commithash`", `"$hash`")]" | sc $_ 

【讨论】:

【参考方案10】:
    希望您知道如何在构建时调用外部程序并截取输出。 希望您知道如何在 git 的工作目录中忽略未版本控制的文件。

正如@learath2 所述,git rev-parse HEAD 的输出将为您提供纯哈希。

如果你在 Git-repository 中使用标签(而且你使用标签,是不是比 git rev-parse 更具描述性和可读性),可能会从 git describe 接收输出(同时在 git checkout 中也成功使用)

你可以调用 rev-parse|describe in:

一些制作舞台 在提交后挂钩中 在污迹过滤器中,如果您选择smudge/clean filters 实现方式

【讨论】:

【参考方案11】:

我正在使用已接受的答案和一个小补充的组合。 我安装了 AutoT4 扩展 (https://marketplace.visualstudio.com/items?itemName=BennorMcCarthy.AutoT4),以便在构建之前重新运行模板。

从 GIT 获取版本

我在项目属性的预构建事件中有git -C $(ProjectDir) describe --long --always &gt; "$(ProjectDir)git_version.txt"。 将 git_version.txt 和 VersionInfo.cs 添加到 .gitignore 是一个不错的主意。

在元数据中嵌入版本

我在我的项目中添加了一个VersionInfo.tt 模板:

<#@ template debug="false" hostspecific="true" language="C#" #>
<#@ assembly name="System.Core" #>
<#@ import namespace="System.Linq" #>
<#@ import namespace="System.Text" #>
<#@ import namespace="System.Collections.Generic" #>
<#@ import namespace="System.IO" #>
<#@ output extension=".cs" #>

using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;

<#
if (File.Exists(Host.ResolvePath("git_version.txt")))

    Write("[assembly: AssemblyInformationalVersion(\""+ File.ReadAllText(Host.ResolvePath("git_version.txt")).Trim() + "\")]");
else
    Write("// version file not found in " + Host.ResolvePath("git_version.txt"));


#>

现在我在“ProductVersion”中有我的 git 标签 + 哈希。

【讨论】:

【参考方案12】:

另一种方法是从 Pre-Build 步骤生成一个 Version.cs 文件。我在一个小的概念验证项目中对此进行了探索,该项目会打印出其当前的提交哈希。

项目已上传至https://github.com/sashoalm/GitCommitHashPrinter。

创建 Version.cs 文件的批处理代码是这样的:

@echo off

echo "Writing Version.cs file..."

@rem Pushd/popd are used to temporarily cd to where the BAT file is.
pushd $(ProjectDir)

@rem Verify that the command succeeds (i.e. Git is installed and we are in the repo).
git rev-parse HEAD || exit 1

@rem Syntax for storing a command's output into a variable (see https://***.com/a/2340018/492336).
@rem 'git rev-parse HEAD' returns the commit hash.
for /f %%i in ('git rev-parse HEAD') do set commitHash=%%i

@rem Syntax for printing multiline text to a file (see https://***.com/a/23530712/492336).
(
echo namespace GitCommitHashPrinter
echo 
echo     class Version
echo     
echo         public static string CommitHash  get; set;  = "%commitHash%";
echo     
echo 
)>"Version.cs"

popd    

【讨论】:

【参考方案13】: 打开.csproj并将&lt;GenerateAssemblyInfo&gt;false&lt;/GenerateAssemblyInfo&gt;添加到第一个PropertyGroup 您可能希望将已生成的AssemblyInfo.cs 的内容复制到obj 文件夹中,这样您就不必自己编写所有内容。 在属性文件夹中创建AssemblyInfo.tt(T4 模板)。 粘贴以下内容 + 之前自动生成的 AssemblyInfo.cs 的旧内容
<#@ template debug="true" hostspecific="True" language="C#" #>
<#@ assembly name="System.Core" #>
<# /*There's a bug with VS2022 where you have to be real specific about envDTE.*/ #>
<#@ assembly name="./PublicAssemblies/envdte.dll" #>  
<#@ import namespace="System.IO" #>
<#@ import namespace="System.Text.RegularExpressions" #>
<#@ import namespace="System.Globalization" #>
<#@ output extension=".cs" #>
<#
    var dte = ((IServiceProvider)this.Host).GetService(typeof(EnvDTE.DTE)) as EnvDTE.DTE;
    string buildConfig = dte.Solution.SolutionBuild.ActiveConfiguration.Name;
    string solutionDirectory = Path.GetDirectoryName(dte.Solution.FullName);

    var (gitRevision, gitBranch, gitCompactRevision) = ("", "", "");

    using(var process = new System.Diagnostics.Process() 
        StartInfo = new System.Diagnostics.ProcessStartInfo() 
            WorkingDirectory = solutionDirectory,
            FileName = @"cmd.exe",
            Arguments = "/C git rev-parse HEAD & git rev-parse --abbrev-ref HEAD",
            RedirectStandardError = true,
            RedirectStandardOutput = true,
            UseShellExecute = false,
            CreateNoWindow = true
        
    ) 
        process.Start();
        string[] lines = process.StandardOutput.ReadToEnd().Split();
        gitRevision = lines[0].Trim();
        gitBranch = lines[1].Trim();
        gitCompactRevision = gitRevision.Substring(0, 6);
    
    string appPurpose         = "Launcher"; // & Updater
    string companyShort       = "todo";
    string companyFull        = "todo";
    string productNameShort   = "todo";
    string productName        = $"companyShort productNameShort";
    string fileName           = $"companyShortproductNameShort";
    string exeNAME            = $"fileNameLaunch";
    string originalFilename   = $"exeNAME.exe";
    string CALLEXE            = $"fileName.exe";
    string BROWSEREXE         = $"fileNameBrowser.exe";
    string FULLINSTALLER      = $"fileNameSetup.exe";

    DateTime dtBuiltDate      = DateTime.UtcNow;
    string cBuildYear         = dtBuiltDate.Year.ToString();
    string cBuildDay          = dtBuiltDate.ToString("dd");
    string cBuildMonth        = dtBuiltDate.ToString("MM");
    string cBuildTime         = dtBuiltDate.ToString("T", DateTimeFormatInfo.InvariantInfo);
    string assemblyVersion    = $"3.0.cBuildYear.cBuildMonthcBuildDay";

    string JOB_NAME           = System.Environment.GetEnvironmentVariable("JOB_NAME") ?? "0.0";
    string buildVersion       = System.Environment.GetEnvironmentVariable("BUILD_NUMBER") ?? "0-dev";
    string buildSeries        = Regex.Replace(JOB_NAME, @"[^0-9\.]+", "");
    string buildNumber        = Regex.Replace(buildVersion, @"[^0-9\.]+", "");
    string InternalVersion    = $"JOB_NAME.buildVersion";
    string fileVersion        = Regex.Replace(InternalVersion, @"[^0-9\.]+", "");
#>
using System.Reflection;

[assembly: System.Runtime.InteropServices.ComVisible(false)]
[assembly: System.Resources.NeutralResourcesLanguageAttribute("en")]
[assembly: AssemblyConfigurationAttribute("<#= buildConfig #>")]
[assembly: AssemblyProduct("<#= productName #>")]
[assembly: AssemblyTitle("<#= $"companyShortproductNameShort" #>")]
[assembly: AssemblyCompany("<#= companyFull #>")]
[assembly: AssemblyDescription("<#= $"companyShort productNameShort .... appPurpose - ...... by companyFull" #>")]
[assembly: AssemblyCopyright("<#= $"© 1983-cBuildYear companyFull" #>")]
[assembly: AssemblyTrademark("<#= $"productName is a trademark of companyFull, Inc." #>")]
[assembly: AssemblyInformationalVersion("<#= InternalVersion #>")]
[assembly: AssemblyVersion("<#= assemblyVersion #>")]
[assembly: AssemblyFileVersion("<#= fileVersion #>")]
[assembly: AssemblyMetadataAttribute("OriginalFilename",    "<#= originalFilename #>")]
[assembly: AssemblyMetadataAttribute("NAME",                "<#= $"productName appPurpose" #>")]
[assembly: AssemblyMetadataAttribute("EXENAME",             "<#= exeNAME #>")]
[assembly: AssemblyMetadataAttribute("DIRNAME",             "<#= productNameShort #>")]
[assembly: AssemblyMetadataAttribute("CALLEXE",             "<#= $"fileName.exe" #>")]
[assembly: AssemblyMetadataAttribute("BROWSEREXE",          "<#= $"fileNameBrowser.exe" #>")]
[assembly: AssemblyMetadataAttribute("FULLINSTALLER",       "<#= $"fileNameSetup.exe" #>")]
[assembly: AssemblyMetadataAttribute("COMPANY",             "<#= companyFull #>")]
[assembly: AssemblyMetadataAttribute("License",             "<#= $"Contains copyrighted code and applications ..." #>")]
[assembly: AssemblyMetadataAttribute("TermsOfUse",          "<#= "https://www.company.com/en-us/terms-of-use/" #>")]
[assembly: AssemblyMetadataAttribute("Website",             "<#= "https://www.company.com/en-us" #>")]
[assembly: AssemblyMetadataAttribute("UpdateURL",           "https://subdomain.product.net/version_check")]

[assembly: AssemblyMetadataAttribute("BuildYear",           "<#= cBuildYear #>")]
[assembly: AssemblyMetadataAttribute("BuildDay",            "<#= cBuildDay #>")]
[assembly: AssemblyMetadataAttribute("BuildMonth",          "<#= cBuildMonth #>")]
[assembly: AssemblyMetadataAttribute("BuildTime",           "<#= cBuildTime #>")]
[assembly: AssemblyMetadataAttribute("DateModified",        "<#= $"dtBuiltDate.ToString("MMM dd, yyyy", DateTimeFormatInfo.InvariantInfo) at cBuildTime" #>")]

[assembly: AssemblyMetadataAttribute("BuildSeries",         "<#= buildSeries #>")]
[assembly: AssemblyMetadataAttribute("BuildNumber",         "<#= buildNumber #>")]
[assembly: AssemblyMetadataAttribute("BuildDate",           "<#= dtBuiltDate.ToString("s") #>")]
[assembly: AssemblyMetadataAttribute("BuildMachine",        "<#= Environment.MachineName #>")]
[assembly: AssemblyMetadataAttribute("BuildMachineUser",    "<#= Environment.UserName #>")]
[assembly: AssemblyMetadataAttribute("BuildOSVersion",      "<#= Environment.OSVersion #>")]
[assembly: AssemblyMetadataAttribute("BuildPlatform",       "<#= Environment.OSVersion.Platform #>")]
[assembly: AssemblyMetadataAttribute("BuildClrVersion",     "<#= Environment.Version #>")]

[assembly: AssemblyMetadataAttribute("BuildBranch",         "<#= gitBranch #>")]
[assembly: AssemblyMetadataAttribute("BuildRevision",       "<#= gitCompactRevision #>")]
[assembly: AssemblyMetadataAttribute("CommitHash",          "<#= gitRevision #>")]
[assembly: AssemblyMetadataAttribute("RepositoryUrl",       "")]
[assembly: AssemblyMetadataAttribute("RepositoryType",      "")]
<#+

#>

您现在可以使用 C# 的全部功能来生成您想要的任何内容,例如您当前所在的 git 分支和修订版。一些提示:

可以在&lt;# #&gt; 块内的任何位置声明变量 您希望使用的任何方法都必须在文件末尾的&lt;#+ #&gt; 块中声明。 (+ 符号很重要,必须是文件末尾的最后一个)) &lt;# #&gt; 块之外的所有内容都只是纯文本。 VS2019 没有语法高亮或智能感知。 .tt 文件是纯文本。我建议在安装 T4 Support 扩展后使用 vscode 编辑它(在 vs2019 中不可用...)

【讨论】:

【参考方案14】:

参考另一个答案 (https://***.com/a/44278482/4537127),我还使用 VersionInfo.tt 文本模板在没有 AutoT4 的情况下生成 AssemblyInformationalVersion

(至少可以在我的 C# WPF 应用程序中使用)

问题是预构建事件是在模板转换之后运行的,所以在克隆之后,git_version.txt 文件不存在并且构建失败。 在手动创建它以允许转换通过一次后,它在转换后被更新,并且总是一个提交后

我必须对 .csproj 文件进行两次调整(这至少适用于 Visual Studio Community 2017)

1) 导入文本转换目标并进行模板转换以在每个构建上运行:(Ref https://msdn.microsoft.com/en-us/library/ee847423.aspx)

<PropertyGroup>
    <VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">15.0</VisualStudioVersion>
    <VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
    <TransformOnBuild>true</TransformOnBuild>
    <TransformOutOfDateOnly>false</TransformOutOfDateOnly>
</PropertyGroup>

&lt;Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /&gt;之后

<Import Project="$(VSToolsPath)\TextTemplating\Microsoft.TextTemplating.targets" />

2) 使git describe 在模板转换之前运行(这样git_version.txtVersionInfo.tt 转换时就在那里):

<Target Name="PreBuild" BeforeTargets="ExecuteTransformations">
  <Exec Command="git -C $(ProjectDir) describe --long --always --dirty &gt; $(ProjectDir)git_version.txt" />
</Target>

..以及获取AssemblyInformationalVersion的C#代码(参考https://***.com/a/7770189/4537127)

public string AppGitHash

    get
    
        AssemblyInformationalVersionAttribute attribute = (AssemblyInformationalVersionAttribute)Assembly.GetExecutingAssembly().GetCustomAttributes(typeof(AssemblyInformationalVersionAttribute), false).FirstOrDefault();

        return attribute.InformationalVersion;
    

..并将生成的文件添加到.gitignore

VersionInfo.cs
git_version.txt

【讨论】:

【参考方案15】:

地点

<Target Name="UpdateVersion" BeforeTargets="CoreCompile">
  <Exec Command="php &quot;$(SolutionDir)build.php&quot; $(SolutionDir) &quot;$(ProjectDir)Server.csproj&quot;" />
</Target>

YOUR_PROJECT_NAME.csproj

<?php

function between(string $string, string $after, string $before, int $offset = 0) : string
    return substr($string, $pos = strpos($string, $after, $offset) + strlen($after),
        strpos($string, $before, $pos) - $pos);


$pipes = [];
$proc = proc_open("git rev-parse --short HEAD", [
    0 => ["pipe", "r"],
    1 => ["pipe", "w"],
    2 => ["pipe", "w"]
], $pipes, $argv[1]);

if(is_resource($proc))
    $rev = stream_get_contents($pipes[1]);
    proc_close($proc);


$manifest = file_get_contents($argv[2]);
$version = between($manifest, "<Version>", "</Version>");
$ver = explode("-", $version)[0] . "-" . trim($rev);
file_put_contents($argv[2], str_replace($version, $ver, $manifest));

echo "New version generated: $ver" . PHP_EOL;

【讨论】:

【参考方案16】:

受到@John Jesus 回答的极大启发,我创建了一个 Powershell v1 脚本,该脚本在每个 Build 上运行,以将 Assembly Version 调整为当前的 Git 标签。

Powershell 脚本

# Get build running directory
$scriptPath = split-path -parent $MyInvocation.MyCommand.Path
try 
    $v = git describe --tags

catch [System.Management.Automation.CommandNotFoundException] 
    # Git not found
    exit


# Letters are incompatible with AssemblyVersion.cs so we remove them
$v = $v -replace "v", ""
# Version format is major[.minor[.build[.revision]] so we remove them
$v = $v -replace "-(\D.*)", ''
$v = $v -replace "-", '.'

# We replace versions inside AssemblyInfo.cs content
$info = (Get-Content ($scriptPath + "/properties/AssemblyInfo.cs"))
$av = '[assembly: AssemblyVersion("'+$v+'")]'
$avf = '[assembly: AssemblyFileVersion("'+$v+'")]'
$info = $info -replace '\[assembly: AssemblyVersion\("(.*)"\)]', $av
$info = $info -replace '\[assembly: AssemblyFileVersion\("(.*)"\)]', $avf
Set-Content -Path ($scriptPath + "/properties/AssemblyInfo.cs") -Value $info -Encoding UTF8

将它放在您的解决方案文件夹中并设置一个 Prebuild Event 来启动它:

【讨论】:

【参考方案17】:

我将这些文件部署在我们的开发/暂存系统上以便快速查看:

git.exe -C "$(ProjectDir.TrimEnd('\'))" describe --long > "$(ProjectDir)_Version.info":

我的结果:10.02.0.3-247-gbeeadd082

git.exe -C "$(ProjectDir.TrimEnd('\'))" branch --show-current > "$(ProjectDir)_Branch.info"

我的结果:功能/JMT-3931-jaguar

(Visual Studio 预构建事件)

【讨论】:

以上是关于在 .Net dll 中嵌入 git commit hash的主要内容,如果未能解决你的问题,请参考以下文章

sh Git向DataPlist iOS提交数据(本Tenseg博客文章中讨论的代码:http://www.tenseg.net/blog/2017/08/03/git-commit-info-in-

嵌入式 Mysql (libmysql.dll) 和 C# & Asp.Net

xsltc:如何在 DLL 中嵌入 XML 文件

如何在 VS 创建的 exe 中嵌入引用?

可以将 DLL 合并到 .NET EXE 中吗? [复制]

将一个 dll 作为嵌入式资源嵌入到另一个 dll 中,然后从我的代码中调用它