Embed git commit hash in a .Net dll

C#Git

C# Problem Overview


I'm building a C# application, using Git as my version control.

Is there a way to automatically embed the last commit hash in the executable when I build my application?

For example, printing the commit hash to console would look something like:

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

Note that this has to be done at build time, not runtime, as my deployed executable will not have the git repo accessible.

A related question for C++ can be found here.

EDIT

Per @mattanja's request, I'm posting the git hook script I use in my projects. The setup:

  • The hooks are linux shell scripts, which are placed under: path_to_project\.git\hooks
  • If you are using msysgit, the hooks folder already contains some sample scripts. In order to make git call them, remove the '.sample' extension from the script name.
  • The names of the hook scripts match the event that invokes them. In my case, I modified post-commit and post-merge.
  • My AssemblyInfo.cs file is directly under the project path (same level as the .git folder). It contains 23 lines, and I use git to generate the 24th.

As my linux-shelling a bit rusty, the script simply reads the first 23-lines of AssemblyInfo.cs to a temporary file, echos the git hash to the last line, and renames the file back to AssemblyInfo.cs. I'm sure there are better ways of doing this:

#!/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

Hope this helps.

C# Solutions


Solution 1 - C#

You can embed a version.txt file into the executable and then read the version.txt out of the executable. To create the version.txt file, use git describe --long

Here are the steps:

Use a Build Event to call git

  • Right-click on the project and select Properties

  • In Build Events, add Pre-Build event containing (notice the quotes):

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

    That will create a version.txt file in your project directory.

Embed the version.txt in the executable

  • Right click on the project and select Add Existing Item
  • Add the version.txt file (change the file chooser filter to let you see All Files)
  • After version.txt is added, right-click on it in the Solution Explorer and select Properties
  • Change the Build Action to Embedded Resource
  • Change Copy to Output Directory to Copy Always
  • Add version.txt to your .gitignore file

Read the embedded text file version string

Here's some sample code to read the embedded text file version string:

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

Solution 2 - C#

UPDATE:

Things have evolved since I originally answered this question. The Microsoft.NET.Sdk (meaning you must be using an sdk-style project) now includes support for adding the commit hash to both the assembly informational version as well as to the nuget package metadata, if some conditions are met:

  1. The <SourceRevisionId> property must be defined. This can be done by adding a target like this:
<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>

This target executes a command that will set SourceRevisionId to be the abbreviated (8 character) hash. The BeforeTargets causes this to be run before the assembly informational version is created.

  1. To include the hash in the nuget package metadata, the <RepositoryUrl> must also be defined.

  2. <SourceControlInformationFeatureSupported> property must be true, this causes the nuget pack task to pick up the SourceRevisionId as well.

I would steer people away from using the MSBuildGitHash package, since this new technique is cleaner and most consistent.

ORIGINAL:

I've created a simple nuget package that you can include in your project which will take care of this for you: https://www.nuget.org/packages/MSBuildGitHash/

This nuget package implements a "pure" MSBuild solution. If you'd rather not depend on a nuget package you can simply copy these Targets into your csproj file and it should include the git hash as a custom assembly attribute:

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

There are two targets here. The first one, "GetGitHash", loads the git hash into an MSBuild property named BuildHash, it only does this if BuildHash is not already defined. This allows you to pass it to MSBuild on the command line, if you prefer. You could pass it to MSBuild like so:

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

The second target, "WriteGitHash", will write the hash value to a file in the temporary "obj" folder named "CustomAssemblyInfo.cs". This file will contain a line that looks like:

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

This CustomAssemblyInfo.cs file will be compiled into your assembly, so you can use reflection to look for the AssemblyMetadata at runtime. The following code shows how this can be done when the AssemblyInfo class is included in the same assembly.

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

Some benefits to this design is that it doesn't touch any files in your project folder, all the mutated files are under the "obj" folder. Your project will also build identically from within Visual Studio or from the command line. It can also be easily customized for your project, and will be source controlled along with your csproj file.

Solution 3 - C#

We use tags in git to track versions.

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

You can get the version with hash from git via:

git describe --long

Our build process puts the git hash in the AssemblyInformationalVersion attribute of the AssemblyInfo.cs file:

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

Once you compile, you can view the version from windows explorer:

enter image description here

You can also get it programmatically via:

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

where YOURTYPE is any Type in the Assembly that has the AssemblyInformationalVersion attribute.

Solution 4 - C#

I think this question is worth giving a complete step by step answer. The strategy here to is run a powershell script from the pre-build events that takes in a template file and generates an AssemblyInfo.cs file with the git tag + commit count information included.

Step 1: make an AssemblyInfo_template.cs file in the Project\Properties folder, based on your original AssemblyInfo.cs but containing:

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

Step 2: Create a powershell script named InjectGitVersion.ps1 whose source is:

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

Step 3: Save the InjectGitVersion.ps1 file to your solution directory in a BuildScripts folder

Step 4: Add the following line to the project's Pre-Build events

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

Step 5: Build your project.

Step 6: Optionally, add AssemblyInfo.cs to your git ignore file

Solution 5 - C#

Another way to do this is to use the NetRevisionTool with some On-Board Visual Studio magic. I will showcase this here for Visual Studio 2013 Professional Edition, but this will work with other versions as well.

So first download the NetRevisionTool. You include the NetRevisionTool.exe in your PATH or check it in into your repository and create a visual studio pre-build and a post-build action and change your AssemblyInfo.cs.

An example that would add your git-hash to your AssemblyInformationVersion would be the following: In your project settings:

enter image description here

in the AssemblyInfo.cs of your project you change/add the line:

[assembly: AssemblyInformationalVersion("1.1.{dmin:2015}.{chash:6}{!}-{branch}")]

in the shown screenshot i checked in NetRevisionTool.exe in the External/bin folder

After build, if you then right-click your binary and go to properties then you should see something like the following:

enter image description here

Hope this helps somebody out there

Solution 6 - C#

It's now very easy with .NET Revision Task for MSBuild and working with Visual Studio 2019.

Simply install the NuGet package Unclassified.NetRevisionTask, then configure the information you want in theAssemblyInfo.cs file as described in the GitHub documentation.

If you only want the hash of the last commit (length=8):

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

Build your project/solution and you'll have something like this:

enter image description here

Solution 7 - C#

As the other answer already mentions the git bit, once you have the SHA you can consider generating the AssemblyInfo.cs file of your project in a pre-build hook.

One way to do this is to create an AssemblyInfo.cs.tmpl template file, with a placeholder for your SHA in say $$GITSHA$$, e.g.

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

Your pre build hook then has to replace this placeholder and output the AssemblyInfo.cs file for the C# compiler to pick up.

To see how this can be done using SubWCRev for SVN see this answer. It shouldn't be hard to do something similar for git.

Other ways would be a "make stage" as mentioned, i.e. write an MSBuild task that does something similar. Yet another way may be to post process the DLL somehow (ildasm+ilasm say), but I think the options mentioned above are probably easiest.

Solution 8 - C#

For a fully automated and flexible method checkout https://github.com/Fody/Stamp. We've successfully used this for our Git projects (as well as the this version for SVN projects)

Update: This is outdated since Stamp.Fody is no longer maintained

Solution 9 - C#

You can use a powershell one-liner to update all assemblyinfo files with the commit hash.

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

Solution 10 - C#

  1. I hope you know how to call external programs and intercept output at the build-time.
  2. I hope you know how to have in git's working directory ignore unversioned files.

As noted by @learath2, output of git rev-parse HEAD will give you plain hash.

If you use tags in Git-repository (and you use tags, isn't it more descriptive and readable than git rev-parse), output may be received from git describe (while also successfully used later in git checkout)

You can call rev-parse|describe in:

  • some make stage

  • in post-commit hook

  • in smudge filter, if you'll select smudge/clean filters way of implementation

Solution 11 - C#

I'm using a combination of the accepted answer and a small adition. I have th AutoT4 extension installed (https://marketplace.visualstudio.com/items?itemName=BennorMcCarthy.AutoT4) to re-run the templates before build.

getting version from GIT

I have git -C $(ProjectDir) describe --long --always > "$(ProjectDir)git_version.txt" in my pre-build event in project properties. Adding git_version.txt and VersionInfo.cs to .gitignore is quite a good idea.

embedding version in metadata

I have added a VersionInfo.tt template to my project:

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

#>

Now I have my git tag + hash in "ProductVersion".

Solution 12 - C#

Another way would be to generate a Version.cs file from a Pre-Build step. I explored this in a little proof-of-concept project which prints out its current commit hash.

Tha project is uploaded on https://github.com/sashoalm/GitCommitHashPrinter.

The batch code which creates the Version.cs file is this:

@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    

Solution 13 - C#

  • Open the .csproj and add <GenerateAssemblyInfo>false</GenerateAssemblyInfo> to the first PropertyGroup
    • You may want to copy the contents of the already generated AssemblyInfo.cs in the obj folder so you don't have to write everything yourself.
  • Create AssemblyInfo.tt (T4 template) in the properties folder.
  • Paste the following contents + the old contents of your previously auto generated 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           = $"{companyShort}{productNameShort}";
    string exeNAME            = $"{fileName}Launch";
    string originalFilename   = $"{exeNAME}.exe";
    string CALLEXE            = $"{fileName}.exe";
    string BROWSEREXE         = $"{fileName}Browser.exe";
    string FULLINSTALLER      = $"{fileName}Setup.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}.{cBuildMonth}{cBuildDay}";

    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("<#= $"{companyShort}{productNameShort}" #>")]
[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",          "<#= $"{fileName}Browser.exe" #>")]
[assembly: AssemblyMetadataAttribute("FULLINSTALLER",       "<#= $"{fileName}Setup.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",      "")]
<#+

#>

You can now use the full power of C# to generate whatever you want, such as the git branch and revision you're currently on. Some tips:

  • Variables can be declared anywhere inside a <# #> block
  • Any methods you wish to use must be declared at the end of the file in a <#+ #> block. (The + sign is very important and it must be the last thing at the end of the file))
  • Everything outside of <# #> blocks is just plain text.
  • VS2019 has no syntax highlighting or intellisense. The .tt file is plain text. I recommend editing it with vscode after installing T4 Support extension (not available in vs2019...)

Solution 14 - C#

Referring to the another answer (https://stackoverflow.com/a/44278482/4537127) i also utilised the VersionInfo.tt text template to generate AssemblyInformationalVersion without AutoT4.

(Atleast works in my C# WPF application)

Problem was that the Pre-build events were run after template transformations, so after cloning, the git_version.txt file was not there and build fails. After creating it manually to allow transformation to pass once, it was updated after transformation, and was always one commit behind.

I had to make two adjustments to the .csproj file (this applies at least for Visual Studio Community 2017)

  1. Import the Text Transformation Targets and make template transformations to run on every build: (Ref https://msdn.microsoft.com/en-us/library/ee847423.aspx)

    15.0 $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) true false

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

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

2) Make the git describe run before template transformations (so that git_version.txt is there when VersionInfo.tt is transformed) :

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

..And the C# code to get the AssemblyInformationalVersion (Ref https://stackoverflow.com/a/7770189/4537127)

public string AppGitHash
{
    get
    {
        AssemblyInformationalVersionAttribute attribute = (AssemblyInformationalVersionAttribute)Assembly.GetExecutingAssembly().GetCustomAttributes(typeof(AssemblyInformationalVersionAttribute), false).FirstOrDefault();

        return attribute.InformationalVersion;
    }
}

..And add the generated files to .gitignore

VersionInfo.cs
git_version.txt

Solution 15 - C#

Place

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

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

Solution 16 - C#

Heavily inspired by @John Jesus answer, I've created a Powershell v1 script that runs on each Build to adjust the Assembly Version to the current Git tag.

The Powershell script

# 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

Place it your solution folder and set a Prebuild Event to launch it: prebuild-event

Solution 17 - C#

I deploy these files on our dev/staging systems to have a quick look:

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

MyResult: 10.02.0.3-247-gbeeadd082

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

MyResult: feature/JMT-3931-jaguar

(Visual Studio PreBuild Events)

Solution 18 - C#

Here is a simple solution that works in Visual Studio 2019 and gets the git commit hash directly into the C# file. Add the following C# code to your solution:

namespace MyNameSpace
{

    [System.AttributeUsage(System.AttributeTargets.Assembly, Inherited = false, AllowMultiple = false)]
    sealed class GitHashAttribute : System.Attribute
    {
        public string Hash { get; }
        public GitHashAttribute(string hsh)
        {
            this.Hash = hsh;
        }
    }
    var hash = Assembly.GetEntryAssembly().GetCustomAttribute<GitHashAttribute>().Hash;
}

Variable hash is then going to contain the desired string if you add the following lines to your .csproj file.

<Target Name="SetSourceRevisionId" BeforeTargets="InitializeSourceControlInformation">
	<Exec Command="git.exe describe --long --always --dirty --exclude='*' --abbrev=40"
		  ConsoleToMSBuild="True" IgnoreExitCode="False">
		<Output PropertyName="SourceRevisionId" TaskParameter="ConsoleOutput" />
	</Exec>
</Target>

<Target Name="SetHash" AfterTargets="InitializeSourceControlInformation">
  <ItemGroup>
	<AssemblyAttribute Include="MyNameSpace.GitHashAttribute">
		<_Parameter1>$(SourceRevisionId)</_Parameter1>
	</AssemblyAttribute>
  </ItemGroup>
</Target>

Make sure than MyNameSpace in both files matches your demand. The important point here is that the ItemGroup has to be embedded into a Target with appropriate AfterTargets set.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionbavazaView Question on Stackoverflow
Solution 1 - C#John JesusView Answer on Stackoverflow
Solution 2 - C#MarkPflugView Answer on Stackoverflow
Solution 3 - C#HandcraftsmanView Answer on Stackoverflow
Solution 4 - C#Atilio JobsonView Answer on Stackoverflow
Solution 5 - C#schmendrickView Answer on Stackoverflow
Solution 6 - C#krlzlxView Answer on Stackoverflow
Solution 7 - C#MarcusView Answer on Stackoverflow
Solution 8 - C#mamuesstackView Answer on Stackoverflow
Solution 9 - C#roh85View Answer on Stackoverflow
Solution 10 - C#Lazy BadgerView Answer on Stackoverflow
Solution 11 - C#jelinek.01View Answer on Stackoverflow
Solution 12 - C#sashoalmView Answer on Stackoverflow
Solution 13 - C#Derek ZiembaView Answer on Stackoverflow
Solution 14 - C#kimmoliView Answer on Stackoverflow
Solution 15 - C#PeratXView Answer on Stackoverflow
Solution 16 - C#AzerpasView Answer on Stackoverflow
Solution 17 - C#user2029101View Answer on Stackoverflow
Solution 18 - C#Claas BontusView Answer on Stackoverflow