Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 52 additions & 17 deletions src/Vcs/Git/GitVcs.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System;
using System.Diagnostics;
using System.IO;

namespace Skarp.Version.Cli.Vcs.Git
{
Expand All @@ -10,14 +11,15 @@ public class GitVcs : IVcs
/// </summary>
/// <param name="csProjFilePath">Path to the cs project file that was version updated</param>
/// <param name="message">The message to include in the commit</param>
public void Commit(string csProjFilePath, string message)
/// <param name="cwd"></param>
public void Commit(string csProjFilePath, string message, string cwd = null)
{
if(!LaunchGitWithArgs($"add \"{csProjFilePath}\""))
if(!LaunchGitWithArgs($"add \"{csProjFilePath}\"", cwd: cwd))
{
throw new OperationCanceledException($"Unable to add cs proj file {csProjFilePath} to git index");
}

if(!LaunchGitWithArgs($"commit -m \"{message}\""))
if(!LaunchGitWithArgs($"commit -m \"{message}\"", cwd: cwd))
{
throw new OperationCanceledException("Unable to commit");
}
Expand All @@ -27,42 +29,54 @@ public void Commit(string csProjFilePath, string message)
/// Determines whether the current repository is clean.
/// </summary>
/// <returns></returns>
public bool IsRepositoryClean()
public bool IsRepositoryClean(string cwd = null)
{
return LaunchGitWithArgs("diff-index --quiet HEAD --");
return LaunchGitWithArgs("diff-index HEAD --", cwd: cwd);
}

/// <summary>
/// Determines whether git is present in PATH on the current computer
/// </summary>
/// <returns></returns>
public bool IsVcsToolPresent()
public bool IsVcsToolPresent(string cwd = null)
{
// launching `git --help` returns exit code 0 where as `git` returns 1 as git wants a cmd line argument
return LaunchGitWithArgs("--help");
return LaunchGitWithArgs("--help", cwd: cwd);
}

/// <summary>
/// Creates a new tag
/// </summary>
/// <param name="tagName">Name of the tag</param>
public void Tag(string tagName)
/// <param name="cwd"></param>
public void Tag(string tagName, string cwd = null)
{
if(!LaunchGitWithArgs($"tag {tagName}"))
if(!LaunchGitWithArgs($"tag {tagName}", cwd: cwd))
{
throw new OperationCanceledException("Unable to create tag");
}
}

private static bool LaunchGitWithArgs(string args, int waitForExitTimeMs = 1000, int exitCode = 0)
/// <summary>
/// Helper method for launching git with different arguments while returning just a boolean of whether the
/// "command" was successful
/// </summary>
/// <param name="args">The args to pass onto git, e.g `diff` to launch `git diff`</param>
/// <param name="waitForExitTimeMs">How long to wait for the git operation to complete</param>
/// <param name="exitCode">The expected exit code</param>
/// <param name="cwd">The working directory to change into, if any. Leave null for "current directory" </param>
/// <returns></returns>
internal static bool LaunchGitWithArgs(
string args,
int waitForExitTimeMs = 1000,
int exitCode = 0,
string cwd = null
)
{
try
{
var startInfo = CreateGitShellStartInfo(args);
var proc = Process.Start(startInfo);
proc.WaitForExit(waitForExitTimeMs);

return proc.ExitCode == exitCode;
var (procExitCode, stdOut, stdErr) = LaunchGitWithArgsInner(args, waitForExitTimeMs, cwd);
return procExitCode == exitCode;
}
catch (Exception ex)
{
Expand All @@ -71,15 +85,36 @@ private static bool LaunchGitWithArgs(string args, int waitForExitTimeMs = 1000,
}
}

private static ProcessStartInfo CreateGitShellStartInfo(string args)
internal static (int ExitCode, string stdOut, string stdErr) LaunchGitWithArgsInner(
string args,
int waitForExitTimeMs,
string cwd = null
)
{
return new ProcessStartInfo("git")
var startInfo = CreateGitShellStartInfo(args, cwd);
var proc = Process.Start(startInfo);
proc.WaitForExit(waitForExitTimeMs);

var stdOut = proc.StandardOutput.ReadToEnd();
var stdErr = proc.StandardError.ReadToEnd();
return (proc.ExitCode, stdOut,stdErr);
}

internal static ProcessStartInfo CreateGitShellStartInfo(string args, string cwd = null)
{
var procInfo = new ProcessStartInfo("git")
{
Arguments = args,
RedirectStandardError = true,
RedirectStandardInput = true,
RedirectStandardOutput = true,
};

if (!string.IsNullOrWhiteSpace(cwd))
{
procInfo.WorkingDirectory = cwd;
}
return procInfo;
}

public string ToolName()
Expand Down
12 changes: 8 additions & 4 deletions src/Vcs/IVcs.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,29 +16,33 @@ public interface IVcs
/// are available in the current CLI contenxt - i.e check that `git` command can be found
/// and executed
/// </summary>
/// <param name="cwd">Change working directory - leave null for current directory</param>
/// <returns><c>true</c> if the tool exists, <c>false</c> otherwise</returns>
bool IsVcsToolPresent();
bool IsVcsToolPresent(string cwd = null);

/// <summary>
/// When implemented by a concrete class it returns <c>true</c> if the
/// current HEAD of the local repository is clean - i.e no pending changes
/// </summary>
/// <param name="cwd">Change working directory - leave null for current directory</param>
/// <returns></returns>
bool IsRepositoryClean();
bool IsRepositoryClean(string cwd = null);

/// <summary>
/// When implemented by a concrete class it allows to create a commit with the
/// changed version in the project file
/// </summary>
/// <param name="csProjFilePath">Path to the cs project file</param>
/// <param name="message">The message to create the commit message with</param>
void Commit(string csProjFilePath, string message);
/// <param name="cwd">Change working directory - leave null for current directory</param>
void Commit(string csProjFilePath, string message, string cwd = null);

/// <summary>
/// When implemented by a concrete class it will tag the latest commit with the
/// given tag name
/// </summary>
/// <param name="tagName">The name of the tag to create - i.e v1.0.2 </param>
void Tag(string tagName);
/// <param name="cwd">Change working directory - leave null for current directory</param>
void Tag(string tagName, string cwd = null);
}
}
31 changes: 0 additions & 31 deletions test/GitVcsTest.cs

This file was deleted.

78 changes: 78 additions & 0 deletions test/Vcs/Git/GitVcsTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
using System;
using System.IO;
using Skarp.Version.Cli.Vcs.Git;
using Xunit;

namespace Skarp.Version.Cli.Test.Vcs.Git
{
public class GitVcsTest : IClassFixture<GitVcsFixture>
{
private readonly GitVcsFixture _fixture;


public GitVcsTest(GitVcsFixture fixture)
{
_fixture = fixture;
}

[Fact]
public void ReturnsProperToolname()
{
Assert.Equal("git", _fixture.Vcs.ToolName());
}

[Fact]
public void DetectingGitOnMachineWorks()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

question: can we make a negative test for this as well?

I am not sure how to go about it, but currently the implementation could just be return true and the test would pass :)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess we could but I'm not sure how easily that is done - I do agree with your comment though.

running it on a machine without git in CI will not work, as it won't be able to get the source code - but perhaps altering the path for the process info we use to launch the tool will work.

{
Assert.True(_fixture.Vcs.IsVcsToolPresent(_fixture.AbsolutePathToGitTestDir));
}

[Fact]
public void IsRepositoryCleanWorks()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue: add test with dirty state as well

{
Assert.True(_fixture.Vcs.IsRepositoryClean(_fixture.AbsolutePathToGitTestDir));
}

[Fact]
public void CanCommit()
{
// arrange
var commitMessage = Guid.NewGuid().ToString("N");
var fileToCommit = "dotnet-version.dll";
File.Copy(fileToCommit, Path.Combine(_fixture.GitTestDir, fileToCommit));

// act
_fixture.Vcs.Commit(fileToCommit, commitMessage, _fixture.AbsolutePathToGitTestDir);

// assert

// grep the git-log for messages containing our guid message
var (
exitCode,
stdOut,
_
) = GitVcs.LaunchGitWithArgsInner($"log --grep={commitMessage}", 1000,
_fixture.AbsolutePathToGitTestDir);
Assert.Equal(0, exitCode);
Assert.Contains(commitMessage, stdOut);
}

[Fact]
public void CanCreateTags()
{
var tagToMake = Guid.NewGuid().ToString("N");

_fixture.Vcs.Tag(tagToMake, _fixture.AbsolutePathToGitTestDir);

var (exitCode, stdOut, _) =
GitVcs.LaunchGitWithArgsInner(
"tag -l"
, 1000,
_fixture.AbsolutePathToGitTestDir
);

Assert.Equal(0, exitCode);
Assert.Contains(tagToMake, stdOut);
}
}
}
59 changes: 59 additions & 0 deletions test/Vcs/Git/GitVcsTestFixture.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
using System;
using System.Diagnostics;
using System.IO;
using System.IO.Compression;
using System.Threading;
using Skarp.Version.Cli.Vcs.Git;

namespace Skarp.Version.Cli.Test.Vcs.Git
{
public class GitVcsFixture : IDisposable
{
public readonly string GitTestDir;
public readonly GitVcs Vcs;
public readonly string AbsolutePathToGitTestDir;

public GitVcsFixture()
{
GitTestDir = "./target-git-dir";
AbsolutePathToGitTestDir = Path.Combine(
Directory.GetCurrentDirectory(),
GitTestDir
);
DirectoryDelete(GitTestDir, recursive: true);

ZipFile.ExtractToDirectory("./target-git.zip", "./");
Vcs = new GitVcs();

var (_, stdOut, _) =
GitVcs.LaunchGitWithArgsInner(
"config user.email",
1000,
AbsolutePathToGitTestDir
);
if (string.IsNullOrWhiteSpace(stdOut))
{
GitVcs.LaunchGitWithArgs("config user.email nicklas@skarp.dk");
GitVcs.LaunchGitWithArgs("config user.name Nicklas Laine Overgaard");
}
}


private void DirectoryDelete(string dir, bool recursive)
{
try
{
Directory.Delete(dir, recursive);
}
// we don't want to fail at all if deleting the dir fails
catch (Exception ex)
{
}
}

public void Dispose()
{
DirectoryDelete(GitTestDir, recursive: true);
}
}
}
Loading