在.Net dll中嵌入git commit hash

时间:2013-02-28 17:00:53

标签: c# git

我正在构建一个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 );
    }
}

请注意,这必须在构建时完成,而不是运行时,因为我部署的可执行文件不会访问git repo。

可以找到C ++的相关问题here

修改

根据@ mattanja的请求,我发布了我在项目中使用的git hook脚本。设置:

  • 挂钩是linux shell脚本,位于: path_to_project \ .git \ hooks
  • 如果您使用的是msysgit hooks 文件夹中已包含一些示例脚本。为了让git调用它们,请从脚本名称中删除“.sample”扩展名。
  • 钩子脚本的名称与调用它们的事件匹配。就我而言,我修改了提交后合并后
  • 我的 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

希望这有帮助。

14 个答案:

答案 0 :(得分:63)

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

以下是步骤:

使用构建事件来调用git

  • 右键单击项目,然后选择“属性”

  • 在构建事件中,添加包含(注意引号)的预构建事件:

    " C:\ Program Files \ Git \ bin \ git.exe" describe --long> " $(PROJECTDIR)\ version.txt"

    这将在项目目录中创建 version.txt 文件。

将version.txt嵌入可执行文件

  • 右键单击项目并选择Add Existing Item
  • 添加 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();
        }
    }
}

答案 1 :(得分:47)

我们在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资源管理器中查看版本:

enter image description here

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

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

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

答案 2 :(得分:21)

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

这个nuget包实现了一个纯粹的" MSBuild解决方案。如果您不依赖于nuget包,则只需将这些Targets复制到csproj文件中,它应该包含git hash作为自定义程序集属性:

<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>

这里有两个目标。第一个,&#34; GetGitHash&#34;,将git哈希加载到名为BuildHash的MSBuild属性中,如果尚未定义BuildHash,则执行此操作。如果您愿意,这允许您在命令行上将其传递给MSBuild。您可以将其传递给MSBuild,如下所示:

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

第二个目标&#34; WriteGitHash&#34;,将哈希值写入临时&#34; obj&#34;名为&#34; CustomAssemblyInfo.cs&#34;的文件夹。该文件将包含如下所示的行:

[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;
    }
}

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

答案 3 :(得分:12)

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

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

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

enter image description here

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

[assembly:AssemblyInformationalVersion(&#34; 1.1。{dmin:2015}。{chash:6} {!} - {branch}&#34;)]

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

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

enter image description here

希望这有助于那里的人

答案 4 :(得分:11)

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

步骤1:根据原始的AssemblyInfo.cs在Project \ Properties文件夹中创建AssemblyInfo_template.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步:将以下行添加到项目的预建活动中

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

第5步:构建项目。

步骤6:(可选)将AssemblyInfo.cs添加到您的git ignore文件

答案 5 :(得分:4)

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

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

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

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

构建您的项目/解决方案,您将获得以下内容:

enter image description here

答案 6 :(得分:3)

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

执行此操作的一种方法是创建一个AssemblyInfo.cs.tmpl模板文件,其中包含SAL的占位符,例如$$ GITSHA $$,例如

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

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

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

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

答案 7 :(得分:2)

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

答案 8 :(得分:0)

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

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

答案 9 :(得分:0)

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

从GIT获取版本

我在项目属性中的预构建事件中有conf。 将git_version.txt和VersionInfo.cs添加到.gitignore是个不错的主意。

在元数据中嵌入版本

我在项目中添加了git -C $(ProjectDir) describe --long --always > "$(ProjectDir)git_version.txt"模板:

VersionInfo.tt

现在我在&#34; ProductVersion&#34;中有我的git标签+ hash。

答案 10 :(得分:0)

参考另一个答案(https://stackoverflow.com/a/44278482/4537127)我还使用VersionInfo.tt文字模板生成AssemblyInformationalVersion而没有使用AutoT4。

(Atleast在我的C#WPF应用程序中工作)

问题是预构建事件是在模板转换后运行的,因此在克隆之后,git_version.txt文件不在那里并且构建失败。 手动创建它以允许转换传递一次后,它在转换后更新,并始终一个提交

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

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>

以及<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />

之后
<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>

..和C#代码获取AssemblyInformationalVersion(参考https://stackoverflow.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

答案 11 :(得分:0)

另一种方法是从“预构建”步骤生成Version.cs文件。我在一个概念验证项目中对此进行了探索,该项目显示了其当前的提交哈希。

Tha项目已上传到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://stackoverflow.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://stackoverflow.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    

答案 12 :(得分:0)

放置

<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;

答案 13 :(得分:-1)

  1. 我希望您知道如何在构建时调用外部程序并拦截输出。
  2. 我希望你知道如何在git的工作目录中忽略无版本文件。
  3. 如@ learath2所述,git rev-parse HEAD的输出将为您提供简单的哈希值。

    如果您在Git-repository中使用标签(并且您使用标签,它不是git rev-parse更具描述性和可读性),则可以从git describe收到输出(同时也可以在以后成功使用) git checkout

    您可以在:

    中调用rev-parse | describe
    • 一些制作阶段
    • in post-commit hook
    • 在涂抹过滤器中,如果您选择smudge/clean filters实施方式