Skip to content

Instantly share code, notes, and snippets.

@edwardrowe
Last active December 7, 2023 03:13
Show Gist options
  • Save edwardrowe/fdec706fe53bfff0671e063f263ada63 to your computer and use it in GitHub Desktop.
Save edwardrowe/fdec706fe53bfff0671e063f263ada63 to your computer and use it in GitHub Desktop.
Exposes Git commands to C#, intended for use in Unity as part of a Build Automation tool
/* MIT License
Copyright (c) 2016 RedBlueGames
Code written by Doug Cox
*/
using System;
using UnityEngine;
/// <summary>
/// GitException includes the error output from a Git.Run() command as well as the
/// ExitCode it returned.
/// </summary>
public class GitException : InvalidOperationException
{
public GitException(int exitCode, string errors) : base(errors) =>
this.ExitCode = exitCode;
/// <summary>
/// The exit code returned when running the Git command.
/// </summary>
public readonly int ExitCode;
}
public static class Git
{
/* Properties ============================================================================================================= */
/// <summary>
/// Retrieves the build version from git based on the most recent matching tag and
/// commit history. This returns the version as: {major.minor.build} where 'build'
/// represents the nth commit after the tagged commit.
/// Note: The initial 'v' and the commit hash code are removed.
/// </summary>
public static string BuildVersion
{
get
{
var version = Run(@"describe --tags --long --match ""v[0-9]*""");
// Remove initial 'v' and ending git commit hash.
version = version.Replace('-', '.');
version = version.Substring(1, version.LastIndexOf('.') - 1);
return version;
}
}
/// <summary>
/// The currently active branch.
/// </summary>
public static string Branch => Run(@"rev-parse --abbrev-ref HEAD");
/// <summary>
/// Returns a listing of all uncommitted or untracked (added) files.
/// </summary>
public static string Status => Run(@"status --porcelain");
/* Methods ================================================================================================================ */
/// <summary>
/// Runs git.exe with the specified arguments and returns the output.
/// </summary>
public static string Run(string arguments)
{
using (var process = new System.Diagnostics.Process())
{
var exitCode = process.Run(@"git", arguments, Application.dataPath,
out var output, out var errors);
if (exitCode == 0)
{
return output;
}
else
{
throw new GitException(exitCode, errors);
}
}
}
}
using System.Diagnostics;
using System.Text;
public static class ProcessExtensions
{
/* Properties ============================================================================================================= */
/* Methods ================================================================================================================ */
/// <summary>
/// Runs the specified process and waits for it to exit. Its output and errors are
/// returned as well as the exit code from the process.
/// See: https://stackoverflow.com/questions/4291912/process-start-how-to-get-the-output
/// Note that if any deadlocks occur, read the above thread (cubrman's response).
/// </summary>
/// <remarks>
/// This should be run from a using block and disposed after use. It won't
/// work properly to keep it around.
/// </remarks>
public static int Run(this Process process, string application,
string arguments, string workingDirectory, out string output,
out string errors )
{
process.StartInfo = new ProcessStartInfo
{
CreateNoWindow = true,
UseShellExecute = false,
RedirectStandardError = true,
RedirectStandardOutput = true,
FileName = application,
Arguments = arguments,
WorkingDirectory = workingDirectory
};
// Use the following event to read both output and errors output.
var outputBuilder = new StringBuilder();
var errorsBuilder = new StringBuilder();
process.OutputDataReceived += (_, args) => outputBuilder.AppendLine(args.Data);
process.ErrorDataReceived += (_, args) => errorsBuilder.AppendLine(args.Data);
// Start the process and wait for it to exit.
process.Start();
process.BeginOutputReadLine();
process.BeginErrorReadLine();
process.WaitForExit();
output = outputBuilder.ToString().TrimEnd();
errors = errorsBuilder.ToString().TrimEnd();
return process.ExitCode;
}
}
@dimmduh
Copy link

dimmduh commented Aug 19, 2020

@edwardrowe
Copy link
Author

edwardrowe commented Aug 19, 2020

You're right, thanks for the note. I've added ProcessExtensions.cs into this gist.

@Yuriy-Pelekh
Copy link

Don't you need to add these lines after process.WaitForExit(); if you want to call the Run function multiple times? Otherwise, you get an exception.

process.CancelErrorRead();
process.CancelOutputRead();

Also, it worth moving subscriptions on events out of that function or unsubscribe after the process completed.

@edwardrowe
Copy link
Author

Thanks you're probably right on both those. I'm pretty sure we only used this extension from the using statement in Git.cs above, so we didn't encounter those issues.

I'm gonna make a comment for lack of time atm to fix it up properly.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment