diff --git a/appveyor.yml b/appveyor.yml
index 6df9d5c357..44e0cb940d 100644
--- a/appveyor.yml
+++ b/appveyor.yml
@@ -13,6 +13,3 @@ build_script:
test: off
skip_tags: true
-
-cache:
- - src\packages -> **\packages.config # preserve "packages" directory in the root of build folder but will reset it if packages.config is modified
\ No newline at end of file
diff --git a/build.cake b/build.cake
index 2cc1a05dbe..d09798da06 100644
--- a/build.cake
+++ b/build.cake
@@ -72,12 +72,12 @@ Task("Version")
UpdateAssemblyInfo = true,
LogFilePath = "console",
OutputType = GitVersionOutput.BuildServer,
- ToolPath = @"src\GitVersionExe\bin\Release\net40\GitVersion.exe"
+ ToolPath = @"src\GitVersionExe\bin\Release\net461\GitVersion.exe"
});
GitVersion assertedVersions = GitVersion(new GitVersionSettings
{
OutputType = GitVersionOutput.Json,
- ToolPath = @"src\GitVersionExe\bin\Release\net40\GitVersion.exe"
+ ToolPath = @"src\GitVersionExe\bin\Release\net461\GitVersion.exe"
});
version = assertedVersions.MajorMinorPatch;
@@ -140,12 +140,12 @@ Task("Run-Tests")
.IsDependentOn("DogfoodBuild")
.Does(() =>
{
- var settings = new DotNetCoreTestSettings
- {
+ var settings = new DotNetCoreTestSettings
+ {
Configuration = configuration,
NoBuild = true,
Filter = "TestCategory!=NoMono"
- };
+ };
DotNetCoreTest("./src/GitVersionCore.Tests/GitVersionCore.Tests.csproj", settings);
DotNetCoreTest("./src/GitVersionExe.Tests/GitVersionExe.Tests.csproj", settings);
@@ -155,24 +155,24 @@ Task("Run-Tests")
void ILRepackGitVersionExe(bool includeLibGit2Sharp)
{
- var tempMergeDir = "ILMergeTemp";
- var exeName = "GitVersion.exe";
- var keyFilePath = "./src/key.snk";
- var targetDir = "./src/GitVersionExe/bin/" + configuration + "/net40/";
- var targetPath = targetDir + exeName;
-
- CreateDirectory(tempMergeDir);
- string outFilePath = "./" + tempMergeDir + "/" + exeName;
-
- var sourcePattern = targetDir + "*.dll";
- var sourceFiles = GetFiles(sourcePattern);
- if(!includeLibGit2Sharp)
- {
- var excludePattern = "**/LibGit2Sharp.dll";
- sourceFiles = sourceFiles - GetFiles(excludePattern);
- }
- var settings = new ILRepackSettings { AllowDup = "", Keyfile = keyFilePath, Internalize = true, NDebug = true, TargetKind = TargetKind.Exe, TargetPlatform = TargetPlatformVersion.v4, XmlDocs = false };
- ILRepack(outFilePath, targetPath, sourceFiles, settings);
+ var tempMergeDir = "ILMergeTemp";
+ var exeName = "GitVersion.exe";
+ var keyFilePath = "./src/key.snk";
+ var targetDir = "./src/GitVersionExe/bin/" + configuration + "/net461/";
+ var targetPath = targetDir + exeName;
+
+ CreateDirectory(tempMergeDir);
+ string outFilePath = "./" + tempMergeDir + "/" + exeName;
+
+ var sourcePattern = targetDir + "*.dll";
+ var sourceFiles = GetFiles(sourcePattern);
+ if(!includeLibGit2Sharp)
+ {
+ var excludePattern = "**/LibGit2Sharp.dll";
+ sourceFiles = sourceFiles - GetFiles(excludePattern);
+ }
+ var settings = new ILRepackSettings { AllowDup = "", Keyfile = keyFilePath, Internalize = true, NDebug = true, TargetKind = TargetKind.Exe, TargetPlatform = TargetPlatformVersion.v4, XmlDocs = false };
+ ILRepack(outFilePath, targetPath, sourceFiles, settings);
}
@@ -191,7 +191,7 @@ Task("Commandline-Package")
CreateDirectory(toolsDir);
CreateDirectory(libDir);
- var targetDir = "./src/GitVersionExe/bin/" + configuration + "/net40/";
+ var targetDir = "./src/GitVersionExe/bin/" + configuration + "/net461/";
var libGitFiles = GetFiles(targetDir + "LibGit2Sharp.dll*");
var nugetAssetsPath = "./src/GitVersionExe/NugetAssets/";
@@ -227,17 +227,17 @@ Task("Portable-Package")
.Does(() =>
{
- ILRepackGitVersionExe(true);
+ ILRepackGitVersionExe(true);
- var outputDir = buildDir + "NuGetExeBuild";
- var toolsDir = outputDir + "/tools";
- var libDir = toolsDir + "/lib";
+ var outputDir = buildDir + "NuGetExeBuild";
+ var toolsDir = outputDir + "/tools";
+ var libDir = toolsDir + "/lib";
- CreateDirectory(outputDir);
- CreateDirectory(toolsDir);
- CreateDirectory(libDir);
+ CreateDirectory(outputDir);
+ CreateDirectory(toolsDir);
+ CreateDirectory(libDir);
- var targetDir = "./src/GitVersionExe/bin/" + configuration + "/net40/";
+ var targetDir = "./src/GitVersionExe/bin/" + configuration + "/net461/";
var nugetAssetsPath = "./src/GitVersionExe/NugetAssets/";
Information("Copying files to packaging direcory..");
@@ -269,21 +269,21 @@ Task("GitVersionCore-Package")
.IsDependentOn("Build")
.Does(() =>
{
- var outputDir = buildDir + "NuGetRefBuild";
- CreateDirectory(outputDir);
+ var outputDir = buildDir + "NuGetRefBuild";
+ CreateDirectory(outputDir);
+
+ var msBuildSettings = new DotNetCoreMSBuildSettings();
+ msBuildSettings.SetVersion(nugetVersion);
+ msBuildSettings.Properties["PackageVersion"] = new string[]{ nugetVersion };
+ var settings = new DotNetCorePackSettings
+ {
+ Configuration = configuration,
+ OutputDirectory = outputDir,
+ NoBuild = true,
+ MSBuildSettings = msBuildSettings
+ };
- var msBuildSettings = new DotNetCoreMSBuildSettings();
- msBuildSettings.SetVersion(nugetVersion);
- msBuildSettings.Properties["PackageVersion"] = new string[]{ nugetVersion };
- var settings = new DotNetCorePackSettings
- {
- Configuration = configuration,
- OutputDirectory = outputDir,
- NoBuild = true,
- MSBuildSettings = msBuildSettings
- };
-
- DotNetCorePack("./src/GitVersionCore", settings);
+ DotNetCorePack("./src/GitVersionCore", settings);
})
.ReportError(exception =>
{
@@ -298,34 +298,31 @@ Task("GitVersion-DotNet-Package")
// var publishDir = buildDir + "Published";
// CreateDirectory(outputDir);
- var outputDir = buildDir + "NuGetExeDotNetCoreBuild";
- var toolsDir = outputDir + "/tools";
- var libDir = toolsDir + "/lib";
-
- CreateDirectory(outputDir);
- CreateDirectory(toolsDir);
- CreateDirectory(libDir);
-
+ var outputDir = buildDir + "NuGetExeDotNetCoreBuild";
+ var toolsDir = outputDir + "/tools";
+ var libDir = toolsDir + "/lib";
- var msBuildSettings = new DotNetCoreMSBuildSettings();
- msBuildSettings.SetVersion(nugetVersion);
- msBuildSettings.Properties["PackageVersion"] = new string[]{ nugetVersion };
+ CreateDirectory(outputDir);
+ CreateDirectory(toolsDir);
+ CreateDirectory(libDir);
- var framework = "netcoreapp20";
- var settings = new DotNetCorePublishSettings
- {
- Framework = framework,
- Configuration = configuration,
- OutputDirectory = toolsDir,
- MSBuildSettings = msBuildSettings
- };
-
- DotNetCorePublish("./src/GitVersionExe", settings);
+ var msBuildSettings = new DotNetCoreMSBuildSettings();
+ msBuildSettings.SetVersion(nugetVersion);
+ msBuildSettings.Properties["PackageVersion"] = new string[]{ nugetVersion };
+ var framework = "netcoreapp20";
+ var settings = new DotNetCorePublishSettings
+ {
+ Framework = framework,
+ Configuration = configuration,
+ OutputDirectory = toolsDir,
+ MSBuildSettings = msBuildSettings
+ };
- // var targetDir = "./src/GitVersionExe/bin/" + configuration + "/" + framework + "/";
+ DotNetCorePublish("./src/GitVersionExe/GitVersionExe.csproj", settings);
+ // var targetDir = "./src/GitVersionExe/bin/" + configuration + "/" + framework + "/";
var nugetAssetsPath = "./src/GitVersionExe/NugetAssets/";
Information("Copying files to packaging direcory..");
@@ -338,34 +335,56 @@ Task("GitVersion-DotNet-Package")
var nuGetPackSettings = new NuGetPackSettings { Version = nugetVersion, BasePath = outputDir, OutputDirectory = outputDir };
NuGetPack(outputDir + "/GitVersion.CommandLine.DotNetCore.nuspec", nuGetPackSettings);
+
})
.ReportError(exception =>
{
Error(exception.Dump());
});
+Task("GitVersion-DotNetCoreTool-Package")
+ .Description("Produces the nuget package for GitVersion global tool")
+ .Does(() =>
+{
+ var msBuildSettings = new DotNetCoreMSBuildSettings();
+ msBuildSettings.SetVersion(nugetVersion);
+ msBuildSettings.Properties["PackageVersion"] = new string[]{ nugetVersion };
+
+ var outputDir = buildDir + "NuGetExeDotNetCoreToolBuild";
+ CreateDirectory(outputDir);
+
+ DotNetCorePack("./src/GitVersionExe/GitVersion.Tool.csproj", new DotNetCorePackSettings {
+ Configuration = configuration,
+ OutputDirectory = outputDir,
+ MSBuildSettings = msBuildSettings
+ });
+})
+.ReportError(exception =>
+{
+ Error(exception.Dump());
+});
Task("GitVersionTaskPackage")
.Description("Produces the nuget package for GitVersionTask")
.Does(() =>
{
- var outputDir = buildDir + "NuGetTaskBuild";
- CreateDirectory(outputDir);
+ var outputDir = buildDir + "NuGetTaskBuild";
+ CreateDirectory(outputDir);
- var msBuildSettings = new DotNetCoreMSBuildSettings();
- msBuildSettings.SetVersion(nugetVersion);
+ var msBuildSettings = new DotNetCoreMSBuildSettings();
+ msBuildSettings.SetVersion(nugetVersion);
- msBuildSettings.Properties["PackageVersion"] = new string[]{ nugetVersion };
- var settings = new DotNetCorePackSettings
- {
- Configuration = configuration,
- OutputDirectory = outputDir,
- NoBuild = true,
- MSBuildSettings = msBuildSettings
- };
+ msBuildSettings.Properties["PackageVersion"] = new string[]{ nugetVersion };
+ var settings = new DotNetCorePackSettings
+ {
+ Configuration = configuration,
+ OutputDirectory = outputDir,
+ NoBuild = true,
+ MSBuildSettings = msBuildSettings
+ };
- DotNetCorePack("./src/GitVersionTask", settings);
+ DotNetCorePack("./src/GitVersionTask", settings);
})
.ReportError(exception =>
@@ -380,6 +399,7 @@ Task("Zip-Files")
.IsDependentOn("GitVersionCore-Package")
.IsDependentOn("GitVersionTaskPackage")
.IsDependentOn("GitVersion-DotNet-Package")
+ .IsDependentOn("GitVersion-DotNetCoreTool-Package")
.IsDependentOn("Run-Tests-In-NUnitConsole")
.Does(() =>
{
@@ -445,6 +465,7 @@ Task("Upload-AppVeyor-Artifacts")
"NuGetExeBuild:GitVersion.Portable." + nugetVersion +".nupkg",
"NuGetCommandLineBuild:GitVersion.CommandLine." + nugetVersion +".nupkg",
"NuGetExeDotNetCoreBuild:GitVersion.CommandLine.DotNetCore." + nugetVersion +".nupkg",
+ "NuGetExeDotNetCoreToolBuild:GitVersion.CommandLine.DotNetCore.Tool." + nugetVersion +".nupkg",
"NuGetRefBuild:GitVersionCore." + nugetVersion +".nupkg",
"NuGetTaskBuild:GitVersionTask." + nugetVersion +".nupkg",
"zip:GitVersion_" + nugetVersion + ".zip",
@@ -456,6 +477,7 @@ Task("Upload-AppVeyor-Artifacts")
AppVeyor.UploadArtifact("build/NuGetExeBuild/GitVersion.Portable." + nugetVersion +".nupkg");
AppVeyor.UploadArtifact("build/NuGetCommandLineBuild/GitVersion.CommandLine." + nugetVersion +".nupkg");
AppVeyor.UploadArtifact("build/NuGetExeDotNetCoreBuild/GitVersion.CommandLine.DotNetCore." + nugetVersion +".nupkg");
+ AppVeyor.UploadArtifact("build/NuGetExeDotNetCoreToolBuild/GitVersion.CommandLine.DotNetCore.Tool." + nugetVersion +".nupkg");
AppVeyor.UploadArtifact("build/NuGetRefBuild/GitVersionCore." + nugetVersion +".nupkg");
AppVeyor.UploadArtifact("build/NuGetTaskBuild/GitVersionTask." + nugetVersion +".nupkg");
AppVeyor.UploadArtifact("build/GitVersion_" + nugetVersion + ".zip");
diff --git a/deploy.cake b/deploy.cake
index b0c5712c83..08d03efae0 100644
--- a/deploy.cake
+++ b/deploy.cake
@@ -82,6 +82,7 @@ Task("DownloadGitHubReleaseArtifacts")
if (!artifactLookup.ContainsKey("NuGetRefBuild")) { throw new Exception("NuGetRefBuild artifact missing"); }
if (!artifactLookup.ContainsKey("NuGetCommandLineBuild")) { throw new Exception("NuGetCommandLineBuild artifact missing"); }
if (!artifactLookup.ContainsKey("NuGetExeDotNetCoreBuild")) { throw new Exception("NuGetExeDotNetCoreBuild artifact missing"); }
+ if (!artifactLookup.ContainsKey("NuGetExeDotNetCoreToolBuild")) { throw new Exception("NuGetExeDotNetCoreToolBuild artifact missing"); }
if (!artifactLookup.ContainsKey("NuGetTaskBuild")) { throw new Exception("NuGetTaskBuild artifact missing"); }
if (!artifactLookup.ContainsKey("NuGetExeBuild")) { throw new Exception("NuGetExeBuild artifact missing"); }
// if (!artifactLookup.ContainsKey("GemBuild")) { throw new Exception("GemBuild artifact missing"); }
@@ -141,6 +142,22 @@ Task("Publish-NuGetExeDotNetCore")
publishingError = true;
});
+Task("Publish-NuGetExeDotNetCoreTool")
+ .IsDependentOn("DownloadGitHubReleaseArtifacts")
+ .Does(() =>
+{
+ NuGetPush(
+ "./releaseArtifacts/" + artifactLookup["NuGetExeDotNetCoreToolBuild"],
+ new NuGetPushSettings {
+ ApiKey = EnvironmentVariable("NuGetApiKey"),
+ Source = "https://www.nuget.org/api/v2/package"
+ });
+})
+.OnError(exception =>
+{
+ Information("Publish-NuGet Task failed, but continuing with next Task...");
+ publishingError = true;
+});
Task("Publish-MsBuildTask")
.IsDependentOn("DownloadGitHubReleaseArtifacts")
@@ -321,6 +338,8 @@ Task("Publish-DockerImage")
Task("Deploy")
// .IsDependentOn("Publish-NuGetPackage")
.IsDependentOn("Publish-NuGetCommandLine")
+ //.IsDependentOn("Publish-NuGetExeDotNetCore")
+ //.IsDependentOn("Publish-NuGetExeDotNetCoreTool")
.IsDependentOn("Publish-MsBuildTask")
// .IsDependentOn("Publish-Chocolatey")
// .IsDependentOn("Publish-Gem")
diff --git a/src/Directory.Build.props b/src/Directory.Build.props
index 6e345ab46f..342e1fe838 100644
--- a/src/Directory.Build.props
+++ b/src/Directory.Build.props
@@ -1,7 +1,7 @@
- 1.3.1
4.2.3
- [1.0.185]
+ 0.26.0-preview-0027
+ 1.0.226
\ No newline at end of file
diff --git a/src/GitTools.Core.Tests/Git/DynamicRepositoriesTests.cs b/src/GitTools.Core.Tests/Git/DynamicRepositoriesTests.cs
new file mode 100644
index 0000000000..21843fd7b1
--- /dev/null
+++ b/src/GitTools.Core.Tests/Git/DynamicRepositoriesTests.cs
@@ -0,0 +1,325 @@
+namespace GitTools.Tests.Git
+{
+ using System;
+ using System.IO;
+ using System.Linq;
+ using GitTools.Git;
+ using IO;
+ using LibGit2Sharp;
+ using NUnit.Framework;
+ using Shouldly;
+ using Testing;
+
+ [TestFixture]
+ public class DynamicRepositoriesTests
+ {
+ const string DefaultBranchName = "master";
+ const string SpecificBranchName = "feature/foo";
+
+ [Test]
+ [TestCase(DefaultBranchName, DefaultBranchName)]
+ [TestCase(SpecificBranchName, SpecificBranchName)]
+ [Category("NoMono")]
+ public void WorksCorrectlyWithRemoteRepository(string branchName, string expectedBranchName)
+ {
+ var repoName = Guid.NewGuid().ToString();
+ var tempPath = Path.GetTempPath();
+ var tempDir = Path.Combine(tempPath, repoName);
+ Directory.CreateDirectory(tempDir);
+ string dynamicRepositoryPath = null;
+
+ try
+ {
+ using (var fixture = new EmptyRepositoryFixture())
+ {
+ var expectedDynamicRepoLocation = Path.Combine(tempPath, fixture.RepositoryPath.Split(Path.DirectorySeparatorChar).Last());
+
+ fixture.Repository.MakeCommits(5);
+ fixture.Repository.CreateFileAndCommit("TestFile.txt");
+
+ var branch = fixture.Repository.CreateBranch(SpecificBranchName);
+
+ // Copy contents into working directory
+ File.Copy(Path.Combine(fixture.RepositoryPath, "TestFile.txt"), Path.Combine(tempDir, "TestFile.txt"));
+
+ var repositoryInfo = new RepositoryInfo
+ {
+ Url = fixture.RepositoryPath
+ };
+
+ using (var dynamicRepository = DynamicRepositories.CreateOrOpen(repositoryInfo, tempPath, branchName, branch.Tip.Sha))
+ {
+ dynamicRepositoryPath = dynamicRepository.Repository.Info.Path;
+ dynamicRepository.Repository.Info.Path.ShouldBe(Path.Combine(expectedDynamicRepoLocation, ".git\\"));
+
+ var currentBranch = dynamicRepository.Repository.Head.CanonicalName;
+
+ currentBranch.ShouldEndWith(expectedBranchName);
+ }
+ }
+ }
+ finally
+ {
+ Directory.Delete(tempDir, true);
+
+ if (dynamicRepositoryPath != null)
+ {
+ DeleteHelper.DeleteGitRepository(dynamicRepositoryPath);
+ }
+ }
+ }
+
+ [Test]
+ public void UpdatesExistingDynamicRepository()
+ {
+ var repoName = Guid.NewGuid().ToString();
+ var tempPath = Path.GetTempPath();
+ var tempDir = Path.Combine(tempPath, repoName);
+ Directory.CreateDirectory(tempDir);
+ string dynamicRepositoryPath = null;
+
+ try
+ {
+ using (var mainRepositoryFixture = new EmptyRepositoryFixture())
+ {
+ var commit = mainRepositoryFixture.Repository.MakeACommit();
+
+ var repositoryInfo = new RepositoryInfo
+ {
+ Url = mainRepositoryFixture.RepositoryPath
+ };
+
+ using (var dynamicRepository = DynamicRepositories.CreateOrOpen(repositoryInfo, tempPath, "master", commit.Sha))
+ {
+ dynamicRepositoryPath = dynamicRepository.Repository.Info.Path;
+ }
+
+ var newCommit = mainRepositoryFixture.Repository.MakeACommit();
+
+ using (var dynamicRepository = DynamicRepositories.CreateOrOpen(repositoryInfo, tempPath, "master", newCommit.Sha))
+ {
+ dynamicRepository.Repository.Info.Path.ShouldBe(dynamicRepositoryPath);
+ dynamicRepository.Repository.Commits.ShouldContain(c => c.Sha == newCommit.Sha);
+ }
+ }
+ }
+ finally
+ {
+ Directory.Delete(tempDir, true);
+
+ if (dynamicRepositoryPath != null)
+ {
+ DeleteHelper.DeleteGitRepository(dynamicRepositoryPath);
+ }
+ }
+ }
+
+ [Test]
+ [Category("NoMono")]
+ public void PicksAnotherDirectoryNameWhenDynamicRepoFolderTaken()
+ {
+ var repoName = Guid.NewGuid().ToString();
+ var tempPath = Path.GetTempPath();
+ var tempDir = Path.Combine(tempPath, repoName);
+ Directory.CreateDirectory(tempDir);
+ string expectedDynamicRepoLocation = null;
+
+ try
+ {
+ using (var fixture = new EmptyRepositoryFixture())
+ {
+ var head = fixture.Repository.CreateFileAndCommit("TestFile.txt");
+ File.Copy(Path.Combine(fixture.RepositoryPath, "TestFile.txt"), Path.Combine(tempDir, "TestFile.txt"));
+ expectedDynamicRepoLocation = Path.Combine(tempPath, fixture.RepositoryPath.Split(Path.DirectorySeparatorChar).Last());
+ Directory.CreateDirectory(expectedDynamicRepoLocation);
+
+ var repositoryInfo = new RepositoryInfo
+ {
+ Url = fixture.RepositoryPath
+ };
+
+ using (var dynamicRepository = DynamicRepositories.CreateOrOpen(repositoryInfo, tempPath, "master", head.Sha))
+ {
+ dynamicRepository.Repository.Info.Path.ShouldBe(Path.Combine(expectedDynamicRepoLocation + "_1", ".git\\"));
+ }
+ }
+ }
+ finally
+ {
+ DeleteHelper.DeleteDirectory(tempDir, true);
+ if (expectedDynamicRepoLocation != null)
+ {
+ DeleteHelper.DeleteDirectory(expectedDynamicRepoLocation, true);
+ }
+
+ if (expectedDynamicRepoLocation != null)
+ {
+ DeleteHelper.DeleteGitRepository(expectedDynamicRepoLocation + "_1");
+ }
+ }
+ }
+
+ [Test]
+ [Category("NoMono")]
+ public void PicksAnotherDirectoryNameWhenDynamicRepoFolderIsInUse()
+ {
+ var tempPath = Path.GetTempPath();
+ var expectedDynamicRepoLocation = default(string);
+ var expectedDynamicRepo2Location = default(string);
+
+ try
+ {
+ using (var fixture = new EmptyRepositoryFixture())
+ {
+ var head = fixture.Repository.CreateFileAndCommit("TestFile.txt");
+ var repositoryInfo = new RepositoryInfo
+ {
+ Url = fixture.RepositoryPath
+ };
+
+ using (var dynamicRepository = DynamicRepositories.CreateOrOpen(repositoryInfo, tempPath, "master", head.Sha))
+ using (var dynamicRepository2 = DynamicRepositories.CreateOrOpen(repositoryInfo, tempPath, "master", head.Sha))
+ {
+ expectedDynamicRepoLocation = dynamicRepository.Repository.Info.Path;
+ expectedDynamicRepo2Location = dynamicRepository2.Repository.Info.Path;
+ dynamicRepository.Repository.Info.Path.ShouldNotBe(dynamicRepository2.Repository.Info.Path);
+ }
+ }
+ }
+ finally
+ {
+ if (expectedDynamicRepoLocation != null)
+ {
+ DeleteHelper.DeleteDirectory(expectedDynamicRepoLocation, true);
+ }
+
+ if (expectedDynamicRepo2Location != null)
+ {
+ DeleteHelper.DeleteGitRepository(expectedDynamicRepo2Location);
+ }
+ }
+ }
+
+ [Test]
+ public void ThrowsExceptionWhenNotEnoughInfo()
+ {
+ var tempDir = Path.GetTempPath();
+
+ var repositoryInfo = new RepositoryInfo
+ {
+ Url = tempDir
+ };
+
+ Should.Throw(() => DynamicRepositories.CreateOrOpen(repositoryInfo, tempDir, null, null));
+ }
+
+ [Test]
+ public void UsingDynamicRepositoryWithFeatureBranchWorks()
+ {
+ var repoName = Guid.NewGuid().ToString();
+ var tempPath = Path.GetTempPath();
+ var tempDir = Path.Combine(tempPath, repoName);
+ Directory.CreateDirectory(tempDir);
+
+ try
+ {
+ using (var mainRepositoryFixture = new EmptyRepositoryFixture())
+ {
+ var commit = mainRepositoryFixture.Repository.MakeACommit();
+
+ var repositoryInfo = new RepositoryInfo
+ {
+ Url = mainRepositoryFixture.RepositoryPath
+ };
+
+ Commands.Checkout(mainRepositoryFixture.Repository, mainRepositoryFixture.Repository.CreateBranch("feature1"));
+
+ Should.NotThrow(() =>
+ {
+ using (DynamicRepositories.CreateOrOpen(repositoryInfo, tempPath, "feature1", commit.Sha))
+ {
+ }
+ });
+ }
+ }
+ finally
+ {
+ Directory.Delete(tempDir, true);
+ }
+ }
+
+ [Test]
+ public void UsingDynamicRepositoryWithoutTargetBranchFails()
+ {
+ var tempPath = Path.GetTempPath();
+
+ using (var mainRepositoryFixture = new EmptyRepositoryFixture())
+ {
+ mainRepositoryFixture.Repository.MakeACommit();
+
+ var repositoryInfo = new RepositoryInfo
+ {
+ Url = mainRepositoryFixture.RepositoryPath
+ };
+
+ Should.Throw(() =>
+ {
+ using (DynamicRepositories.CreateOrOpen(repositoryInfo, tempPath, null, null))
+ {
+ }
+ });
+ }
+ }
+
+ [Test]
+ public void UsingDynamicRepositoryWithoutTargetBranchCommitFails()
+ {
+ var tempPath = Path.GetTempPath();
+
+ using (var mainRepositoryFixture = new EmptyRepositoryFixture())
+ {
+ mainRepositoryFixture.Repository.MakeACommit();
+
+ var repositoryInfo = new RepositoryInfo
+ {
+ Url = mainRepositoryFixture.RepositoryPath
+ };
+
+ Should.Throw(() =>
+ {
+ using (DynamicRepositories.CreateOrOpen(repositoryInfo, tempPath, "master", null))
+ {
+ }
+ });
+ }
+ }
+
+ [Test]
+ public void TestErrorThrownForInvalidRepository()
+ {
+ var repoName = Guid.NewGuid().ToString();
+ var tempPath = Path.GetTempPath();
+ var tempDir = Path.Combine(tempPath, repoName);
+ Directory.CreateDirectory(tempDir);
+
+ try
+ {
+ var repositoryInfo = new RepositoryInfo
+ {
+ Url = "http://127.0.0.1/testrepo.git"
+ };
+
+ Should.Throw(() =>
+ {
+ using (DynamicRepositories.CreateOrOpen(repositoryInfo, tempPath, "master", "sha"))
+ {
+ }
+ });
+ }
+ finally
+ {
+ Directory.Delete(tempDir, true);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/GitTools.Core.Tests/Git/Extensions/AuthenticationInfoExtensionsTests.cs b/src/GitTools.Core.Tests/Git/Extensions/AuthenticationInfoExtensionsTests.cs
new file mode 100644
index 0000000000..7534c9d58a
--- /dev/null
+++ b/src/GitTools.Core.Tests/Git/Extensions/AuthenticationInfoExtensionsTests.cs
@@ -0,0 +1,54 @@
+namespace GitTools.Tests.Git.Extensions
+{
+ using GitTools.Git;
+ using NUnit.Framework;
+
+ [TestFixture]
+ public class AuthenticationInfoExtensionsTests
+ {
+ [TestCase("user", "password", null, false)]
+ [TestCase(null, null, "token", false)]
+ [TestCase(null, null, null, true)]
+ public void TheIsEmptyMethod(string username, string password, string token, bool expectedValue)
+ {
+ var authenticationInfo = new AuthenticationInfo
+ {
+ Username = username,
+ Password = password,
+ Token = token
+ };
+
+ Assert.AreEqual(expectedValue, authenticationInfo.IsEmpty());
+ }
+
+ [TestCase("user", "password", null, true)]
+ [TestCase(null, null, "token", false)]
+ [TestCase(null, null, null, false)]
+ public void TheIsUsernameAndPasswordAuthenticationMethod(string username, string password, string token, bool expectedValue)
+ {
+ var authenticationInfo = new AuthenticationInfo
+ {
+ Username = username,
+ Password = password,
+ Token = token
+ };
+
+ Assert.AreEqual(expectedValue, authenticationInfo.IsUsernameAndPasswordAuthentication());
+ }
+
+ [TestCase("user", "password", null, false)]
+ [TestCase(null, null, "token", true)]
+ [TestCase(null, null, null, false)]
+ public void TheIsTokenAuthenticationMethod(string username, string password, string token, bool expectedValue)
+ {
+ var authenticationInfo = new AuthenticationInfo
+ {
+ Username = username,
+ Password = password,
+ Token = token
+ };
+
+ Assert.AreEqual(expectedValue, authenticationInfo.IsTokenAuthentication());
+ }
+ }
+}
diff --git a/src/GitTools.Core.Tests/Git/GitDirFinderTests.cs b/src/GitTools.Core.Tests/Git/GitDirFinderTests.cs
new file mode 100644
index 0000000000..54fe915e35
--- /dev/null
+++ b/src/GitTools.Core.Tests/Git/GitDirFinderTests.cs
@@ -0,0 +1,57 @@
+namespace GitTools.Tests.Git
+{
+ using System;
+ using System.IO;
+ using GitTools.Git;
+ using LibGit2Sharp;
+ using NUnit.Framework;
+
+ [TestFixture]
+ public class GitDirFinderTests
+ {
+ string workDirectory;
+ string gitDirectory;
+
+ [SetUp]
+ public void CreateTemporaryRepository()
+ {
+ workDirectory = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString());
+
+ gitDirectory = Repository.Init(workDirectory).TrimEnd(new[] { Path.DirectorySeparatorChar });
+
+ Assert.NotNull(gitDirectory);
+ }
+
+ [TearDown]
+ public void Cleanup()
+ {
+ Directory.Delete(workDirectory, true);
+ }
+
+ [Test]
+ public void From_WorkingDirectory()
+ {
+ Assert.AreEqual(gitDirectory, GitDirFinder.TreeWalkForDotGitDir(workDirectory));
+ }
+
+ [Test]
+ public void From_WorkingDirectory_Parent()
+ {
+ var parentDirectory = Directory.GetParent(workDirectory).FullName;
+ Assert.Null(GitDirFinder.TreeWalkForDotGitDir(parentDirectory));
+ }
+
+ [Test]
+ public void From_GitDirectory()
+ {
+ Assert.AreEqual(gitDirectory, GitDirFinder.TreeWalkForDotGitDir(gitDirectory));
+ }
+
+ [Test]
+ public void From_RefsDirectory()
+ {
+ var refsDirectory = Path.Combine(gitDirectory, "refs");
+ Assert.AreEqual(gitDirectory, GitDirFinder.TreeWalkForDotGitDir(refsDirectory));
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/GitTools.Core.Tests/Git/GitRepositoryHelperTests.cs b/src/GitTools.Core.Tests/Git/GitRepositoryHelperTests.cs
new file mode 100644
index 0000000000..db79be553f
--- /dev/null
+++ b/src/GitTools.Core.Tests/Git/GitRepositoryHelperTests.cs
@@ -0,0 +1,230 @@
+namespace GitTools.Tests.Git
+{
+ using GitTools.Git;
+ using LibGit2Sharp;
+ using NUnit.Framework;
+ using Shouldly;
+ using Testing;
+
+ public class GitRepositoryHelperTests
+ {
+ [Test]
+ public void NormalisationOfPullRequestsWithFetch()
+ {
+ using (var fixture = new EmptyRepositoryFixture())
+ {
+ fixture.Repository.MakeACommit();
+
+ Commands.Checkout(fixture.Repository, fixture.Repository.CreateBranch("feature/foo"));
+ fixture.Repository.MakeACommit();
+ var commit = fixture.Repository.CreatePullRequestRef("feature/foo", "master", prNumber: 3);
+ using (var localFixture = fixture.CloneRepository())
+ {
+ localFixture.Checkout(commit.Sha);
+ GitRepositoryHelper.NormalizeGitDirectory(localFixture.RepositoryPath, new AuthenticationInfo(), noFetch: false, currentBranch: string.Empty);
+
+ var normalisedPullBranch = localFixture.Repository.Branches["pull/3/merge"];
+ normalisedPullBranch.ShouldNotBe(null);
+ }
+ }
+ }
+
+ [Test]
+ public void NormalisationOfPullRequestsWithoutFetch()
+ {
+ using (var fixture = new EmptyRepositoryFixture())
+ {
+ fixture.Repository.MakeACommit();
+
+ Commands.Checkout(fixture.Repository, fixture.Repository.CreateBranch("feature/foo"));
+ fixture.Repository.MakeACommit();
+ var commit = fixture.Repository.CreatePullRequestRef("feature/foo", "master", prNumber: 3, allowFastFowardMerge: true);
+ using (var localFixture = fixture.CloneRepository())
+ {
+ localFixture.Checkout(commit.Sha);
+ GitRepositoryHelper.NormalizeGitDirectory(localFixture.RepositoryPath, new AuthenticationInfo(), noFetch: true, currentBranch: "refs/pull/3/merge");
+
+ var normalisedPullBranch = localFixture.Repository.FindBranch("pull/3/merge");
+ normalisedPullBranch.ShouldNotBe(null);
+ }
+ }
+ }
+
+ [Test]
+ public void NormalisationOfTag()
+ {
+ using (var fixture = new EmptyRepositoryFixture())
+ {
+ fixture.Repository.MakeACommit();
+
+ Commands.Checkout(fixture.Repository, fixture.Repository.CreateBranch("feature/foo"));
+ fixture.Repository.MakeACommit();
+
+ fixture.BranchTo("release/2.0.0");
+ fixture.MakeACommit();
+ fixture.MakeATaggedCommit("2.0.0-rc.1");
+ fixture.Checkout("master");
+ fixture.MergeNoFF("release/2.0.0");
+ fixture.Repository.Branches.Remove(fixture.Repository.Branches["release/2.0.0"]);
+ var remoteTagSha = fixture.Repository.Tags["2.0.0-rc.1"].Target.Sha;
+
+ using (var localFixture = fixture.CloneRepository())
+ {
+ localFixture.Checkout(remoteTagSha);
+ GitRepositoryHelper.NormalizeGitDirectory(localFixture.RepositoryPath, new AuthenticationInfo(), noFetch: false, currentBranch: string.Empty);
+
+ localFixture.Repository.Head.FriendlyName.ShouldBe("(no branch)");
+ localFixture.Repository.Head.Tip.Sha.ShouldBe(remoteTagSha);
+ }
+ }
+ }
+
+ [Test]
+ public void UpdatesCurrentBranch()
+ {
+ using (var fixture = new EmptyRepositoryFixture())
+ {
+ fixture.Repository.MakeACommit();
+ Commands.Checkout(fixture.Repository, fixture.Repository.CreateBranch("develop"));
+ fixture.Repository.MakeACommit();
+ Commands.Checkout(fixture.Repository, "master");
+ using (var localFixture = fixture.CloneRepository())
+ {
+ // Advance remote
+ Commands.Checkout(fixture.Repository, "develop");
+ var advancedCommit = fixture.Repository.MakeACommit();
+ Commands.Fetch((Repository)localFixture.Repository, localFixture.Repository.Network.Remotes["origin"].Name, new string[0], null, null);
+ Commands.Checkout(localFixture.Repository, advancedCommit.Sha);
+ GitRepositoryHelper.NormalizeGitDirectory(localFixture.RepositoryPath, new AuthenticationInfo(), noFetch: false, currentBranch: "refs/heads/develop");
+
+ var normalisedBranch = localFixture.Repository.Branches["develop"];
+ normalisedBranch.ShouldNotBe(null);
+ normalisedBranch.Tip.Sha.ShouldBe(advancedCommit.Sha);
+ localFixture.Repository.Head.Tip.Sha.ShouldBe(advancedCommit.Sha);
+ }
+ }
+ }
+
+ [Test]
+ public void ShouldNotChangeBranchWhenNormalizingTheDirectory()
+ {
+ using (var fixture = new EmptyRepositoryFixture())
+ {
+ fixture.Repository.MakeATaggedCommit("v1.0.0");
+
+ Commands.Checkout(fixture.Repository, fixture.Repository.CreateBranch("develop"));
+ var lastCommitOnDevelop = fixture.Repository.MakeACommit();
+
+ Commands.Checkout(fixture.Repository, fixture.Repository.CreateBranch("feature/foo"));
+ fixture.Repository.MakeACommit();
+
+ using (var localFixture = fixture.CloneRepository())
+ {
+ Commands.Checkout(localFixture.Repository, "origin/develop");
+
+ // Another commit on feature/foo will force an update
+ fixture.Checkout("feature/foo");
+ fixture.Repository.MakeACommit();
+
+ GitRepositoryHelper.NormalizeGitDirectory(localFixture.RepositoryPath, new AuthenticationInfo(), noFetch: false, currentBranch: null);
+
+ localFixture.Repository.Head.Tip.Sha.ShouldBe(lastCommitOnDevelop.Sha);
+ }
+ }
+ }
+
+ [Test]
+ public void ShouldNotMoveLocalBranchWhenRemoteAdvances()
+ {
+ using (var fixture = new EmptyRepositoryFixture())
+ {
+ fixture.Repository.MakeACommit();
+
+ Commands.Checkout(fixture.Repository, fixture.Repository.CreateBranch("feature/foo"));
+ fixture.Repository.MakeACommit();
+ using (var localFixture = fixture.CloneRepository())
+ {
+ localFixture.Checkout("feature/foo");
+ var expectedTip = localFixture.Repository.Head.Tip;
+ // Advance remote
+ fixture.Repository.MakeACommit();
+ GitRepositoryHelper.NormalizeGitDirectory(localFixture.RepositoryPath, new AuthenticationInfo(), noFetch: false, currentBranch: null);
+
+ var normalisedBranch = localFixture.Repository.Branches["feature/foo"];
+ normalisedBranch.ShouldNotBe(null);
+ normalisedBranch.Tip.Sha.ShouldBe(expectedTip.Sha);
+ }
+ }
+ }
+
+ [Test]
+ public void CheckedOutShaShouldNotChanged()
+ {
+ using (var fixture = new EmptyRepositoryFixture())
+ {
+ fixture.Repository.MakeACommit();
+ var commitToBuild = fixture.Repository.MakeACommit();
+ fixture.Repository.MakeACommit();
+
+ using (var localFixture = fixture.CloneRepository())
+ {
+ Commands.Checkout(localFixture.Repository, commitToBuild);
+ GitRepositoryHelper.NormalizeGitDirectory(localFixture.RepositoryPath, new AuthenticationInfo(), noFetch: false, currentBranch: "refs/heads/master");
+
+ var normalisedBranch = localFixture.Repository.Branches["master"];
+ normalisedBranch.Tip.Sha.ShouldBe(commitToBuild.Sha);
+ }
+ }
+ }
+
+
+ [Test]
+ // Copied from GitVersion, to attempt fixing this bug: https://travis-ci.org/GitTools/GitVersion/jobs/171288284#L2025
+ public void GitHubFlowMajorRelease()
+ {
+ using (var fixture = new EmptyRepositoryFixture())
+ {
+ fixture.SequenceDiagram.Participant("master");
+
+ fixture.Repository.MakeACommit();
+ fixture.ApplyTag("1.3.0");
+
+ // Create release branch
+ fixture.BranchTo("release/2.0.0", "release");
+ fixture.SequenceDiagram.Activate("release/2.0.0");
+ fixture.MakeACommit();
+ // fixture.AssertFullSemver("2.0.0-beta.1+1");
+ fixture.MakeACommit();
+ // fixture.AssertFullSemver("2.0.0-beta.1+2");
+
+ // Apply beta.1 tag should be exact tag
+ fixture.ApplyTag("2.0.0-beta.1");
+ // fixture.AssertFullSemver("2.0.0-beta.1");
+
+ // test that the CommitsSinceVersionSource should still return commit count
+ // var version = fixture.GetVersion();
+ // version.CommitsSinceVersionSource.ShouldBe("2");
+
+ // Make a commit after a tag should bump up the beta
+ fixture.MakeACommit();
+ // fixture.AssertFullSemver("2.0.0-beta.2+3");
+
+ // Complete release
+ fixture.Checkout("master");
+ fixture.MergeNoFF("release/2.0.0");
+ fixture.SequenceDiagram.Destroy("release/2.0.0");
+ fixture.SequenceDiagram.NoteOver("Release branches are deleted once merged", "release/2.0.0");
+
+ //fixture.AssertFullSemver("2.0.0+0");
+ fixture.ApplyTag("2.0.0");
+ // fixture.AssertFullSemver("2.0.0");
+ fixture.MakeACommit();
+
+#if !NETCOREAPP1_1
+ fixture.Repository.DumpGraph();
+#endif
+ // fixture.AssertFullSemver("2.0.1+1");
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/GitTools.Core.Tests/GitTools.Core.Tests.csproj b/src/GitTools.Core.Tests/GitTools.Core.Tests.csproj
new file mode 100644
index 0000000000..5fe5827add
--- /dev/null
+++ b/src/GitTools.Core.Tests/GitTools.Core.Tests.csproj
@@ -0,0 +1,21 @@
+
+
+
+ Debug
+ Any CPU
+ Library
+ netcoreapp2.0
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/GitTools.Core.Tests/GitTools.Core.Tests.v2.ncrunchproject b/src/GitTools.Core.Tests/GitTools.Core.Tests.v2.ncrunchproject
new file mode 100644
index 0000000000..09d2a65de7
Binary files /dev/null and b/src/GitTools.Core.Tests/GitTools.Core.Tests.v2.ncrunchproject differ
diff --git a/src/GitTools.Core.Tests/GlobalInitialization.cs b/src/GitTools.Core.Tests/GlobalInitialization.cs
new file mode 100644
index 0000000000..cf04da6f54
--- /dev/null
+++ b/src/GitTools.Core.Tests/GlobalInitialization.cs
@@ -0,0 +1,13 @@
+using NUnit.Framework;
+
+[SetUpFixture]
+public class GlobalInitialization
+{
+ [OneTimeSetUp]
+ public static void SetUp()
+ {
+#if DEBUG
+ //LogManager.AddDebugListener(true);
+#endif
+ }
+}
\ No newline at end of file
diff --git a/src/GitTools.Core/Diposable.cs b/src/GitTools.Core/Diposable.cs
new file mode 100644
index 0000000000..173e6b3447
--- /dev/null
+++ b/src/GitTools.Core/Diposable.cs
@@ -0,0 +1,92 @@
+namespace GitTools
+{
+ using System;
+ using Logging;
+
+ ///
+ /// Base class for disposable objects.
+ ///
+ public abstract class Disposable : IDisposable
+ {
+ static readonly ILog Log = LogProvider.GetLogger(typeof(Disposable));
+
+ readonly object _syncRoot = new object();
+
+ bool _disposing;
+
+ ///
+ /// Finalizes an instance of the class.
+ ///
+ ~Disposable()
+ {
+ Dispose(false);
+ }
+
+ bool IsDisposed { get; set; }
+
+ ///
+ /// Disposes this instance.
+ ///
+ public void Dispose()
+ {
+ Dispose(true);
+ GC.SuppressFinalize(this);
+ }
+
+ ///
+ /// Disposes the managed resources.
+ ///
+ protected virtual void DisposeManaged()
+ {
+ }
+
+ ///
+ /// Disposes the unmanaged resources.
+ ///
+ protected virtual void DisposeUnmanaged()
+ {
+ }
+
+ ///
+ /// Releases unmanaged and - optionally - managed resources.
+ ///
+ /// true to release both managed and unmanaged resources; false to release only unmanaged resources.
+ void Dispose(bool isDisposing)
+ {
+ lock (_syncRoot)
+ {
+ if (!IsDisposed)
+ {
+ if (!_disposing)
+ {
+ _disposing = true;
+
+ if (isDisposing)
+ {
+ try
+ {
+ DisposeManaged();
+ }
+ catch (Exception ex)
+ {
+ Log.ErrorException("Error while disposing managed resources of '{0}'.", ex, GetType().FullName);
+ }
+ }
+
+ try
+ {
+ DisposeUnmanaged();
+ }
+ catch (Exception ex)
+ {
+ Log.ErrorException("Error while disposing unmanaged resources of '{0}'.", ex, GetType().FullName);
+ }
+
+ IsDisposed = true;
+ _disposing = false;
+ }
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/GitTools.Core/Exceptions/GitToolsException.cs b/src/GitTools.Core/Exceptions/GitToolsException.cs
new file mode 100644
index 0000000000..375561e201
--- /dev/null
+++ b/src/GitTools.Core/Exceptions/GitToolsException.cs
@@ -0,0 +1,25 @@
+namespace GitTools
+{
+ using System;
+ using System.Runtime.Serialization;
+
+ [Serializable]
+ public class GitToolsException : Exception
+ {
+ public GitToolsException(string messageFormat, params object[] args)
+ : base(string.Format(messageFormat, args))
+ {
+ }
+ public GitToolsException(string message, Exception innerException)
+ : base(message, innerException)
+ {
+ }
+
+#if NETDESKTOP
+ protected GitToolsException(SerializationInfo info, StreamingContext context)
+ : base(info, context)
+ {
+ }
+#endif
+ }
+}
\ No newline at end of file
diff --git a/src/GitTools.Core/Exceptions/WarningException.cs b/src/GitTools.Core/Exceptions/WarningException.cs
new file mode 100644
index 0000000000..0eaed198d0
--- /dev/null
+++ b/src/GitTools.Core/Exceptions/WarningException.cs
@@ -0,0 +1,21 @@
+namespace GitTools
+{
+ using System;
+ using System.Runtime.Serialization;
+
+ [Serializable]
+ public class WarningException : Exception
+ {
+ public WarningException(string message)
+ : base(message)
+ {
+ }
+
+#if NETDESKTOP
+ protected WarningException(SerializationInfo info, StreamingContext context)
+ : base(info, context)
+ {
+ }
+#endif
+ }
+}
\ No newline at end of file
diff --git a/src/GitTools.Core/Extensions/StringExtensions.cs b/src/GitTools.Core/Extensions/StringExtensions.cs
new file mode 100644
index 0000000000..d5bc1f553f
--- /dev/null
+++ b/src/GitTools.Core/Extensions/StringExtensions.cs
@@ -0,0 +1,15 @@
+namespace GitTools
+{
+ using System.Text;
+ using JetBrains.Annotations;
+
+ public static class StringExtensions
+ {
+ [StringFormatMethod("format")]
+ public static void AppendLineFormat(this StringBuilder stringBuilder, string format, params object[] args)
+ {
+ stringBuilder.AppendFormat(format, args);
+ stringBuilder.AppendLine();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/GitTools.Core/Git/AuthenticationInfo.cs b/src/GitTools.Core/Git/AuthenticationInfo.cs
new file mode 100644
index 0000000000..9a0406a965
--- /dev/null
+++ b/src/GitTools.Core/Git/AuthenticationInfo.cs
@@ -0,0 +1,11 @@
+namespace GitTools.Git
+{
+ using LibGit2Sharp;
+
+ public class AuthenticationInfo
+ {
+ public string Username { get; set; }
+ public string Password { get; set; }
+ public string Token { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/src/GitTools.Core/Git/DynamicRepositories.cs b/src/GitTools.Core/Git/DynamicRepositories.cs
new file mode 100644
index 0000000000..244c55120d
--- /dev/null
+++ b/src/GitTools.Core/Git/DynamicRepositories.cs
@@ -0,0 +1,219 @@
+namespace GitTools.Git
+{
+ using System;
+ using System.Diagnostics.CodeAnalysis;
+ using System.IO;
+ using System.Linq;
+ using LibGit2Sharp;
+ using Logging;
+
+ public static class DynamicRepositories
+ {
+ static readonly ILog Log = LogProvider.GetLogger(typeof(DynamicRepositories));
+
+ ///
+ /// Creates a dynamic repository based on the repository info
+ ///
+ /// The source repository information.
+ /// The path to create the dynamic repository, NOT thread safe.
+ ///
+ ///
+ /// The git repository.
+ public static DynamicRepository CreateOrOpen(RepositoryInfo repositoryInfo, string dynamicRepsitoryPath, string targetBranch, string targetCommit)
+ {
+ if (string.IsNullOrWhiteSpace(dynamicRepsitoryPath) || !Directory.Exists(dynamicRepsitoryPath))
+ throw new GitToolsException(string.Format("Dynamic repository path {0} does not exist, ensure it is created before trying to create dynamic repository.", dynamicRepsitoryPath));
+ if (string.IsNullOrWhiteSpace(targetBranch))
+ throw new GitToolsException("Dynamic Git repositories must have a target branch");
+ if (string.IsNullOrWhiteSpace(targetCommit))
+ throw new GitToolsException("Dynamic Git repositories must have a target commit");
+
+ var tempRepositoryPath = GetAndLockTemporaryRepositoryPath(repositoryInfo.Url, dynamicRepsitoryPath);
+ var dynamicRepositoryPath = CreateDynamicRepository(tempRepositoryPath, repositoryInfo, targetBranch, targetCommit);
+
+ return new DynamicRepository(new Repository(dynamicRepositoryPath), () => ReleaseDynamicRepoLock(tempRepositoryPath));
+ }
+
+ static void ReleaseDynamicRepoLock(string repoPath)
+ {
+ var lockFile = GetLockFile(repoPath);
+ try
+ {
+ File.Delete(lockFile);
+ }
+ catch (Exception ex)
+ {
+ throw new GitToolsException(string.Format("Failed to delete dynamic repository lock file '{0}', this dynamic repository will not be used until the lock file is removed", lockFile), ex);
+ }
+ }
+
+ static bool TakeDynamicRepoLock(string possibleDynamicRepoPath)
+ {
+ try
+ {
+ // Ensure directory exists
+ try { Directory.CreateDirectory(possibleDynamicRepoPath); } catch (IOException) { }
+ // Check if file exists and create lock file in a safe way
+ using (new FileStream(GetLockFile(possibleDynamicRepoPath), FileMode.CreateNew)) { }
+ }
+ catch (IOException)
+ {
+ return false;
+ }
+ return true;
+ }
+
+ static string GetLockFile(string repoPath)
+ {
+ return Path.Combine(repoPath, "dynamicrepository.lock");
+ }
+
+ static string GetAndLockTemporaryRepositoryPath(string targetUrl, string dynamicRepositoryLocation)
+ {
+ var repositoryName = targetUrl.Split('/', '\\').Last().Replace(".git", string.Empty);
+ var possiblePath = Path.Combine(dynamicRepositoryLocation, repositoryName);
+
+ var i = 1;
+ var originalPath = possiblePath;
+ var possiblePathExists = Directory.Exists(possiblePath);
+ if (VerifyDynamicRepositoryTarget(targetUrl, possiblePathExists, possiblePath)) return possiblePath;
+ do
+ {
+ if (i > 10)
+ {
+ throw new GitToolsException(string.Format(
+ "Failed to find a dynamic repository path at {0} -> {1}",
+ originalPath,
+ possiblePath));
+ }
+ possiblePath = string.Concat(originalPath, "_", i++.ToString());
+ possiblePathExists = Directory.Exists(possiblePath);
+ } while (!VerifyDynamicRepositoryTarget(targetUrl, possiblePathExists, possiblePath));
+
+ return possiblePath;
+ }
+
+ static bool VerifyDynamicRepositoryTarget(string targetUrl, bool possiblePathExists, string possiblePath)
+ {
+ // First take a lock on that path
+ var lockTaken = TakeDynamicRepoLock(possiblePath);
+ if (!lockTaken) return false;
+
+ if (!possiblePathExists) return true;
+
+ // Then verify it's suitable
+ if (!GitRepoHasMatchingRemote(possiblePath, targetUrl))
+ {
+ // Release lock if not suitable
+ ReleaseDynamicRepoLock(possiblePath);
+ return false;
+ }
+ return true;
+ }
+
+ static bool GitRepoHasMatchingRemote(string possiblePath, string targetUrl)
+ {
+ try
+ {
+ using (var repository = new Repository(possiblePath))
+ {
+ return repository.Network.Remotes.Any(r => r.Url == targetUrl);
+ }
+ }
+ catch (Exception)
+ {
+ return false;
+ }
+ }
+
+ [SuppressMessage("ReSharper", "ArgumentsStyleLiteral")]
+ [SuppressMessage("ReSharper", "ArgumentsStyleNamedExpression")]
+ static string CreateDynamicRepository(string targetPath, RepositoryInfo repositoryInfo, string targetBranch, string targetCommit)
+ {
+ Log.Info(string.Format("Creating dynamic repository at '{0}'", targetPath));
+
+ var gitDirectory = Path.Combine(targetPath, ".git");
+ if (Directory.Exists(gitDirectory))
+ {
+ Log.Info("Git repository already exists");
+ using (var repo = new Repository(gitDirectory))
+ {
+ // We need to fetch before we can checkout the commit
+ var remote = GitRepositoryHelper.EnsureOnlyOneRemoteIsDefined(repo);
+ GitRepositoryHelper.Fetch(repositoryInfo.Authentication, remote, repo);
+ CheckoutCommit(repo, targetCommit);
+ }
+ GitRepositoryHelper.NormalizeGitDirectory(gitDirectory, repositoryInfo.Authentication, noFetch: true, currentBranch: targetBranch);
+
+ return gitDirectory;
+ }
+
+ CloneRepository(repositoryInfo.Url, gitDirectory, repositoryInfo.Authentication);
+
+ using (var repo = new Repository(gitDirectory))
+ {
+ CheckoutCommit(repo, targetCommit);
+ }
+
+ // Normalize (download branches) before using the branch
+ GitRepositoryHelper.NormalizeGitDirectory(gitDirectory, repositoryInfo.Authentication, noFetch: true, currentBranch: targetBranch);
+
+ return gitDirectory;
+ }
+
+ static void CheckoutCommit(IRepository repo, string targetCommit)
+ {
+ Log.Info(string.Format("Checking out {0}", targetCommit));
+ Commands.Checkout(repo, targetCommit);
+ }
+
+ static void CloneRepository(string repositoryUrl, string gitDirectory, AuthenticationInfo authentication)
+ {
+ Credentials credentials = null;
+
+ if (authentication != null)
+ {
+ if (!string.IsNullOrWhiteSpace(authentication.Username) && !string.IsNullOrWhiteSpace(authentication.Password))
+ {
+ Log.Info(string.Format("Setting up credentials using name '{0}'", authentication.Username));
+
+ credentials = new UsernamePasswordCredentials
+ {
+ Username = authentication.Username,
+ Password = authentication.Password
+ };
+ }
+ }
+
+ Log.Info(string.Format("Retrieving git info from url '{0}'", repositoryUrl));
+
+ try
+ {
+ Repository.Clone(repositoryUrl, gitDirectory,
+ new CloneOptions
+ {
+ Checkout = false,
+ CredentialsProvider = (url, usernameFromUrl, types) => credentials
+ });
+ }
+ catch (LibGit2SharpException ex)
+ {
+ var message = ex.Message;
+ if (message.Contains("401"))
+ {
+ throw new GitToolsException("Unauthorised: Incorrect username/password");
+ }
+ if (message.Contains("403"))
+ {
+ throw new GitToolsException("Forbidden: Possbily Incorrect username/password");
+ }
+ if (message.Contains("404"))
+ {
+ throw new GitToolsException("Not found: The repository was not found");
+ }
+
+ throw new GitToolsException("There was an unknown problem with the Git repository you provided");
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/GitTools.Core/Git/DynamicRepository.cs b/src/GitTools.Core/Git/DynamicRepository.cs
new file mode 100644
index 0000000000..6f4e0e1435
--- /dev/null
+++ b/src/GitTools.Core/Git/DynamicRepository.cs
@@ -0,0 +1,31 @@
+namespace GitTools.Git
+{
+ using System;
+ using LibGit2Sharp;
+
+ public class DynamicRepository : IDisposable
+ {
+ readonly Action _dispose;
+
+ public DynamicRepository(Repository repository, Action dispose)
+ {
+ Repository = repository;
+ _dispose = dispose;
+ }
+
+ public Repository Repository { get; private set; }
+
+ public void Dispose()
+ {
+ try
+ {
+ Repository.Dispose();
+
+ }
+ finally
+ {
+ _dispose();
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/GitTools.Core/Git/Extensions/AuthenticationInfoExtensions.cs b/src/GitTools.Core/Git/Extensions/AuthenticationInfoExtensions.cs
new file mode 100644
index 0000000000..a421d2786c
--- /dev/null
+++ b/src/GitTools.Core/Git/Extensions/AuthenticationInfoExtensions.cs
@@ -0,0 +1,84 @@
+namespace GitTools.Git
+{
+ using LibGit2Sharp;
+ using Logging;
+
+ public static class AuthenticationInfoExtensions
+ {
+ static readonly ILog Log = LogProvider.GetLogger(typeof(AuthenticationInfoExtensions));
+
+ public static FetchOptions ToFetchOptions(this AuthenticationInfo authenticationInfo)
+ {
+ var fetchOptions = new FetchOptions();
+
+ if (authenticationInfo != null)
+ {
+ if (!string.IsNullOrEmpty(authenticationInfo.Username))
+ {
+ fetchOptions.CredentialsProvider = (url, user, types) => new UsernamePasswordCredentials
+ {
+ Username = authenticationInfo.Username,
+ Password = authenticationInfo.Password
+ };
+ }
+ }
+
+ return fetchOptions;
+ }
+
+ public static bool IsEmpty(this AuthenticationInfo authenticationInfo)
+ {
+ if (authenticationInfo == null)
+ {
+ return true;
+ }
+
+ if (IsUsernameAndPasswordAuthentication(authenticationInfo))
+ {
+ return false;
+ }
+
+ if (IsTokenAuthentication(authenticationInfo))
+ {
+ return false;
+ }
+
+ return true;
+ }
+
+ public static bool IsUsernameAndPasswordAuthentication(this AuthenticationInfo authenticationInfo)
+ {
+ if (authenticationInfo == null)
+ {
+ return false;
+ }
+
+ if (string.IsNullOrWhiteSpace(authenticationInfo.Username))
+ {
+ return false;
+ }
+
+ if (string.IsNullOrWhiteSpace(authenticationInfo.Password))
+ {
+ return false;
+ }
+
+ return true;
+ }
+
+ public static bool IsTokenAuthentication(this AuthenticationInfo authenticationInfo)
+ {
+ if (authenticationInfo == null)
+ {
+ return false;
+ }
+
+ if (string.IsNullOrWhiteSpace(authenticationInfo.Token))
+ {
+ return false;
+ }
+
+ return true;
+ }
+ }
+}
diff --git a/src/GitTools.Core/Git/Extensions/IRepositoryExtensions.cs b/src/GitTools.Core/Git/Extensions/IRepositoryExtensions.cs
new file mode 100644
index 0000000000..65131e4726
--- /dev/null
+++ b/src/GitTools.Core/Git/Extensions/IRepositoryExtensions.cs
@@ -0,0 +1,51 @@
+namespace GitTools.Git
+{
+ using System;
+ using System.Collections.Generic;
+ using System.Linq;
+ using LibGit2Sharp;
+
+ public static class IRepositoryExtensions
+ {
+ private static readonly Dictionary Cache = new Dictionary();
+
+ public static TaggedCommit GetLastTaggedCommit(this IRepository repository)
+ {
+ return GetTag(repository, string.Empty);
+ }
+
+ public static TaggedCommit GetFirstCommit(this IRepository repository)
+ {
+ var branch = repository.Head;
+ return new TaggedCommit(branch.Commits.Last(), "Initial Commit");
+ }
+
+ public static TaggedCommit GetTag(this IRepository repository, string fromTag)
+ {
+ if (!Cache.ContainsKey(fromTag))
+ {
+ var lastTaggedCommit = GetLastTaggedCommit(repository, t => string.IsNullOrEmpty(fromTag) || t.TagName == fromTag);
+ Cache.Add(fromTag, lastTaggedCommit);
+ }
+
+ return Cache[fromTag];
+ }
+
+ public static TaggedCommit GetLastTaggedCommit(this IRepository repository, Func filterTags)
+ {
+ var branch = repository.Head;
+ var tags = repository.Tags
+ .Select(t => new TaggedCommit((Commit) t.Target, t.FriendlyName))
+ .Where(filterTags)
+ .ToArray();
+ var olderThan = branch.Tip.Author.When;
+ var lastTaggedCommit = branch.Commits.FirstOrDefault(c => c.Author.When <= olderThan && tags.Any(a => a.Commit == c));
+ if (lastTaggedCommit != null)
+ {
+ return tags.FirstOrDefault(a => a.Commit.Sha == lastTaggedCommit.Sha);
+ }
+
+ return new TaggedCommit(branch.Commits.Last(), "Initial Commit");
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/GitTools.Core/Git/Extensions/LibGitExtensions.cs b/src/GitTools.Core/Git/Extensions/LibGitExtensions.cs
new file mode 100644
index 0000000000..e816e80c60
--- /dev/null
+++ b/src/GitTools.Core/Git/Extensions/LibGitExtensions.cs
@@ -0,0 +1,199 @@
+namespace GitTools
+{
+ using System;
+ using System.Collections.Generic;
+ using System.IO;
+ using System.Linq;
+ using System.Text;
+
+ using JetBrains.Annotations;
+
+ using LibGit2Sharp;
+ using Logging;
+
+ public static class LibGitExtensions
+ {
+ static readonly ILog Log = LogProvider.GetLogger(typeof(LibGitExtensions));
+
+ public static DateTimeOffset When(this Commit commit)
+ {
+ return commit.Committer.When;
+ }
+
+ public static Branch FindBranch(this IRepository repository, string branchName)
+ {
+ var exact = repository.Branches.FirstOrDefault(x => x.FriendlyName == branchName);
+ if (exact != null)
+ {
+ return exact;
+ }
+
+ return repository.Branches.FirstOrDefault(x => x.FriendlyName == "origin/" + branchName);
+ }
+
+ static bool IsSameBranch(Branch branch, Branch b)
+ {
+ return (b.IsRemote ? b.FriendlyName.Replace(b.RemoteName + "/", string.Empty) : b.FriendlyName) != branch.FriendlyName;
+ }
+
+ public static IEnumerable GetBranchesContainingCommit([NotNull] this Commit commit, IRepository repository, IList branches, bool onlyTrackedBranches)
+ {
+ if (commit == null)
+ {
+ throw new ArgumentNullException("commit");
+ }
+
+ var directBranchHasBeenFound = false;
+ foreach (var branch in branches)
+ {
+ if (branch.Tip != null && branch.Tip.Sha != commit.Sha || (onlyTrackedBranches && !branch.IsTracking))
+ {
+ continue;
+ }
+
+ directBranchHasBeenFound = true;
+ yield return branch;
+ }
+
+ if (directBranchHasBeenFound)
+ {
+ yield break;
+ }
+
+ foreach (var branch in branches.Where(b => (onlyTrackedBranches && !b.IsTracking)))
+ {
+ var commits = repository.Commits.QueryBy(new CommitFilter { IncludeReachableFrom = branch }).Where(c => c.Sha == commit.Sha);
+
+ if (!commits.Any())
+ {
+ continue;
+ }
+
+ yield return branch;
+ }
+ }
+
+ public static GitObject PeeledTarget(this Tag tag)
+ {
+ var target = tag.Target;
+
+ while (target is TagAnnotation)
+ {
+ target = ((TagAnnotation)(target)).Target;
+ }
+
+ return target;
+ }
+
+ public static IEnumerable CommitsPriorToThan(this Branch branch, DateTimeOffset olderThan)
+ {
+ return branch.Commits.SkipWhile(c => c.When() > olderThan);
+ }
+
+ public static bool IsDetachedHead(this Branch branch)
+ {
+ return branch.CanonicalName.Equals("(no branch)", StringComparison.OrdinalIgnoreCase);
+ }
+
+ public static string GetRepositoryDirectory(this IRepository repository, bool omitGitPostFix = true)
+ {
+ var gitDirectory = repository.Info.Path;
+
+ gitDirectory = gitDirectory.TrimEnd(Path.DirectorySeparatorChar);
+
+ if (omitGitPostFix && gitDirectory.EndsWith(".git"))
+ {
+ gitDirectory = gitDirectory.Substring(0, gitDirectory.Length - ".git".Length);
+ gitDirectory = gitDirectory.TrimEnd(Path.DirectorySeparatorChar);
+ }
+
+ return gitDirectory;
+ }
+
+ public static void CheckoutFilesIfExist(this IRepository repository, params string[] fileNames)
+ {
+ if (fileNames == null || fileNames.Length == 0)
+ {
+ return;
+ }
+
+ Log.Info("Checking out files that might be needed later in dynamic repository");
+
+ foreach (var fileName in fileNames)
+ {
+ try
+ {
+ Log.Info(string.Format(" Trying to check out '{0}'", fileName));
+
+ var headBranch = repository.Head;
+ var tip = headBranch.Tip;
+
+ var treeEntry = tip[fileName];
+ if (treeEntry == null)
+ {
+ continue;
+ }
+
+ var fullPath = Path.Combine(repository.GetRepositoryDirectory(), fileName);
+ using (var stream = ((Blob) treeEntry.Target).GetContentStream())
+ {
+ using (var streamReader = new BinaryReader(stream))
+ {
+ File.WriteAllBytes(fullPath, streamReader.ReadBytes((int)stream.Length));
+ }
+ }
+ }
+ catch (Exception ex)
+ {
+ Log.Warn(string.Format(" An error occurred while checking out '{0}': '{1}'", fileName, ex.Message));
+ }
+ }
+ }
+
+ public static void DumpGraph(this IRepository repository, Action writer = null, int? maxCommits = null)
+ {
+ DumpGraph(repository.Info.Path, writer, maxCommits);
+ }
+
+
+ public static void DumpGraph(string workingDirectory, Action writer = null, int? maxCommits = null)
+ {
+ var output = new StringBuilder();
+
+ try
+ {
+ ProcessHelper.Run(
+ o => output.AppendLine(o),
+ e => output.AppendLineFormat("ERROR: {0}", e),
+ null,
+ "git",
+ CreateGitLogArgs(maxCommits),
+ workingDirectory);
+ }
+ catch (FileNotFoundException exception)
+ {
+ if (exception.FileName != "git")
+ {
+ throw;
+ }
+
+ output.AppendLine("Could not execute 'git log' due to the following error:");
+ output.AppendLine(exception.ToString());
+ }
+
+ if (writer != null)
+ {
+ writer(output.ToString());
+ }
+ else
+ {
+ Console.Write(output.ToString());
+ }
+ }
+
+ public static string CreateGitLogArgs(int? maxCommits)
+ {
+ return @"log --graph --format=""%h %cr %d"" --decorate --date=relative --all --remotes=*" + (maxCommits != null ? string.Format(" -n {0}", maxCommits) : null);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/GitTools.Core/Git/GitDirFinder.cs b/src/GitTools.Core/Git/GitDirFinder.cs
new file mode 100644
index 0000000000..ecd7143b23
--- /dev/null
+++ b/src/GitTools.Core/Git/GitDirFinder.cs
@@ -0,0 +1,20 @@
+namespace GitTools.Git
+{
+ using System.IO;
+ using LibGit2Sharp;
+
+ public class GitDirFinder
+ {
+ public static string TreeWalkForDotGitDir(string currentDirectory)
+ {
+ var gitDirectory = Repository.Discover(currentDirectory);
+
+ if (gitDirectory != null)
+ {
+ return gitDirectory.TrimEnd(Path.DirectorySeparatorChar);
+ }
+
+ return null;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/GitTools.Core/Git/Helpers/BugException.cs b/src/GitTools.Core/Git/Helpers/BugException.cs
new file mode 100644
index 0000000000..4e1845c847
--- /dev/null
+++ b/src/GitTools.Core/Git/Helpers/BugException.cs
@@ -0,0 +1,11 @@
+namespace GitTools.Git
+{
+ using System;
+
+ public class BugException : Exception
+ {
+ public BugException(string message) : base(message)
+ {
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/GitTools.Core/Git/Helpers/GitRepositoryHelper.cs b/src/GitTools.Core/Git/Helpers/GitRepositoryHelper.cs
new file mode 100644
index 0000000000..64e1a5b9e2
--- /dev/null
+++ b/src/GitTools.Core/Git/Helpers/GitRepositoryHelper.cs
@@ -0,0 +1,308 @@
+namespace GitTools.Git
+{
+ using System;
+ using System.Collections.Generic;
+ using System.ComponentModel;
+ using System.Linq;
+ using LibGit2Sharp;
+ using Logging;
+
+ ///
+ /// Static helper class for creating/normalising git repositories
+ ///
+ public static class GitRepositoryHelper
+ {
+ static readonly ILog Log = LogProvider.GetLogger(typeof(GitRepositoryHelper));
+
+ ///
+ /// Normalisation of a git directory turns all remote branches into local branches, turns pull request refs into a real branch and a few other things. This is designed to be run *only on the build server* which checks out repositories in different ways.
+ /// It is not recommended to run normalisation against a local repository
+ ///
+ public static void NormalizeGitDirectory(string gitDirectory, AuthenticationInfo authentication, bool noFetch, string currentBranch)
+ {
+ using (var repo = new Repository(gitDirectory))
+ {
+ // Need to unsure the HEAD does not move, this is essentially a BugCheck
+ var expectedSha = repo.Head.Tip.Sha;
+ try
+ {
+ var remote = EnsureOnlyOneRemoteIsDefined(repo);
+
+ AddMissingRefSpecs(repo, remote);
+
+ //If noFetch is enabled, then GitVersion will assume that the git repository is normalized before execution, so that fetching from remotes is not required.
+ if (noFetch)
+ {
+ Log.Info("Skipping fetching, if GitVersion does not calculate your version as expected you might need to allow fetching or use dynamic repositories");
+ }
+ else
+ {
+ Fetch(authentication, remote, repo);
+ }
+
+ EnsureLocalBranchExistsForCurrentBranch(repo, remote, currentBranch);
+ CreateOrUpdateLocalBranchesFromRemoteTrackingOnes(repo, remote.Name);
+
+ var headSha = repo.Refs.Head.TargetIdentifier;
+
+ if (!repo.Info.IsHeadDetached)
+ {
+ Log.Info(string.Format("HEAD points at branch '{0}'.", headSha));
+ return;
+ }
+
+ Log.Info(string.Format("HEAD is detached and points at commit '{0}'.", headSha));
+ Log.Info(string.Format("Local Refs:\r\n" + string.Join(Environment.NewLine, repo.Refs.FromGlob("*").Select(r => string.Format("{0} ({1})", r.CanonicalName, r.TargetIdentifier)))));
+
+ // In order to decide whether a fake branch is required or not, first check to see if any local branches have the same commit SHA of the head SHA.
+ // If they do, go ahead and checkout that branch
+ // If no, go ahead and check out a new branch, using the known commit SHA as the pointer
+ var localBranchesWhereCommitShaIsHead = repo.Branches.Where(b => !b.IsRemote && b.Tip.Sha == headSha).ToList();
+
+ var matchingCurrentBranch = !string.IsNullOrEmpty(currentBranch)
+ ? localBranchesWhereCommitShaIsHead.SingleOrDefault(b => b.CanonicalName.Replace("/heads/", "/") == currentBranch.Replace("/heads/", "/"))
+ : null;
+ if (matchingCurrentBranch != null)
+ {
+ Log.Info(string.Format("Checking out local branch '{0}'.", currentBranch));
+ Commands.Checkout(repo, matchingCurrentBranch);
+ }
+ else if (localBranchesWhereCommitShaIsHead.Count > 1)
+ {
+ var branchNames = localBranchesWhereCommitShaIsHead.Select(r => r.CanonicalName);
+ var csvNames = string.Join(", ", branchNames);
+ const string moveBranchMsg = "Move one of the branches along a commit to remove warning";
+
+ Log.Warn(string.Format("Found more than one local branch pointing at the commit '{0}' ({1}).", headSha, csvNames));
+ var master = localBranchesWhereCommitShaIsHead.SingleOrDefault(n => n.FriendlyName == "master");
+ if (master != null)
+ {
+ Log.Warn("Because one of the branches is 'master', will build master." + moveBranchMsg);
+ Commands.Checkout(repo, master);
+ }
+ else
+ {
+ var branchesWithoutSeparators = localBranchesWhereCommitShaIsHead.Where(b => !b.FriendlyName.Contains('/') && !b.FriendlyName.Contains('-')).ToList();
+ if (branchesWithoutSeparators.Count == 1)
+ {
+ var branchWithoutSeparator = branchesWithoutSeparators[0];
+ Log.Warn(string.Format("Choosing {0} as it is the only branch without / or - in it. " + moveBranchMsg, branchWithoutSeparator.CanonicalName));
+ Commands.Checkout(repo, branchWithoutSeparator);
+ }
+ else
+ {
+ throw new WarningException("Failed to try and guess branch to use. " + moveBranchMsg);
+ }
+ }
+ }
+ else if (localBranchesWhereCommitShaIsHead.Count == 0)
+ {
+ Log.Info(string.Format("No local branch pointing at the commit '{0}'. Fake branch needs to be created.", headSha));
+ CreateFakeBranchPointingAtThePullRequestTip(repo, authentication);
+ }
+ else
+ {
+ Log.Info(string.Format("Checking out local branch 'refs/heads/{0}'.", localBranchesWhereCommitShaIsHead[0].FriendlyName));
+ Commands.Checkout(repo, repo.Branches[localBranchesWhereCommitShaIsHead[0].FriendlyName]);
+ }
+ }
+ finally
+ {
+ if (repo.Head.Tip.Sha != expectedSha)
+ {
+ if (Environment.GetEnvironmentVariable("IGNORE_NORMALISATION_GIT_HEAD_MOVE") != "1")
+ {
+ // Whoa, HEAD has moved, it shouldn't have. We need to blow up because there is a bug in normalisation
+ throw new BugException(string.Format(@"GitTools.Core has a bug, your HEAD has moved after repo normalisation.
+
+To disable this error set an environmental variable called IGNORE_NORMALISATION_GIT_HEAD_MOVE to 1
+
+Please run `git {0}` and submit it along with your build log (with personal info removed) in a new issue at https://github.com/GitTools/GitTools.Core",
+ LibGitExtensions.CreateGitLogArgs(100)));
+ }
+ }
+ }
+ }
+ }
+
+ public static void Fetch(AuthenticationInfo authentication, Remote remote, Repository repo)
+ {
+ Log.Info(string.Format("Fetching from remote '{0}' using the following refspecs: {1}.",
+ remote.Name, string.Join(", ", remote.FetchRefSpecs.Select(r => r.Specification))));
+ Commands.Fetch(repo, remote.Name, new string[0], authentication.ToFetchOptions(), null);
+ }
+
+ static void EnsureLocalBranchExistsForCurrentBranch(Repository repo, Remote remote, string currentBranch)
+ {
+ if (string.IsNullOrEmpty(currentBranch)) return;
+
+ var isRef = currentBranch.Contains("refs");
+ var isBranch = currentBranch.Contains("refs/heads");
+ var localCanonicalName = !isRef ? "refs/heads/" + currentBranch : isBranch ? currentBranch : currentBranch.Replace("refs/", "refs/heads/");
+
+ var repoTip = repo.Head.Tip;
+
+ // We currently have the rep.Head of the *default* branch, now we need to look up the right one
+ var originCanonicalName = string.Format("{0}/{1}", remote.Name, currentBranch);
+ var originBranch = repo.Branches[originCanonicalName];
+ if (originBranch != null)
+ {
+ repoTip = originBranch.Tip;
+ }
+
+ var repoTipId = repoTip.Id;
+
+ if (repo.Branches.All(b => b.CanonicalName != localCanonicalName))
+ {
+ Log.Info(isBranch ?
+ string.Format("Creating local branch {0}", localCanonicalName) :
+ string.Format("Creating local branch {0} pointing at {1}", localCanonicalName, repoTipId));
+ repo.Refs.Add(localCanonicalName, repoTipId);
+ }
+ else
+ {
+ Log.Info(isBranch ?
+ string.Format("Updating local branch {0} to point at {1}", localCanonicalName, repoTip.Sha) :
+ string.Format("Updating local branch {0} to match ref {1}", localCanonicalName, currentBranch));
+ repo.Refs.UpdateTarget(repo.Refs[localCanonicalName], repoTipId);
+ }
+
+ Commands.Checkout(repo, localCanonicalName);
+ }
+
+ static void AddMissingRefSpecs(Repository repo, Remote remote)
+ {
+ if (remote.FetchRefSpecs.Any(r => r.Source == "refs/heads/*"))
+ return;
+
+ var allBranchesFetchRefSpec = string.Format("+refs/heads/*:refs/remotes/{0}/*", remote.Name);
+
+ Log.Info(string.Format("Adding refspec: {0}", allBranchesFetchRefSpec));
+
+ repo.Network.Remotes.Update(remote.Name,
+ r => r.FetchRefSpecs.Add(allBranchesFetchRefSpec));
+ }
+
+ static void CreateFakeBranchPointingAtThePullRequestTip(Repository repo, AuthenticationInfo authentication)
+ {
+ var remote = repo.Network.Remotes.Single();
+
+ Log.Info("Fetching remote refs to see if there is a pull request ref");
+ var remoteTips = (string.IsNullOrEmpty(authentication.Username) ?
+ GetRemoteTipsForAnonymousUser(repo, remote) :
+ GetRemoteTipsUsingUsernamePasswordCredentials(repo, remote, authentication.Username, authentication.Password))
+ .ToList();
+
+ Log.Info("Remote Refs:\r\n" + string.Join(Environment.NewLine, remoteTips.Select(r => r.CanonicalName)));
+
+ var headTipSha = repo.Head.Tip.Sha;
+
+ var refs = remoteTips.Where(r => r.TargetIdentifier == headTipSha).ToList();
+
+ if (refs.Count == 0)
+ {
+ var message = string.Format("Couldn't find any remote tips from remote '{0}' pointing at the commit '{1}'.", remote.Url, headTipSha);
+ throw new WarningException(message);
+ }
+
+ if (refs.Count > 1)
+ {
+ var names = string.Join(", ", refs.Select(r => r.CanonicalName));
+ var message = string.Format("Found more than one remote tip from remote '{0}' pointing at the commit '{1}'. Unable to determine which one to use ({2}).", remote.Url, headTipSha, names);
+ throw new WarningException(message);
+ }
+
+ var reference = refs[0];
+ var canonicalName = reference.CanonicalName;
+ Log.Info(string.Format("Found remote tip '{0}' pointing at the commit '{1}'.", canonicalName, headTipSha));
+
+ if (canonicalName.StartsWith("refs/tags"))
+ {
+ Log.Info(string.Format("Checking out tag '{0}'", canonicalName));
+ Commands.Checkout(repo, reference.Target.Sha);
+ return;
+ }
+
+ if (!canonicalName.StartsWith("refs/pull/") && !canonicalName.StartsWith("refs/pull-requests/"))
+ {
+ var message = string.Format("Remote tip '{0}' from remote '{1}' doesn't look like a valid pull request.", canonicalName, remote.Url);
+ throw new WarningException(message);
+ }
+
+ var fakeBranchName = canonicalName.Replace("refs/pull/", "refs/heads/pull/").Replace("refs/pull-requests/", "refs/heads/pull-requests/");
+
+ Log.Info(string.Format("Creating fake local branch '{0}'.", fakeBranchName));
+ repo.Refs.Add(fakeBranchName, new ObjectId(headTipSha));
+
+ Log.Info(string.Format("Checking local branch '{0}' out.", fakeBranchName));
+ Commands.Checkout(repo, fakeBranchName);
+ }
+
+ static IEnumerable GetRemoteTipsUsingUsernamePasswordCredentials(Repository repo, Remote remote, string username, string password)
+ {
+ return repo.Network.ListReferences(remote, (url, fromUrl, types) => new UsernamePasswordCredentials
+ {
+ Username = username,
+ Password = password
+ }).Select(r => r.ResolveToDirectReference());
+ }
+
+ static IEnumerable GetRemoteTipsForAnonymousUser(Repository repo, Remote remote)
+ {
+ return repo.Network.ListReferences(remote).Select(r => r.ResolveToDirectReference());
+ }
+
+ static void CreateOrUpdateLocalBranchesFromRemoteTrackingOnes(Repository repo, string remoteName)
+ {
+ var prefix = string.Format("refs/remotes/{0}/", remoteName);
+ var remoteHeadCanonicalName = string.Format("{0}{1}", prefix, "HEAD");
+
+ foreach (var remoteTrackingReference in repo.Refs.FromGlob(prefix + "*").Where(r => r.CanonicalName != remoteHeadCanonicalName))
+ {
+ var remoteTrackingReferenceName = remoteTrackingReference.CanonicalName;
+ var branchName = remoteTrackingReferenceName.Substring(prefix.Length);
+ var localCanonicalName = "refs/heads/" + branchName;
+
+ // We do not want to touch our current branch
+ if (branchName == repo.Head.FriendlyName) continue;
+
+ if (repo.Refs.Any(x => x.CanonicalName == localCanonicalName))
+ {
+ var localRef = repo.Refs[localCanonicalName];
+ var remotedirectReference = remoteTrackingReference.ResolveToDirectReference();
+ if (localRef.ResolveToDirectReference().TargetIdentifier == remotedirectReference.TargetIdentifier)
+ {
+ Log.Info(string.Format("Skipping update of '{0}' as it already matches the remote ref.", remoteTrackingReference.CanonicalName));
+ continue;
+ }
+ var remoteRefTipId = remotedirectReference.Target.Id;
+ Log.Info(string.Format("Updating local ref '{0}' to point at {1}.", localRef.CanonicalName, remoteRefTipId));
+ repo.Refs.UpdateTarget(localRef, remoteRefTipId);
+ continue;
+ }
+
+ Log.Info(string.Format("Creating local branch from remote tracking '{0}'.", remoteTrackingReference.CanonicalName));
+ repo.Refs.Add(localCanonicalName, new ObjectId(remoteTrackingReference.ResolveToDirectReference().TargetIdentifier), true);
+
+ var branch = repo.Branches[branchName];
+ repo.Branches.Update(branch, b => b.TrackedBranch = remoteTrackingReferenceName);
+ }
+ }
+
+ public static Remote EnsureOnlyOneRemoteIsDefined(IRepository repo)
+ {
+ var remotes = repo.Network.Remotes;
+ var howMany = remotes.Count();
+
+ if (howMany == 1)
+ {
+ var remote = remotes.Single();
+ Log.Info(string.Format("One remote found ({0} -> '{1}').", remote.Name, remote.Url));
+ return remote;
+ }
+
+ var message = string.Format("{0} remote(s) have been detected. When being run on a build server, the Git repository is expected to bear one (and no more than one) remote.", howMany);
+ throw new WarningException(message);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/GitTools.Core/Git/RepositoryInfo.cs b/src/GitTools.Core/Git/RepositoryInfo.cs
new file mode 100644
index 0000000000..3a987ec8b1
--- /dev/null
+++ b/src/GitTools.Core/Git/RepositoryInfo.cs
@@ -0,0 +1,13 @@
+namespace GitTools.Git
+{
+ public class RepositoryInfo
+ {
+ public RepositoryInfo()
+ {
+ Authentication = new AuthenticationInfo();
+ }
+
+ public AuthenticationInfo Authentication { get; set; }
+ public string Url { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/src/GitTools.Core/Git/RepositoryLoader.cs b/src/GitTools.Core/Git/RepositoryLoader.cs
new file mode 100644
index 0000000000..b545160d48
--- /dev/null
+++ b/src/GitTools.Core/Git/RepositoryLoader.cs
@@ -0,0 +1,36 @@
+namespace GitTools.Git
+{
+ using System;
+ using LibGit2Sharp;
+ using Logging;
+
+ public class RepositoryLoader
+ {
+ static readonly ILog Log = LogProvider.GetLogger(typeof(RepositoryLoader));
+
+ public static Repository GetRepo(string gitDirectory)
+ {
+ try
+ {
+ var repository = new Repository(gitDirectory);
+
+ var branch = repository.Head;
+ if (branch.Tip == null)
+ {
+ throw Log.ErrorAndCreateException("No Tip found. Has repo been initialized?");
+ }
+
+ return repository;
+ }
+ catch (Exception ex)
+ {
+ if (ex.Message.Contains("LibGit2Sharp.Core.NativeMethods") || ex.Message.Contains("FilePathMarshaler"))
+ {
+ throw Log.ErrorAndCreateException("Restart of the process may be required to load an updated version of LibGit2Sharp.");
+ }
+
+ throw;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/GitTools.Core/Git/TaggedCommit.cs b/src/GitTools.Core/Git/TaggedCommit.cs
new file mode 100644
index 0000000000..1eec38256a
--- /dev/null
+++ b/src/GitTools.Core/Git/TaggedCommit.cs
@@ -0,0 +1,16 @@
+namespace GitTools.Git
+{
+ using LibGit2Sharp;
+
+ public class TaggedCommit
+ {
+ public TaggedCommit(Commit commit, string tagName)
+ {
+ Commit = commit;
+ TagName = tagName;
+ }
+
+ public Commit Commit { get; private set; }
+ public string TagName { get; private set; }
+ }
+}
\ No newline at end of file
diff --git a/src/GitTools.Core/GitTools.Core.csproj b/src/GitTools.Core/GitTools.Core.csproj
new file mode 100644
index 0000000000..ad6fb64cc0
--- /dev/null
+++ b/src/GitTools.Core/GitTools.Core.csproj
@@ -0,0 +1,28 @@
+
+
+ netstandard2.0
+ Library
+
+
+ GitTools.Core
+ GitTools.Core
+ GitTools Contributors
+ https://github.com/GitTools/GitTools.Core
+ false
+ git tools
+ Core library for GitTools
+ Copyright GitTools 2015.
+ https://github.com/GitTools/GitTools.Core/blob/master/LICENSE
+ https://raw.github.com/GitTools/GitTools.Core/master/GitTools_logo.png
+ TRACE;LIBLOG_PORTABLE;
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/GitTools.Core/Helpers/ProcessHelper.cs b/src/GitTools.Core/Helpers/ProcessHelper.cs
new file mode 100644
index 0000000000..46a2116641
--- /dev/null
+++ b/src/GitTools.Core/Helpers/ProcessHelper.cs
@@ -0,0 +1,195 @@
+namespace GitTools
+{
+ using System;
+ using System.Collections.Generic;
+ using System.ComponentModel;
+ using System.Diagnostics;
+ using System.IO;
+ using System.Threading;
+
+
+ public static class ProcessHelper
+ {
+ static volatile object lockObject = new object();
+
+ // http://social.msdn.microsoft.com/Forums/en/netfxbcl/thread/f6069441-4ab1-4299-ad6a-b8bb9ed36be3
+ public static Process Start(ProcessStartInfo startInfo)
+ {
+ Process process;
+
+ lock (lockObject)
+ {
+ using (new ChangeErrorMode(ErrorModes.FailCriticalErrors | ErrorModes.NoGpFaultErrorBox))
+ {
+ try
+ {
+ process = Process.Start(startInfo);
+ }
+ catch (Win32Exception exception)
+ {
+ switch ((NativeErrorCode)exception.NativeErrorCode)
+ {
+ case NativeErrorCode.Success:
+ // Success is not a failure.
+ break;
+
+ case NativeErrorCode.FileNotFound:
+ throw new FileNotFoundException(string.Format("The executable file '{0}' could not be found.",
+ startInfo.FileName),
+ startInfo.FileName,
+ exception);
+
+ case NativeErrorCode.PathNotFound:
+ throw new DirectoryNotFoundException(string.Format("The path to the executable file '{0}' could not be found.",
+ startInfo.FileName),
+ exception);
+ }
+
+ throw;
+ }
+
+ try
+ {
+ if (process != null)
+ {
+ process.PriorityClass = ProcessPriorityClass.Idle;
+ }
+ }
+ catch
+ {
+ // NOTE: It seems like in some situations, setting the priority class will throw a Win32Exception
+ // with the error code set to "Success", which I think we can safely interpret as a success and
+ // not an exception.
+ //
+ // See: https://travis-ci.org/GitTools/GitVersion/jobs/171288284#L2026
+ // And: https://msdn.microsoft.com/en-us/library/windows/desktop/ms681382.aspx
+ //
+ // There's also the case where the process might be killed before we try to adjust its priority
+ // class, in which case it will throw an InvalidOperationException. What we ideally should do
+ // is start the process in a "suspended" state, adjust the priority class, then resume it, but
+ // that's not possible in pure .NET.
+ //
+ // See: https://travis-ci.org/GitTools/GitVersion/jobs/166709203#L2278
+ // And: http://www.codeproject.com/Articles/230005/Launch-a-process-suspended
+ //
+ // -- @asbjornu
+ }
+ }
+ }
+
+ return process;
+ }
+
+ // http://csharptest.net/532/using-processstart-to-capture-console-output/
+ public static int Run(Action output, Action errorOutput, TextReader input, string exe, string args, string workingDirectory, params KeyValuePair[] environmentalVariables)
+ {
+ if (String.IsNullOrEmpty(exe))
+ throw new ArgumentNullException("exe");
+ if (output == null)
+ throw new ArgumentNullException("output");
+
+ workingDirectory = workingDirectory ?? Environment.CurrentDirectory;
+
+ var psi = new ProcessStartInfo
+ {
+ UseShellExecute = false,
+ RedirectStandardError = true,
+ RedirectStandardOutput = true,
+ RedirectStandardInput = true,
+ WindowStyle = ProcessWindowStyle.Hidden,
+ CreateNoWindow = true,
+ ErrorDialog = false,
+ WorkingDirectory = workingDirectory,
+ FileName = exe,
+ Arguments = args
+ };
+ foreach (var environmentalVariable in environmentalVariables)
+ {
+ if (psi.EnvironmentVariables.ContainsKey(environmentalVariable.Key))
+ {
+ psi.EnvironmentVariables[environmentalVariable.Key] = environmentalVariable.Value;
+ }
+ else
+ {
+ psi.EnvironmentVariables.Add(environmentalVariable.Key, environmentalVariable.Value);
+ }
+ if (psi.EnvironmentVariables.ContainsKey(environmentalVariable.Key) && environmentalVariable.Value == null)
+ psi.EnvironmentVariables.Remove(environmentalVariable.Key);
+ }
+
+ using (var process = Start(psi))
+ using (var mreOut = new ManualResetEvent(false))
+ using (var mreErr = new ManualResetEvent(false))
+ {
+ process.EnableRaisingEvents = true;
+ process.OutputDataReceived += (o, e) =>
+ {
+ // ReSharper disable once AccessToDisposedClosure
+ if (e.Data == null)
+ mreOut.Set();
+ else
+ output(e.Data);
+ };
+ process.BeginOutputReadLine();
+ process.ErrorDataReceived += (o, e) =>
+ {
+ // ReSharper disable once AccessToDisposedClosure
+ if (e.Data == null)
+ mreErr.Set();
+ else
+ errorOutput(e.Data);
+ };
+ process.BeginErrorReadLine();
+
+ string line;
+ while (input != null && null != (line = input.ReadLine()))
+ process.StandardInput.WriteLine(line);
+
+ process.StandardInput.Close();
+ process.WaitForExit();
+
+ mreOut.WaitOne();
+ mreErr.WaitOne();
+
+ return process.ExitCode;
+ }
+ }
+
+ ///
+ /// System error codes.
+ /// See: https://msdn.microsoft.com/en-us/library/windows/desktop/ms681382.aspx
+ ///
+ private enum NativeErrorCode
+ {
+ Success = 0x0,
+ FileNotFound = 0x2,
+ PathNotFound = 0x3
+ }
+
+ [Flags]
+ public enum ErrorModes
+ {
+ Default = 0x0,
+ FailCriticalErrors = 0x1,
+ NoGpFaultErrorBox = 0x2,
+ NoAlignmentFaultExcept = 0x4,
+ NoOpenFileErrorBox = 0x8000
+ }
+
+ public struct ChangeErrorMode : IDisposable
+ {
+ readonly int oldMode;
+
+ public ChangeErrorMode(ErrorModes mode)
+ {
+ Environment.ExitCode = oldMode = (int) mode;
+ }
+
+
+ void IDisposable.Dispose()
+ {
+ Environment.ExitCode = oldMode;
+ }
+ }
+ }
+}
diff --git a/src/GitTools.Core/IO/FileSystem.cs b/src/GitTools.Core/IO/FileSystem.cs
new file mode 100644
index 0000000000..27019a79f3
--- /dev/null
+++ b/src/GitTools.Core/IO/FileSystem.cs
@@ -0,0 +1,48 @@
+namespace GitTools.IO
+{
+ using System.Collections.Generic;
+ using System.IO;
+
+ public class FileSystem : IFileSystem
+ {
+ public void Copy(string @from, string to, bool overwrite)
+ {
+ File.Copy(from, to, overwrite);
+ }
+
+ public void Move(string @from, string to)
+ {
+ File.Move(from, to);
+ }
+
+ public bool Exists(string file)
+ {
+ return File.Exists(file);
+ }
+
+ public void Delete(string path)
+ {
+ File.Delete(path);
+ }
+
+ public string ReadAllText(string path)
+ {
+ return File.ReadAllText(path);
+ }
+
+ public void WriteAllText(string file, string fileContents)
+ {
+ File.WriteAllText(file, fileContents);
+ }
+
+ public IEnumerable DirectoryGetFiles(string directory, string searchPattern, SearchOption searchOption)
+ {
+ return Directory.GetFiles(directory, searchPattern, searchOption);
+ }
+
+ public Stream OpenWrite(string path)
+ {
+ return File.OpenWrite(path);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/GitTools.Core/IO/Helpers/DeleteHelper.cs b/src/GitTools.Core/IO/Helpers/DeleteHelper.cs
new file mode 100644
index 0000000000..f51bf1f52e
--- /dev/null
+++ b/src/GitTools.Core/IO/Helpers/DeleteHelper.cs
@@ -0,0 +1,61 @@
+namespace GitTools.IO
+{
+ using System;
+ using System.IO;
+
+ public static class DeleteHelper
+ {
+ public static void DeleteGitRepository(string directory)
+ {
+ if (string.IsNullOrEmpty(directory))
+ {
+ return;
+ }
+
+ try
+ {
+ foreach (var fileName in Directory.GetFiles(directory, "*.*", SearchOption.AllDirectories))
+ {
+ var fileInfo = new FileInfo(fileName)
+ {
+ IsReadOnly = false
+ };
+
+ try
+ {
+ fileInfo.Delete();
+ }
+ catch (FileNotFoundException)
+ {
+ }
+ catch (UnauthorizedAccessException)
+ {
+ }
+ }
+
+ Directory.Delete(directory, true);
+ }
+ catch (DirectoryNotFoundException)
+ {
+ }
+ catch (UnauthorizedAccessException)
+ {
+ }
+ }
+
+
+ public static void DeleteDirectory(string directory, bool recursive)
+ {
+ try
+ {
+ Directory.Delete(directory, recursive);
+ }
+ catch (DirectoryNotFoundException)
+ {
+ }
+ catch (UnauthorizedAccessException)
+ {
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/GitTools.Core/IO/Interfaces/IFileSystem.cs b/src/GitTools.Core/IO/Interfaces/IFileSystem.cs
new file mode 100644
index 0000000000..0be0ef6dc1
--- /dev/null
+++ b/src/GitTools.Core/IO/Interfaces/IFileSystem.cs
@@ -0,0 +1,17 @@
+namespace GitTools.IO
+{
+ using System.Collections.Generic;
+ using System.IO;
+
+ public interface IFileSystem
+ {
+ void Copy(string from, string to, bool overwrite);
+ void Move(string from, string to);
+ bool Exists(string file);
+ void Delete(string path);
+ string ReadAllText(string path);
+ void WriteAllText(string file, string fileContents);
+ IEnumerable DirectoryGetFiles(string directory, string searchPattern, SearchOption searchOption);
+ Stream OpenWrite(string path);
+ }
+}
\ No newline at end of file
diff --git a/src/GitTools.Core/IO/TemporaryFilesContext.cs b/src/GitTools.Core/IO/TemporaryFilesContext.cs
new file mode 100644
index 0000000000..afc84300f6
--- /dev/null
+++ b/src/GitTools.Core/IO/TemporaryFilesContext.cs
@@ -0,0 +1,65 @@
+namespace GitTools
+{
+ using System;
+ using System.IO;
+ using Logging;
+
+ public class TemporaryFilesContext : IDisposable
+ {
+ static readonly ILog Log = LogProvider.GetLogger(typeof(TemporaryFilesContext));
+ private readonly Guid _randomGuid = Guid.NewGuid();
+ private readonly string _rootDirectory;
+
+ public TemporaryFilesContext()
+ {
+ _rootDirectory = Path.Combine(Path.GetTempPath(), "GitTools", _randomGuid.ToString());
+
+ Directory.CreateDirectory(_rootDirectory);
+ }
+
+ ///
+ /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
+ ///
+ public void Dispose()
+ {
+ Log.InfoFormat("Deleting temporary files from '{0}'", _rootDirectory);
+
+ try
+ {
+ if (Directory.Exists(_rootDirectory))
+ {
+ Directory.Delete(_rootDirectory, true);
+ }
+ }
+ catch (Exception ex)
+ {
+ Log.ErrorException("Failed to delete temporary files", ex);
+ }
+ }
+
+ public string GetDirectory(string relativeDirectoryName)
+ {
+ var fullPath = Path.Combine(_rootDirectory, relativeDirectoryName);
+
+ if (!Directory.Exists(fullPath))
+ {
+ Directory.CreateDirectory(fullPath);
+ }
+
+ return fullPath;
+ }
+
+ public string GetFile(string relativeFilePath)
+ {
+ var fullPath = Path.Combine(_rootDirectory, relativeFilePath);
+
+ var directory = Path.GetDirectoryName(fullPath);
+ if (!Directory.Exists(directory))
+ {
+ Directory.CreateDirectory(directory);
+ }
+
+ return fullPath;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/GitTools.Core/LibLog.4.2/LibLog.cs b/src/GitTools.Core/LibLog.4.2/LibLog.cs
new file mode 100644
index 0000000000..0d18b39089
--- /dev/null
+++ b/src/GitTools.Core/LibLog.4.2/LibLog.cs
@@ -0,0 +1,2380 @@
+//===============================================================================
+// LibLog
+//
+// https://github.com/damianh/LibLog
+//===============================================================================
+// Copyright © 2011-2015 Damian Hickey. All rights reserved.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+//===============================================================================
+
+// ReSharper disable PossibleNullReferenceException
+
+// Define LIBLOG_PORTABLE conditional compilation symbol for PCL compatibility
+//
+// Define LIBLOG_PUBLIC to enable ability to GET a logger (LogProvider.For<>() etc) from outside this library. NOTE:
+// this can have unintended consequences of consumers of your library using your library to resolve a logger. If the
+// reason is because you want to open this functionality to other projects within your solution,
+// consider [InternalsVisibleTo] instead.
+//
+// Define LIBLOG_PROVIDERS_ONLY if your library provides its own logging API and you just want to use the
+// LibLog providers internally to provide built in support for popular logging frameworks.
+
+#pragma warning disable 1591
+
+using System.Diagnostics.CodeAnalysis;
+
+[assembly: SuppressMessage("Microsoft.Design", "CA1020:AvoidNamespacesWithFewTypes", Scope = "namespace", Target = "GitTools.Logging")]
+[assembly: SuppressMessage("Microsoft.Design", "CA1026:DefaultParametersShouldNotBeUsed", Scope = "member", Target = "GitTools.Logging.Logger.#Invoke(GitTools.Logging.LogLevel,System.Func`1,System.Exception,System.Object[])")]
+
+// If you copied this file manually, you need to change all "YourRootNameSpace" so not to clash with other libraries
+// that use LibLog
+#if LIBLOG_PROVIDERS_ONLY
+namespace GitTools.Logging
+#else
+namespace GitTools.Logging
+#endif
+{
+ using System.Collections.Generic;
+ using System.Diagnostics.CodeAnalysis;
+#if LIBLOG_PROVIDERS_ONLY
+ using GitTools.Logging.LibLog.LogProviders;
+#else
+ using GitTools.Logging.LogProviders;
+#endif
+ using System;
+#if !LIBLOG_PROVIDERS_ONLY
+ using System.Diagnostics;
+#if !LIBLOG_PORTABLE
+ using System.Runtime.CompilerServices;
+#endif
+#endif
+
+#if LIBLOG_PROVIDERS_ONLY
+ internal
+#else
+ public
+#endif
+ delegate bool Logger(LogLevel logLevel, Func messageFunc, Exception exception = null, params object[] formatParameters);
+
+#if !LIBLOG_PROVIDERS_ONLY
+ ///
+ /// Simple interface that represent a logger.
+ ///
+#if LIBLOG_PUBLIC
+ public
+#else
+ internal
+#endif
+ interface ILog
+ {
+ ///
+ /// Log a message the specified log level.
+ ///
+ /// The log level.
+ /// The message function.
+ /// An optional exception.
+ /// Optional format parameters for the message generated by the messagefunc.
+ /// true if the message was logged. Otherwise false.
+ ///
+ /// Note to implementers: the message func should not be called if the loglevel is not enabled
+ /// so as not to incur performance penalties.
+ ///
+ /// To check IsEnabled call Log with only LogLevel and check the return value, no event will be written.
+ ///
+ bool Log(LogLevel logLevel, Func messageFunc, Exception exception = null, params object[] formatParameters);
+ }
+#endif
+
+ ///
+ /// The log level.
+ ///
+#if LIBLOG_PROVIDERS_ONLY
+ internal
+#else
+ public
+#endif
+ enum LogLevel
+ {
+ Trace,
+ Debug,
+ Info,
+ Warn,
+ Error,
+ Fatal
+ }
+
+#if !LIBLOG_PROVIDERS_ONLY
+#if !LIBLOG_PORTABLE
+ [ExcludeFromCodeCoverage]
+#endif
+#if LIBLOG_PUBLIC
+ public
+#else
+ internal
+#endif
+ static partial class LogExtensions
+ {
+ public static bool IsDebugEnabled(this ILog logger)
+ {
+ GuardAgainstNullLogger(logger);
+ return logger.Log(LogLevel.Debug, null);
+ }
+
+ public static bool IsErrorEnabled(this ILog logger)
+ {
+ GuardAgainstNullLogger(logger);
+ return logger.Log(LogLevel.Error, null);
+ }
+
+ public static bool IsFatalEnabled(this ILog logger)
+ {
+ GuardAgainstNullLogger(logger);
+ return logger.Log(LogLevel.Fatal, null);
+ }
+
+ public static bool IsInfoEnabled(this ILog logger)
+ {
+ GuardAgainstNullLogger(logger);
+ return logger.Log(LogLevel.Info, null);
+ }
+
+ public static bool IsTraceEnabled(this ILog logger)
+ {
+ GuardAgainstNullLogger(logger);
+ return logger.Log(LogLevel.Trace, null);
+ }
+
+ public static bool IsWarnEnabled(this ILog logger)
+ {
+ GuardAgainstNullLogger(logger);
+ return logger.Log(LogLevel.Warn, null);
+ }
+
+ public static void Debug(this ILog logger, Func messageFunc)
+ {
+ GuardAgainstNullLogger(logger);
+ logger.Log(LogLevel.Debug, messageFunc);
+ }
+
+ public static void Debug(this ILog logger, string message)
+ {
+ if (logger.IsDebugEnabled())
+ {
+ logger.Log(LogLevel.Debug, message.AsFunc());
+ }
+ }
+
+ public static void Debug(this ILog logger, string message, params object[] args)
+ {
+ logger.DebugFormat(message, args);
+ }
+
+ public static void Debug(this ILog logger, Exception exception, string message, params object[] args)
+ {
+ logger.DebugException(message, exception, args);
+ }
+
+ public static void DebugFormat(this ILog logger, string message, params object[] args)
+ {
+ if (logger.IsDebugEnabled())
+ {
+ logger.LogFormat(LogLevel.Debug, message, args);
+ }
+ }
+
+ public static void DebugException(this ILog logger, string message, Exception exception)
+ {
+ if (logger.IsDebugEnabled())
+ {
+ logger.Log(LogLevel.Debug, message.AsFunc(), exception);
+ }
+ }
+
+ public static void DebugException(this ILog logger, string message, Exception exception, params object[] formatParams)
+ {
+ if (logger.IsDebugEnabled())
+ {
+ logger.Log(LogLevel.Debug, message.AsFunc(), exception, formatParams);
+ }
+ }
+
+ public static void Error(this ILog logger, Func messageFunc)
+ {
+ GuardAgainstNullLogger(logger);
+ logger.Log(LogLevel.Error, messageFunc);
+ }
+
+ public static void Error(this ILog logger, string message)
+ {
+ if (logger.IsErrorEnabled())
+ {
+ logger.Log(LogLevel.Error, message.AsFunc());
+ }
+ }
+
+ public static void Error(this ILog logger, string message, params object[] args)
+ {
+ logger.ErrorFormat(message, args);
+ }
+
+ public static void Error(this ILog logger, Exception exception, string message, params object[] args)
+ {
+ logger.ErrorException(message, exception, args);
+ }
+
+ public static void ErrorFormat(this ILog logger, string message, params object[] args)
+ {
+ if (logger.IsErrorEnabled())
+ {
+ logger.LogFormat(LogLevel.Error, message, args);
+ }
+ }
+
+ public static void ErrorException(this ILog logger, string message, Exception exception, params object[] formatParams)
+ {
+ if (logger.IsErrorEnabled())
+ {
+ logger.Log(LogLevel.Error, message.AsFunc(), exception, formatParams);
+ }
+ }
+
+ public static void Fatal(this ILog logger, Func messageFunc)
+ {
+ logger.Log(LogLevel.Fatal, messageFunc);
+ }
+
+ public static void Fatal(this ILog logger, string message)
+ {
+ if (logger.IsFatalEnabled())
+ {
+ logger.Log(LogLevel.Fatal, message.AsFunc());
+ }
+ }
+
+ public static void Fatal(this ILog logger, string message, params object[] args)
+ {
+ logger.FatalFormat(message, args);
+ }
+
+ public static void Fatal(this ILog logger, Exception exception, string message, params object[] args)
+ {
+ logger.FatalException(message, exception, args);
+ }
+
+ public static void FatalFormat(this ILog logger, string message, params object[] args)
+ {
+ if (logger.IsFatalEnabled())
+ {
+ logger.LogFormat(LogLevel.Fatal, message, args);
+ }
+ }
+
+ public static void FatalException(this ILog logger, string message, Exception exception, params object[] formatParams)
+ {
+ if (logger.IsFatalEnabled())
+ {
+ logger.Log(LogLevel.Fatal, message.AsFunc(), exception, formatParams);
+ }
+ }
+
+ public static void Info(this ILog logger, Func messageFunc)
+ {
+ GuardAgainstNullLogger(logger);
+ logger.Log(LogLevel.Info, messageFunc);
+ }
+
+ public static void Info(this ILog logger, string message)
+ {
+ if (logger.IsInfoEnabled())
+ {
+ logger.Log(LogLevel.Info, message.AsFunc());
+ }
+ }
+
+ public static void Info(this ILog logger, string message, params object[] args)
+ {
+ logger.InfoFormat(message, args);
+ }
+
+ public static void Info(this ILog logger, Exception exception, string message, params object[] args)
+ {
+ logger.InfoException(message, exception, args);
+ }
+
+ public static void InfoFormat(this ILog logger, string message, params object[] args)
+ {
+ if (logger.IsInfoEnabled())
+ {
+ logger.LogFormat(LogLevel.Info, message, args);
+ }
+ }
+
+ public static void InfoException(this ILog logger, string message, Exception exception, params object[] formatParams)
+ {
+ if (logger.IsInfoEnabled())
+ {
+ logger.Log(LogLevel.Info, message.AsFunc(), exception, formatParams);
+ }
+ }
+
+ public static void Trace(this ILog logger, Func messageFunc)
+ {
+ GuardAgainstNullLogger(logger);
+ logger.Log(LogLevel.Trace, messageFunc);
+ }
+
+ public static void Trace(this ILog logger, string message)
+ {
+ if (logger.IsTraceEnabled())
+ {
+ logger.Log(LogLevel.Trace, message.AsFunc());
+ }
+ }
+
+ public static void Trace(this ILog logger, string message, params object[] args)
+ {
+ logger.TraceFormat(message, args);
+ }
+
+ public static void Trace(this ILog logger, Exception exception, string message, params object[] args)
+ {
+ logger.TraceException(message, exception, args);
+ }
+
+ public static void TraceFormat(this ILog logger, string message, params object[] args)
+ {
+ if (logger.IsTraceEnabled())
+ {
+ logger.LogFormat(LogLevel.Trace, message, args);
+ }
+ }
+
+ public static void TraceException(this ILog logger, string message, Exception exception, params object[] formatParams)
+ {
+ if (logger.IsTraceEnabled())
+ {
+ logger.Log(LogLevel.Trace, message.AsFunc(), exception, formatParams);
+ }
+ }
+
+ public static void Warn(this ILog logger, Func messageFunc)
+ {
+ GuardAgainstNullLogger(logger);
+ logger.Log(LogLevel.Warn, messageFunc);
+ }
+
+ public static void Warn(this ILog logger, string message)
+ {
+ if (logger.IsWarnEnabled())
+ {
+ logger.Log(LogLevel.Warn, message.AsFunc());
+ }
+ }
+
+ public static void Warn(this ILog logger, string message, params object[] args)
+ {
+ logger.WarnFormat(message, args);
+ }
+
+ public static void Warn(this ILog logger, Exception exception, string message, params object[] args)
+ {
+ logger.WarnException(message, exception, args);
+ }
+
+ public static void WarnFormat(this ILog logger, string message, params object[] args)
+ {
+ if (logger.IsWarnEnabled())
+ {
+ logger.LogFormat(LogLevel.Warn, message, args);
+ }
+ }
+
+ public static void WarnException(this ILog logger, string message, Exception exception, params object[] formatParams)
+ {
+ if (logger.IsWarnEnabled())
+ {
+ logger.Log(LogLevel.Warn, message.AsFunc(), exception, formatParams);
+ }
+ }
+
+ // ReSharper disable once UnusedParameter.Local
+ private static void GuardAgainstNullLogger(ILog logger)
+ {
+ if (logger == null)
+ {
+ throw new ArgumentNullException("logger");
+ }
+ }
+
+ private static void LogFormat(this ILog logger, LogLevel logLevel, string message, params object[] args)
+ {
+ logger.Log(logLevel, message.AsFunc(), null, args);
+ }
+
+ // Avoid the closure allocation, see https://gist.github.com/AArnott/d285feef75c18f6ecd2b
+ private static Func AsFunc(this T value) where T : class
+ {
+ return value.Return;
+ }
+
+ private static T Return(this T value)
+ {
+ return value;
+ }
+ }
+#endif
+
+ ///
+ /// Represents a way to get a
+ ///
+#if LIBLOG_PROVIDERS_ONLY
+ internal
+#else
+ public
+#endif
+ interface ILogProvider
+ {
+ ///
+ /// Gets the specified named logger.
+ ///
+ /// Name of the logger.
+ /// The logger reference.
+ Logger GetLogger(string name);
+
+ ///
+ /// Opens a nested diagnostics context. Not supported in EntLib logging.
+ ///
+ /// The message to add to the diagnostics context.
+ /// A disposable that when disposed removes the message from the context.
+ IDisposable OpenNestedContext(string message);
+
+ ///
+ /// Opens a mapped diagnostics context. Not supported in EntLib logging.
+ ///
+ /// A key.
+ /// A value.
+ /// A disposable that when disposed removes the map from the context.
+ IDisposable OpenMappedContext(string key, string value);
+ }
+
+ ///
+ /// Provides a mechanism to create instances of objects.
+ ///
+#if !LIBLOG_PORTABLE
+ [ExcludeFromCodeCoverage]
+#endif
+#if LIBLOG_PROVIDERS_ONLY
+ internal
+#else
+ public
+#endif
+ static class LogProvider
+ {
+#if !LIBLOG_PROVIDERS_ONLY
+ private const string NullLogProvider = "Current Log Provider is not set. Call SetCurrentLogProvider " +
+ "with a non-null value first.";
+ private static dynamic s_currentLogProvider;
+ private static Action s_onCurrentLogProviderSet;
+
+ [SuppressMessage("Microsoft.Performance", "CA1810:InitializeReferenceTypeStaticFieldsInline")]
+ static LogProvider()
+ {
+ IsDisabled = false;
+ }
+
+ ///
+ /// Sets the current log provider.
+ ///
+ /// The log provider.
+ public static void SetCurrentLogProvider(ILogProvider logProvider)
+ {
+ s_currentLogProvider = logProvider;
+
+ RaiseOnCurrentLogProviderSet();
+ }
+
+ ///
+ /// Gets or sets a value indicating whether this is logging is disabled.
+ ///
+ ///
+ /// true if logging is disabled; otherwise, false.
+ ///
+ public static bool IsDisabled { get; set; }
+
+ ///
+ /// Sets an action that is invoked when a consumer of your library has called SetCurrentLogProvider. It is
+ /// important that hook into this if you are using child libraries (especially ilmerged ones) that are using
+ /// LibLog (or other logging abstraction) so you adapt and delegate to them.
+ ///
+ ///
+ internal static Action OnCurrentLogProviderSet
+ {
+ set
+ {
+ s_onCurrentLogProviderSet = value;
+ RaiseOnCurrentLogProviderSet();
+ }
+ }
+
+ internal static ILogProvider CurrentLogProvider
+ {
+ get
+ {
+ return s_currentLogProvider;
+ }
+ }
+
+ ///
+ /// Gets a logger for the specified type.
+ ///
+ /// The type whose name will be used for the logger.
+ /// An instance of
+#if LIBLOG_PUBLIC
+ public
+#else
+ internal
+#endif
+ static ILog For()
+ {
+ return GetLogger(typeof(T));
+ }
+
+#if !LIBLOG_PORTABLE
+ ///
+ /// Gets a logger for the current class.
+ ///
+ /// An instance of
+ [MethodImpl(MethodImplOptions.NoInlining)]
+#if LIBLOG_PUBLIC
+ public
+#else
+ internal
+#endif
+ static ILog GetCurrentClassLogger()
+ {
+ var stackFrame = new StackFrame(1, false);
+ return GetLogger(stackFrame.GetMethod().DeclaringType);
+ }
+#endif
+
+ ///
+ /// Gets a logger for the specified type.
+ ///
+ /// The type whose name will be used for the logger.
+ /// If the type is null then this name will be used as the log name instead
+ /// An instance of
+#if LIBLOG_PUBLIC
+ public
+#else
+ internal
+#endif
+ static ILog GetLogger(Type type, string fallbackTypeName = "System.Object")
+ {
+ // If the type passed in is null then fallback to the type name specified
+ return GetLogger(type != null ? type.FullName : fallbackTypeName);
+ }
+
+ ///
+ /// Gets a logger with the specified name.
+ ///
+ /// The name.
+ /// An instance of
+#if LIBLOG_PUBLIC
+ public
+#else
+ internal
+#endif
+ static ILog GetLogger(string name)
+ {
+ ILogProvider logProvider = CurrentLogProvider ?? ResolveLogProvider();
+ return logProvider == null
+ ? NoOpLogger.Instance
+ : (ILog)new LoggerExecutionWrapper(logProvider.GetLogger(name), () => IsDisabled);
+ }
+
+ ///
+ /// Opens a nested diagnostics context.
+ ///
+ /// A message.
+ /// An that closes context when disposed.
+ [SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "SetCurrentLogProvider")]
+#if LIBLOG_PUBLIC
+ public
+#else
+ internal
+#endif
+ static IDisposable OpenNestedContext(string message)
+ {
+ ILogProvider logProvider = CurrentLogProvider ?? ResolveLogProvider();
+
+ return logProvider == null
+ ? new DisposableAction(() => { })
+ : logProvider.OpenNestedContext(message);
+ }
+
+ ///
+ /// Opens a mapped diagnostics context.
+ ///
+ /// A key.
+ /// A value.
+ /// An that closes context when disposed.
+ [SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "SetCurrentLogProvider")]
+#if LIBLOG_PUBLIC
+ public
+#else
+ internal
+#endif
+ static IDisposable OpenMappedContext(string key, string value)
+ {
+ ILogProvider logProvider = CurrentLogProvider ?? ResolveLogProvider();
+
+ return logProvider == null
+ ? new DisposableAction(() => { })
+ : logProvider.OpenMappedContext(key, value);
+ }
+#endif
+
+#if LIBLOG_PROVIDERS_ONLY
+ private
+#else
+ internal
+#endif
+ delegate bool IsLoggerAvailable();
+
+#if LIBLOG_PROVIDERS_ONLY
+ private
+#else
+ internal
+#endif
+ delegate ILogProvider CreateLogProvider();
+
+#if LIBLOG_PROVIDERS_ONLY
+ private
+#else
+ internal
+#endif
+ static readonly List> LogProviderResolvers =
+ new List>
+ {
+ new Tuple(SerilogLogProvider.IsLoggerAvailable, () => new SerilogLogProvider()),
+ new Tuple(NLogLogProvider.IsLoggerAvailable, () => new NLogLogProvider()),
+ new Tuple(Log4NetLogProvider.IsLoggerAvailable, () => new Log4NetLogProvider()),
+ new Tuple(EntLibLogProvider.IsLoggerAvailable, () => new EntLibLogProvider()),
+ new Tuple(LoupeLogProvider.IsLoggerAvailable, () => new LoupeLogProvider()),
+ };
+
+#if !LIBLOG_PROVIDERS_ONLY
+ private static void RaiseOnCurrentLogProviderSet()
+ {
+ if (s_onCurrentLogProviderSet != null)
+ {
+ s_onCurrentLogProviderSet(s_currentLogProvider);
+ }
+ }
+#endif
+
+ [SuppressMessage("Microsoft.Globalization", "CA1303:Do not pass literals as localized parameters", MessageId = "System.Console.WriteLine(System.String,System.Object,System.Object)")]
+ [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
+ internal static ILogProvider ResolveLogProvider()
+ {
+ try
+ {
+ foreach (var providerResolver in LogProviderResolvers)
+ {
+ if (providerResolver.Item1())
+ {
+ return providerResolver.Item2();
+ }
+ }
+ }
+ catch (Exception ex)
+ {
+#if LIBLOG_PORTABLE
+ Debug.WriteLine(
+#else
+ Console.WriteLine(
+#endif
+ "Exception occurred resolving a log provider. Logging for this assembly {0} is disabled. {1}",
+ typeof(LogProvider).GetAssemblyPortable().FullName,
+ ex);
+ }
+ return null;
+ }
+
+#if !LIBLOG_PROVIDERS_ONLY
+#if !LIBLOG_PORTABLE
+ [ExcludeFromCodeCoverage]
+#endif
+ internal class NoOpLogger : ILog
+ {
+ internal static readonly NoOpLogger Instance = new NoOpLogger();
+
+ public bool Log(LogLevel logLevel, Func messageFunc, Exception exception, params object[] formatParameters)
+ {
+ return false;
+ }
+ }
+#endif
+ }
+
+#if !LIBLOG_PROVIDERS_ONLY
+#if !LIBLOG_PORTABLE
+ [ExcludeFromCodeCoverage]
+#endif
+ internal class LoggerExecutionWrapper : ILog
+ {
+ private readonly Logger _logger;
+ private readonly Func _getIsDisabled;
+ internal const string FailedToGenerateLogMessage = "Failed to generate log message";
+
+ internal LoggerExecutionWrapper(Logger logger, Func getIsDisabled = null)
+ {
+ _logger = logger;
+ _getIsDisabled = getIsDisabled ?? (() => false);
+ }
+
+ internal Logger WrappedLogger
+ {
+ get { return _logger; }
+ }
+
+ [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
+ public bool Log(LogLevel logLevel, Func messageFunc, Exception exception = null, params object[] formatParameters)
+ {
+ if (_getIsDisabled())
+ {
+ return false;
+ }
+ if (messageFunc == null)
+ {
+ return _logger(logLevel, null);
+ }
+
+ Func wrappedMessageFunc = () =>
+ {
+ try
+ {
+ return messageFunc();
+ }
+ catch (Exception ex)
+ {
+ Log(LogLevel.Error, () => FailedToGenerateLogMessage, ex);
+ }
+ return null;
+ };
+ return _logger(logLevel, wrappedMessageFunc, exception, formatParameters);
+ }
+ }
+#endif
+}
+
+#if LIBLOG_PROVIDERS_ONLY
+namespace GitTools.Logging.LibLog.LogProviders
+#else
+namespace GitTools.Logging.LogProviders
+#endif
+{
+ using System;
+ using System.Collections.Generic;
+ using System.Diagnostics.CodeAnalysis;
+#if !LIBLOG_PORTABLE
+ using System.Diagnostics;
+#endif
+ using System.Globalization;
+ using System.Linq;
+ using System.Linq.Expressions;
+ using System.Reflection;
+#if !LIBLOG_PORTABLE
+ using System.Text;
+#endif
+ using System.Text.RegularExpressions;
+
+#if !LIBLOG_PORTABLE
+ [ExcludeFromCodeCoverage]
+#endif
+ internal abstract class LogProviderBase : ILogProvider
+ {
+ protected delegate IDisposable OpenNdc(string message);
+ protected delegate IDisposable OpenMdc(string key, string value);
+
+ private readonly Lazy _lazyOpenNdcMethod;
+ private readonly Lazy _lazyOpenMdcMethod;
+ private static readonly IDisposable NoopDisposableInstance = new DisposableAction();
+
+ protected LogProviderBase()
+ {
+ _lazyOpenNdcMethod
+ = new Lazy(GetOpenNdcMethod);
+ _lazyOpenMdcMethod
+ = new Lazy(GetOpenMdcMethod);
+ }
+
+ public abstract Logger GetLogger(string name);
+
+ public IDisposable OpenNestedContext(string message)
+ {
+ return _lazyOpenNdcMethod.Value(message);
+ }
+
+ public IDisposable OpenMappedContext(string key, string value)
+ {
+ return _lazyOpenMdcMethod.Value(key, value);
+ }
+
+ protected virtual OpenNdc GetOpenNdcMethod()
+ {
+ return _ => NoopDisposableInstance;
+ }
+
+ protected virtual OpenMdc GetOpenMdcMethod()
+ {
+ return (_, __) => NoopDisposableInstance;
+ }
+ }
+
+#if !LIBLOG_PORTABLE
+ [ExcludeFromCodeCoverage]
+#endif
+ internal class NLogLogProvider : LogProviderBase
+ {
+ private readonly Func _getLoggerByNameDelegate;
+ private static bool s_providerIsAvailableOverride = true;
+
+ [SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "LogManager")]
+ [SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "NLog")]
+ public NLogLogProvider()
+ {
+ if (!IsLoggerAvailable())
+ {
+ throw new InvalidOperationException("NLog.LogManager not found");
+ }
+ _getLoggerByNameDelegate = GetGetLoggerMethodCall();
+ }
+
+ public static bool ProviderIsAvailableOverride
+ {
+ get { return s_providerIsAvailableOverride; }
+ set { s_providerIsAvailableOverride = value; }
+ }
+
+ public override Logger GetLogger(string name)
+ {
+ return new NLogLogger(_getLoggerByNameDelegate(name)).Log;
+ }
+
+ public static bool IsLoggerAvailable()
+ {
+ return ProviderIsAvailableOverride && GetLogManagerType() != null;
+ }
+
+ protected override OpenNdc GetOpenNdcMethod()
+ {
+ Type ndcContextType = Type.GetType("NLog.NestedDiagnosticsContext, NLog");
+ MethodInfo pushMethod = ndcContextType.GetMethodPortable("Push", typeof(string));
+ ParameterExpression messageParam = Expression.Parameter(typeof(string), "message");
+ MethodCallExpression pushMethodCall = Expression.Call(null, pushMethod, messageParam);
+ return Expression.Lambda(pushMethodCall, messageParam).Compile();
+ }
+
+ protected override OpenMdc GetOpenMdcMethod()
+ {
+ Type mdcContextType = Type.GetType("NLog.MappedDiagnosticsContext, NLog");
+
+ MethodInfo setMethod = mdcContextType.GetMethodPortable("Set", typeof(string), typeof(string));
+ MethodInfo removeMethod = mdcContextType.GetMethodPortable("Remove", typeof(string));
+ ParameterExpression keyParam = Expression.Parameter(typeof(string), "key");
+ ParameterExpression valueParam = Expression.Parameter(typeof(string), "value");
+
+ MethodCallExpression setMethodCall = Expression.Call(null, setMethod, keyParam, valueParam);
+ MethodCallExpression removeMethodCall = Expression.Call(null, removeMethod, keyParam);
+
+ Action set = Expression
+ .Lambda>(setMethodCall, keyParam, valueParam)
+ .Compile();
+ Action remove = Expression
+ .Lambda>(removeMethodCall, keyParam)
+ .Compile();
+
+ return (key, value) =>
+ {
+ set(key, value);
+ return new DisposableAction(() => remove(key));
+ };
+ }
+
+ private static Type GetLogManagerType()
+ {
+ return Type.GetType("NLog.LogManager, NLog");
+ }
+
+ private static Func GetGetLoggerMethodCall()
+ {
+ Type logManagerType = GetLogManagerType();
+ MethodInfo method = logManagerType.GetMethodPortable("GetLogger", typeof(string));
+ ParameterExpression nameParam = Expression.Parameter(typeof(string), "name");
+ MethodCallExpression methodCall = Expression.Call(null, method, nameParam);
+ return Expression.Lambda>(methodCall, nameParam).Compile();
+ }
+
+#if !LIBLOG_PORTABLE
+ [ExcludeFromCodeCoverage]
+#endif
+ internal class NLogLogger
+ {
+ private readonly dynamic _logger;
+
+ private static Func _logEventInfoFact;
+
+ private static readonly object _levelTrace;
+ private static readonly object _levelDebug;
+ private static readonly object _levelInfo;
+ private static readonly object _levelWarn;
+ private static readonly object _levelError;
+ private static readonly object _levelFatal;
+
+ static NLogLogger()
+ {
+ try
+ {
+ var logEventLevelType = Type.GetType("NLog.LogLevel, NLog");
+ if (logEventLevelType == null)
+ {
+ throw new InvalidOperationException("Type NLog.LogLevel was not found.");
+ }
+
+ var levelFields = logEventLevelType.GetFieldsPortable().ToList();
+ _levelTrace = levelFields.First(x => x.Name == "Trace").GetValue(null);
+ _levelDebug = levelFields.First(x => x.Name == "Debug").GetValue(null);
+ _levelInfo = levelFields.First(x => x.Name == "Info").GetValue(null);
+ _levelWarn = levelFields.First(x => x.Name == "Warn").GetValue(null);
+ _levelError = levelFields.First(x => x.Name == "Error").GetValue(null);
+ _levelFatal = levelFields.First(x => x.Name == "Fatal").GetValue(null);
+
+ var logEventInfoType = Type.GetType("NLog.LogEventInfo, NLog");
+ if (logEventInfoType == null)
+ {
+ throw new InvalidOperationException("Type NLog.LogEventInfo was not found.");
+ }
+ MethodInfo createLogEventInfoMethodInfo = logEventInfoType.GetMethodPortable("Create",
+ logEventLevelType, typeof(string), typeof(Exception), typeof(IFormatProvider), typeof(string), typeof(object[]));
+ ParameterExpression loggerNameParam = Expression.Parameter(typeof(string));
+ ParameterExpression levelParam = Expression.Parameter(typeof(object));
+ ParameterExpression messageParam = Expression.Parameter(typeof(string));
+ ParameterExpression exceptionParam = Expression.Parameter(typeof(Exception));
+ UnaryExpression levelCast = Expression.Convert(levelParam, logEventLevelType);
+ MethodCallExpression createLogEventInfoMethodCall = Expression.Call(null,
+ createLogEventInfoMethodInfo,
+ levelCast, loggerNameParam, exceptionParam,
+ Expression.Constant(null, typeof(IFormatProvider)), messageParam, Expression.Constant(null, typeof(object[])));
+ _logEventInfoFact = Expression.Lambda>(createLogEventInfoMethodCall,
+ loggerNameParam, levelParam, messageParam, exceptionParam).Compile();
+ }
+ catch { }
+ }
+
+ internal NLogLogger(dynamic logger)
+ {
+ _logger = logger;
+ }
+
+ [SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity")]
+ public bool Log(LogLevel logLevel, Func messageFunc, Exception exception, params object[] formatParameters)
+ {
+ if (messageFunc == null)
+ {
+ return IsLogLevelEnable(logLevel);
+ }
+ messageFunc = LogMessageFormatter.SimulateStructuredLogging(messageFunc, formatParameters);
+
+ if (_logEventInfoFact != null)
+ {
+ if (IsLogLevelEnable(logLevel))
+ {
+ var nlogLevel = this.TranslateLevel(logLevel);
+ Type s_callerStackBoundaryType;
+#if !LIBLOG_PORTABLE
+ StackTrace stack = new StackTrace();
+ Type thisType = GetType();
+ Type knownType0 = typeof(LoggerExecutionWrapper);
+ Type knownType1 = typeof(LogExtensions);
+ //Maybe inline, so we may can't found any LibLog classes in stack
+ s_callerStackBoundaryType = null;
+ for (var i = 0; i < stack.FrameCount; i++)
+ {
+ var declaringType = stack.GetFrame(i).GetMethod().DeclaringType;
+ if (!IsInTypeHierarchy(thisType, declaringType) &&
+ !IsInTypeHierarchy(knownType0, declaringType) &&
+ !IsInTypeHierarchy(knownType1, declaringType))
+ {
+ if (i > 1)
+ s_callerStackBoundaryType = stack.GetFrame(i - 1).GetMethod().DeclaringType;
+ break;
+ }
+ }
+#else
+ s_callerStackBoundaryType = null;
+#endif
+ if (s_callerStackBoundaryType != null)
+ _logger.Log(s_callerStackBoundaryType, _logEventInfoFact(_logger.Name, nlogLevel, messageFunc(), exception));
+ else
+ _logger.Log(_logEventInfoFact(_logger.Name, nlogLevel, messageFunc(), exception));
+ return true;
+ }
+ return false;
+ }
+
+ if (exception != null)
+ {
+ return LogException(logLevel, messageFunc, exception);
+ }
+ switch (logLevel)
+ {
+ case LogLevel.Debug:
+ if (_logger.IsDebugEnabled)
+ {
+ _logger.Debug(messageFunc());
+ return true;
+ }
+ break;
+ case LogLevel.Info:
+ if (_logger.IsInfoEnabled)
+ {
+ _logger.Info(messageFunc());
+ return true;
+ }
+ break;
+ case LogLevel.Warn:
+ if (_logger.IsWarnEnabled)
+ {
+ _logger.Warn(messageFunc());
+ return true;
+ }
+ break;
+ case LogLevel.Error:
+ if (_logger.IsErrorEnabled)
+ {
+ _logger.Error(messageFunc());
+ return true;
+ }
+ break;
+ case LogLevel.Fatal:
+ if (_logger.IsFatalEnabled)
+ {
+ _logger.Fatal(messageFunc());
+ return true;
+ }
+ break;
+ default:
+ if (_logger.IsTraceEnabled)
+ {
+ _logger.Trace(messageFunc());
+ return true;
+ }
+ break;
+ }
+ return false;
+ }
+
+ private static bool IsInTypeHierarchy(Type currentType, Type checkType)
+ {
+ while (currentType != null && currentType != typeof(object))
+ {
+ if (currentType == checkType)
+ {
+ return true;
+ }
+ currentType = currentType.GetBaseTypePortable();
+ }
+ return false;
+ }
+
+ [SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity")]
+ private bool LogException(LogLevel logLevel, Func messageFunc, Exception exception)
+ {
+ switch (logLevel)
+ {
+ case LogLevel.Debug:
+ if (_logger.IsDebugEnabled)
+ {
+ _logger.DebugException(messageFunc(), exception);
+ return true;
+ }
+ break;
+ case LogLevel.Info:
+ if (_logger.IsInfoEnabled)
+ {
+ _logger.InfoException(messageFunc(), exception);
+ return true;
+ }
+ break;
+ case LogLevel.Warn:
+ if (_logger.IsWarnEnabled)
+ {
+ _logger.WarnException(messageFunc(), exception);
+ return true;
+ }
+ break;
+ case LogLevel.Error:
+ if (_logger.IsErrorEnabled)
+ {
+ _logger.ErrorException(messageFunc(), exception);
+ return true;
+ }
+ break;
+ case LogLevel.Fatal:
+ if (_logger.IsFatalEnabled)
+ {
+ _logger.FatalException(messageFunc(), exception);
+ return true;
+ }
+ break;
+ default:
+ if (_logger.IsTraceEnabled)
+ {
+ _logger.TraceException(messageFunc(), exception);
+ return true;
+ }
+ break;
+ }
+ return false;
+ }
+
+ private bool IsLogLevelEnable(LogLevel logLevel)
+ {
+ switch (logLevel)
+ {
+ case LogLevel.Debug:
+ return _logger.IsDebugEnabled;
+ case LogLevel.Info:
+ return _logger.IsInfoEnabled;
+ case LogLevel.Warn:
+ return _logger.IsWarnEnabled;
+ case LogLevel.Error:
+ return _logger.IsErrorEnabled;
+ case LogLevel.Fatal:
+ return _logger.IsFatalEnabled;
+ default:
+ return _logger.IsTraceEnabled;
+ }
+ }
+
+ private object TranslateLevel(LogLevel logLevel)
+ {
+ switch (logLevel)
+ {
+ case LogLevel.Trace:
+ return _levelTrace;
+ case LogLevel.Debug:
+ return _levelDebug;
+ case LogLevel.Info:
+ return _levelInfo;
+ case LogLevel.Warn:
+ return _levelWarn;
+ case LogLevel.Error:
+ return _levelError;
+ case LogLevel.Fatal:
+ return _levelFatal;
+ default:
+ throw new ArgumentOutOfRangeException("logLevel", logLevel, null);
+ }
+ }
+ }
+ }
+
+#if !LIBLOG_PORTABLE
+ [ExcludeFromCodeCoverage]
+#endif
+ internal class Log4NetLogProvider : LogProviderBase
+ {
+ private readonly Func _getLoggerByNameDelegate;
+ private static bool s_providerIsAvailableOverride = true;
+
+ [SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "LogManager")]
+ public Log4NetLogProvider()
+ {
+ if (!IsLoggerAvailable())
+ {
+ throw new InvalidOperationException("log4net.LogManager not found");
+ }
+ _getLoggerByNameDelegate = GetGetLoggerMethodCall();
+ }
+
+ public static bool ProviderIsAvailableOverride
+ {
+ get { return s_providerIsAvailableOverride; }
+ set { s_providerIsAvailableOverride = value; }
+ }
+
+ public override Logger GetLogger(string name)
+ {
+ return new Log4NetLogger(_getLoggerByNameDelegate(name)).Log;
+ }
+
+ internal static bool IsLoggerAvailable()
+ {
+ return ProviderIsAvailableOverride && GetLogManagerType() != null;
+ }
+
+ protected override OpenNdc GetOpenNdcMethod()
+ {
+ Type logicalThreadContextType = Type.GetType("log4net.LogicalThreadContext, log4net");
+ PropertyInfo stacksProperty = logicalThreadContextType.GetPropertyPortable("Stacks");
+ Type logicalThreadContextStacksType = stacksProperty.PropertyType;
+ PropertyInfo stacksIndexerProperty = logicalThreadContextStacksType.GetPropertyPortable("Item");
+ Type stackType = stacksIndexerProperty.PropertyType;
+ MethodInfo pushMethod = stackType.GetMethodPortable("Push");
+
+ ParameterExpression messageParameter =
+ Expression.Parameter(typeof(string), "message");
+
+ // message => LogicalThreadContext.Stacks.Item["NDC"].Push(message);
+ MethodCallExpression callPushBody =
+ Expression.Call(
+ Expression.Property(Expression.Property(null, stacksProperty),
+ stacksIndexerProperty,
+ Expression.Constant("NDC")),
+ pushMethod,
+ messageParameter);
+
+ OpenNdc result =
+ Expression.Lambda(callPushBody, messageParameter)
+ .Compile();
+
+ return result;
+ }
+
+ protected override OpenMdc GetOpenMdcMethod()
+ {
+ Type logicalThreadContextType = Type.GetType("log4net.LogicalThreadContext, log4net");
+ PropertyInfo propertiesProperty = logicalThreadContextType.GetPropertyPortable("Properties");
+ Type logicalThreadContextPropertiesType = propertiesProperty.PropertyType;
+ PropertyInfo propertiesIndexerProperty = logicalThreadContextPropertiesType.GetPropertyPortable("Item");
+
+ MethodInfo removeMethod = logicalThreadContextPropertiesType.GetMethodPortable("Remove");
+
+ ParameterExpression keyParam = Expression.Parameter(typeof(string), "key");
+ ParameterExpression valueParam = Expression.Parameter(typeof(string), "value");
+
+ MemberExpression propertiesExpression = Expression.Property(null, propertiesProperty);
+
+ // (key, value) => LogicalThreadContext.Properties.Item[key] = value;
+ BinaryExpression setProperties = Expression.Assign(Expression.Property(propertiesExpression, propertiesIndexerProperty, keyParam), valueParam);
+
+ // key => LogicalThreadContext.Properties.Remove(key);
+ MethodCallExpression removeMethodCall = Expression.Call(propertiesExpression, removeMethod, keyParam);
+
+ Action set = Expression
+ .Lambda>(setProperties, keyParam, valueParam)
+ .Compile();
+
+ Action remove = Expression
+ .Lambda>(removeMethodCall, keyParam)
+ .Compile();
+
+ return (key, value) =>
+ {
+ set(key, value);
+ return new DisposableAction(() => remove(key));
+ };
+ }
+
+ private static Type GetLogManagerType()
+ {
+ return Type.GetType("log4net.LogManager, log4net");
+ }
+
+ private static Func GetGetLoggerMethodCall()
+ {
+ Type logManagerType = GetLogManagerType();
+ MethodInfo method = logManagerType.GetMethodPortable("GetLogger", typeof(string));
+ ParameterExpression nameParam = Expression.Parameter(typeof(string), "name");
+ MethodCallExpression methodCall = Expression.Call(null, method, nameParam);
+ return Expression.Lambda>(methodCall, nameParam).Compile();
+ }
+
+#if !LIBLOG_PORTABLE
+ [ExcludeFromCodeCoverage]
+#endif
+ internal class Log4NetLogger
+ {
+ private readonly dynamic _logger;
+ private static Type s_callerStackBoundaryType;
+ private static readonly object CallerStackBoundaryTypeSync = new object();
+
+ private readonly object _levelDebug;
+ private readonly object _levelInfo;
+ private readonly object _levelWarn;
+ private readonly object _levelError;
+ private readonly object _levelFatal;
+ private readonly Func