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 _isEnabledForDelegate; + private readonly Action _logDelegate; + private readonly Func _createLoggingEvent; + private Action _loggingEventPropertySetter; + + [SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "ILogger")] + internal Log4NetLogger(dynamic logger) + { + _logger = logger.Logger; + + var logEventLevelType = Type.GetType("log4net.Core.Level, log4net"); + if (logEventLevelType == null) + { + throw new InvalidOperationException("Type log4net.Core.Level was not found."); + } + + var levelFields = logEventLevelType.GetFieldsPortable().ToList(); + _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); + + // Func isEnabledFor = (logger, level) => { return ((log4net.Core.ILogger)logger).IsEnabled(level); } + var loggerType = Type.GetType("log4net.Core.ILogger, log4net"); + if (loggerType == null) + { + throw new InvalidOperationException("Type log4net.Core.ILogger, was not found."); + } + ParameterExpression instanceParam = Expression.Parameter(typeof(object)); + UnaryExpression instanceCast = Expression.Convert(instanceParam, loggerType); + ParameterExpression levelParam = Expression.Parameter(typeof(object)); + UnaryExpression levelCast = Expression.Convert(levelParam, logEventLevelType); + _isEnabledForDelegate = GetIsEnabledFor(loggerType, logEventLevelType, instanceCast, levelCast, instanceParam, levelParam); + + Type loggingEventType = Type.GetType("log4net.Core.LoggingEvent, log4net"); + + _createLoggingEvent = GetCreateLoggingEvent(instanceParam, instanceCast, levelParam, levelCast, loggingEventType); + + _logDelegate = GetLogDelegate(loggerType, loggingEventType, instanceCast, instanceParam); + + _loggingEventPropertySetter = GetLoggingEventPropertySetter(loggingEventType); + } + + private static Action GetLogDelegate(Type loggerType, Type loggingEventType, UnaryExpression instanceCast, + ParameterExpression instanceParam) + { + //Action Log = + //(logger, callerStackBoundaryDeclaringType, level, message, exception) => { ((ILogger)logger).Log(new LoggingEvent(callerStackBoundaryDeclaringType, logger.Repository, logger.Name, level, message, exception)); } + MethodInfo writeExceptionMethodInfo = loggerType.GetMethodPortable("Log", + loggingEventType); + + ParameterExpression loggingEventParameter = + Expression.Parameter(typeof(object), "loggingEvent"); + + UnaryExpression loggingEventCasted = + Expression.Convert(loggingEventParameter, loggingEventType); + + var writeMethodExp = Expression.Call( + instanceCast, + writeExceptionMethodInfo, + loggingEventCasted); + + var logDelegate = Expression.Lambda>( + writeMethodExp, + instanceParam, + loggingEventParameter).Compile(); + + return logDelegate; + } + + private static Func GetCreateLoggingEvent(ParameterExpression instanceParam, UnaryExpression instanceCast, ParameterExpression levelParam, UnaryExpression levelCast, Type loggingEventType) + { + ParameterExpression callerStackBoundaryDeclaringTypeParam = Expression.Parameter(typeof(Type)); + ParameterExpression messageParam = Expression.Parameter(typeof(string)); + ParameterExpression exceptionParam = Expression.Parameter(typeof(Exception)); + + PropertyInfo repositoryProperty = loggingEventType.GetPropertyPortable("Repository"); + PropertyInfo levelProperty = loggingEventType.GetPropertyPortable("Level"); + + ConstructorInfo loggingEventConstructor = + loggingEventType.GetConstructorPortable(typeof(Type), repositoryProperty.PropertyType, typeof(string), levelProperty.PropertyType, typeof(object), typeof(Exception)); + + //Func Log = + //(logger, callerStackBoundaryDeclaringType, level, message, exception) => new LoggingEvent(callerStackBoundaryDeclaringType, ((ILogger)logger).Repository, ((ILogger)logger).Name, (Level)level, message, exception); } + NewExpression newLoggingEventExpression = + Expression.New(loggingEventConstructor, + callerStackBoundaryDeclaringTypeParam, + Expression.Property(instanceCast, "Repository"), + Expression.Property(instanceCast, "Name"), + levelCast, + messageParam, + exceptionParam); + + var createLoggingEvent = + Expression.Lambda>( + newLoggingEventExpression, + instanceParam, + callerStackBoundaryDeclaringTypeParam, + levelParam, + messageParam, + exceptionParam) + .Compile(); + + return createLoggingEvent; + } + + private static Func GetIsEnabledFor(Type loggerType, Type logEventLevelType, + UnaryExpression instanceCast, + UnaryExpression levelCast, + ParameterExpression instanceParam, + ParameterExpression levelParam) + { + MethodInfo isEnabledMethodInfo = loggerType.GetMethodPortable("IsEnabledFor", logEventLevelType); + MethodCallExpression isEnabledMethodCall = Expression.Call(instanceCast, isEnabledMethodInfo, levelCast); + + Func result = + Expression.Lambda>(isEnabledMethodCall, instanceParam, levelParam) + .Compile(); + + return result; + } + + private static Action GetLoggingEventPropertySetter(Type loggingEventType) + { + ParameterExpression loggingEventParameter = Expression.Parameter(typeof(object), "loggingEvent"); + ParameterExpression keyParameter = Expression.Parameter(typeof(string), "key"); + ParameterExpression valueParameter = Expression.Parameter(typeof(object), "value"); + + PropertyInfo propertiesProperty = loggingEventType.GetPropertyPortable("Properties"); + PropertyInfo item = propertiesProperty.PropertyType.GetPropertyPortable("Item"); + + // ((LoggingEvent)loggingEvent).Properties[key] = value; + var body = + Expression.Assign( + Expression.Property( + Expression.Property(Expression.Convert(loggingEventParameter, loggingEventType), + propertiesProperty), item, keyParameter), valueParameter); + + Action result = + Expression.Lambda> + (body, loggingEventParameter, keyParameter, + valueParameter) + .Compile(); + + return result; + } + + public bool Log(LogLevel logLevel, Func messageFunc, Exception exception, params object[] formatParameters) + { + if (messageFunc == null) + { + return IsLogLevelEnable(logLevel); + } + + if (!IsLogLevelEnable(logLevel)) + { + return false; + } + + string message = messageFunc(); + + IEnumerable patternMatches; + + string formattedMessage = + LogMessageFormatter.FormatStructuredMessage(message, + formatParameters, + out patternMatches); + + // determine correct caller - this might change due to jit optimizations with method inlining + if (s_callerStackBoundaryType == null) + { + lock (CallerStackBoundaryTypeSync) + { +#if !LIBLOG_PORTABLE + StackTrace stack = new StackTrace(); + Type thisType = GetType(); + s_callerStackBoundaryType = Type.GetType("LoggerExecutionWrapper"); + for (var i = 1; i < stack.FrameCount; i++) + { + if (!IsInTypeHierarchy(thisType, stack.GetFrame(i).GetMethod().DeclaringType)) + { + s_callerStackBoundaryType = stack.GetFrame(i - 1).GetMethod().DeclaringType; + break; + } + } +#else + s_callerStackBoundaryType = typeof(LoggerExecutionWrapper); +#endif + } + } + + var translatedLevel = TranslateLevel(logLevel); + + object loggingEvent = _createLoggingEvent(_logger, s_callerStackBoundaryType, translatedLevel, formattedMessage, exception); + + PopulateProperties(loggingEvent, patternMatches, formatParameters); + + _logDelegate(_logger, loggingEvent); + + return true; + } + + private void PopulateProperties(object loggingEvent, IEnumerable patternMatches, object[] formatParameters) + { + IEnumerable> keyToValue = + patternMatches.Zip(formatParameters, + (key, value) => new KeyValuePair(key, value)); + + foreach (KeyValuePair keyValuePair in keyToValue) + { + _loggingEventPropertySetter(loggingEvent, keyValuePair.Key, keyValuePair.Value); + } + } + + private static bool IsInTypeHierarchy(Type currentType, Type checkType) + { + while (currentType != null && currentType != typeof(object)) + { + if (currentType == checkType) + { + return true; + } + currentType = currentType.GetBaseTypePortable(); + } + return false; + } + + private bool IsLogLevelEnable(LogLevel logLevel) + { + var level = TranslateLevel(logLevel); + return _isEnabledForDelegate(_logger, level); + } + + private object TranslateLevel(LogLevel logLevel) + { + switch (logLevel) + { + case LogLevel.Trace: + 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 EntLibLogProvider : LogProviderBase + { + private const string TypeTemplate = "Microsoft.Practices.EnterpriseLibrary.Logging.{0}, Microsoft.Practices.EnterpriseLibrary.Logging"; + private static bool s_providerIsAvailableOverride = true; + private static readonly Type LogEntryType; + private static readonly Type LoggerType; + private static readonly Type TraceEventTypeType; + private static readonly Action WriteLogEntry; + private static readonly Func ShouldLogEntry; + + [SuppressMessage("Microsoft.Performance", "CA1810:InitializeReferenceTypeStaticFieldsInline")] + static EntLibLogProvider() + { + LogEntryType = Type.GetType(string.Format(CultureInfo.InvariantCulture, TypeTemplate, "LogEntry")); + LoggerType = Type.GetType(string.Format(CultureInfo.InvariantCulture, TypeTemplate, "Logger")); + TraceEventTypeType = TraceEventTypeValues.Type; + if (LogEntryType == null + || TraceEventTypeType == null + || LoggerType == null) + { + return; + } + WriteLogEntry = GetWriteLogEntry(); + ShouldLogEntry = GetShouldLogEntry(); + } + + [SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "EnterpriseLibrary")] + public EntLibLogProvider() + { + if (!IsLoggerAvailable()) + { + throw new InvalidOperationException("Microsoft.Practices.EnterpriseLibrary.Logging.Logger not found"); + } + } + + public static bool ProviderIsAvailableOverride + { + get { return s_providerIsAvailableOverride; } + set { s_providerIsAvailableOverride = value; } + } + + public override Logger GetLogger(string name) + { + return new EntLibLogger(name, WriteLogEntry, ShouldLogEntry).Log; + } + + internal static bool IsLoggerAvailable() + { + return ProviderIsAvailableOverride + && TraceEventTypeType != null + && LogEntryType != null; + } + + private static Action GetWriteLogEntry() + { + // new LogEntry(...) + var logNameParameter = Expression.Parameter(typeof(string), "logName"); + var messageParameter = Expression.Parameter(typeof(string), "message"); + var severityParameter = Expression.Parameter(typeof(int), "severity"); + + MemberInitExpression memberInit = GetWriteLogExpression( + messageParameter, + Expression.Convert(severityParameter, TraceEventTypeType), + logNameParameter); + + //Logger.Write(new LogEntry(....)); + MethodInfo writeLogEntryMethod = LoggerType.GetMethodPortable("Write", LogEntryType); + var writeLogEntryExpression = Expression.Call(writeLogEntryMethod, memberInit); + + return Expression.Lambda>( + writeLogEntryExpression, + logNameParameter, + messageParameter, + severityParameter).Compile(); + } + + private static Func GetShouldLogEntry() + { + // new LogEntry(...) + var logNameParameter = Expression.Parameter(typeof(string), "logName"); + var severityParameter = Expression.Parameter(typeof(int), "severity"); + + MemberInitExpression memberInit = GetWriteLogExpression( + Expression.Constant("***dummy***"), + Expression.Convert(severityParameter, TraceEventTypeType), + logNameParameter); + + //Logger.Write(new LogEntry(....)); + MethodInfo writeLogEntryMethod = LoggerType.GetMethodPortable("ShouldLog", LogEntryType); + var writeLogEntryExpression = Expression.Call(writeLogEntryMethod, memberInit); + + return Expression.Lambda>( + writeLogEntryExpression, + logNameParameter, + severityParameter).Compile(); + } + + private static MemberInitExpression GetWriteLogExpression(Expression message, + Expression severityParameter, ParameterExpression logNameParameter) + { + var entryType = LogEntryType; + MemberInitExpression memberInit = Expression.MemberInit(Expression.New(entryType), + Expression.Bind(entryType.GetPropertyPortable("Message"), message), + Expression.Bind(entryType.GetPropertyPortable("Severity"), severityParameter), + Expression.Bind( + entryType.GetPropertyPortable("TimeStamp"), + Expression.Property(null, typeof(DateTime).GetPropertyPortable("UtcNow"))), + Expression.Bind( + entryType.GetPropertyPortable("Categories"), + Expression.ListInit( + Expression.New(typeof(List)), + typeof(List).GetMethodPortable("Add", typeof(string)), + logNameParameter))); + return memberInit; + } + +#if !LIBLOG_PORTABLE + [ExcludeFromCodeCoverage] +#endif + internal class EntLibLogger + { + private readonly string _loggerName; + private readonly Action _writeLog; + private readonly Func _shouldLog; + + internal EntLibLogger(string loggerName, Action writeLog, Func shouldLog) + { + _loggerName = loggerName; + _writeLog = writeLog; + _shouldLog = shouldLog; + } + + public bool Log(LogLevel logLevel, Func messageFunc, Exception exception, params object[] formatParameters) + { + var severity = MapSeverity(logLevel); + if (messageFunc == null) + { + return _shouldLog(_loggerName, severity); + } + + + messageFunc = LogMessageFormatter.SimulateStructuredLogging(messageFunc, formatParameters); + if (exception != null) + { + return LogException(logLevel, messageFunc, exception); + } + _writeLog(_loggerName, messageFunc(), severity); + return true; + } + + public bool LogException(LogLevel logLevel, Func messageFunc, Exception exception) + { + var severity = MapSeverity(logLevel); + var message = messageFunc() + Environment.NewLine + exception; + _writeLog(_loggerName, message, severity); + return true; + } + + private static int MapSeverity(LogLevel logLevel) + { + switch (logLevel) + { + case LogLevel.Fatal: + return TraceEventTypeValues.Critical; + case LogLevel.Error: + return TraceEventTypeValues.Error; + case LogLevel.Warn: + return TraceEventTypeValues.Warning; + case LogLevel.Info: + return TraceEventTypeValues.Information; + default: + return TraceEventTypeValues.Verbose; + } + } + } + } + +#if !LIBLOG_PORTABLE + [ExcludeFromCodeCoverage] +#endif + internal class SerilogLogProvider : LogProviderBase + { + private readonly Func _getLoggerByNameDelegate; + private static bool s_providerIsAvailableOverride = true; + + [SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "Serilog")] + public SerilogLogProvider() + { + if (!IsLoggerAvailable()) + { + throw new InvalidOperationException("Serilog.Log not found"); + } + _getLoggerByNameDelegate = GetForContextMethodCall(); + } + + public static bool ProviderIsAvailableOverride + { + get { return s_providerIsAvailableOverride; } + set { s_providerIsAvailableOverride = value; } + } + + public override Logger GetLogger(string name) + { + return new SerilogLogger(_getLoggerByNameDelegate(name)).Log; + } + + internal static bool IsLoggerAvailable() + { + return ProviderIsAvailableOverride && GetLogManagerType() != null; + } + + protected override OpenNdc GetOpenNdcMethod() + { + return message => GetPushProperty()("NDC", message); + } + + protected override OpenMdc GetOpenMdcMethod() + { + return (key, value) => GetPushProperty()(key, value); + } + + private static Func GetPushProperty() + { + Type ndcContextType = Type.GetType("Serilog.Context.LogContext, Serilog") ?? + Type.GetType("Serilog.Context.LogContext, Serilog.FullNetFx"); + + MethodInfo pushPropertyMethod = ndcContextType.GetMethodPortable( + "PushProperty", + typeof(string), + typeof(object), + typeof(bool)); + + ParameterExpression nameParam = Expression.Parameter(typeof(string), "name"); + ParameterExpression valueParam = Expression.Parameter(typeof(object), "value"); + ParameterExpression destructureObjectParam = Expression.Parameter(typeof(bool), "destructureObjects"); + MethodCallExpression pushPropertyMethodCall = Expression + .Call(null, pushPropertyMethod, nameParam, valueParam, destructureObjectParam); + var pushProperty = Expression + .Lambda>( + pushPropertyMethodCall, + nameParam, + valueParam, + destructureObjectParam) + .Compile(); + + return (key, value) => pushProperty(key, value, false); + } + + private static Type GetLogManagerType() + { + return Type.GetType("Serilog.Log, Serilog"); + } + + private static Func GetForContextMethodCall() + { + Type logManagerType = GetLogManagerType(); + MethodInfo method = logManagerType.GetMethodPortable("ForContext", typeof(string), typeof(object), typeof(bool)); + ParameterExpression propertyNameParam = Expression.Parameter(typeof(string), "propertyName"); + ParameterExpression valueParam = Expression.Parameter(typeof(object), "value"); + ParameterExpression destructureObjectsParam = Expression.Parameter(typeof(bool), "destructureObjects"); + MethodCallExpression methodCall = Expression.Call(null, method, new Expression[] + { + propertyNameParam, + valueParam, + destructureObjectsParam + }); + var func = Expression.Lambda>( + methodCall, + propertyNameParam, + valueParam, + destructureObjectsParam) + .Compile(); + return name => func("SourceContext", name, false); + } + +#if !LIBLOG_PORTABLE + [ExcludeFromCodeCoverage] +#endif + internal class SerilogLogger + { + private readonly object _logger; + private static readonly object DebugLevel; + private static readonly object ErrorLevel; + private static readonly object FatalLevel; + private static readonly object InformationLevel; + private static readonly object VerboseLevel; + private static readonly object WarningLevel; + private static readonly Func IsEnabled; + private static readonly Action Write; + private static readonly Action WriteException; + + [SuppressMessage("Microsoft.Design", "CA1065:DoNotRaiseExceptionsInUnexpectedLocations")] + [SuppressMessage("Microsoft.Performance", "CA1810:InitializeReferenceTypeStaticFieldsInline")] + [SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "ILogger")] + [SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "LogEventLevel")] + [SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "Serilog")] + static SerilogLogger() + { + var logEventLevelType = Type.GetType("Serilog.Events.LogEventLevel, Serilog"); + if (logEventLevelType == null) + { + throw new InvalidOperationException("Type Serilog.Events.LogEventLevel was not found."); + } + DebugLevel = Enum.Parse(logEventLevelType, "Debug", false); + ErrorLevel = Enum.Parse(logEventLevelType, "Error", false); + FatalLevel = Enum.Parse(logEventLevelType, "Fatal", false); + InformationLevel = Enum.Parse(logEventLevelType, "Information", false); + VerboseLevel = Enum.Parse(logEventLevelType, "Verbose", false); + WarningLevel = Enum.Parse(logEventLevelType, "Warning", false); + + // Func isEnabled = (logger, level) => { return ((SeriLog.ILogger)logger).IsEnabled(level); } + var loggerType = Type.GetType("Serilog.ILogger, Serilog"); + if (loggerType == null) + { + throw new InvalidOperationException("Type Serilog.ILogger was not found."); + } + MethodInfo isEnabledMethodInfo = loggerType.GetMethodPortable("IsEnabled", logEventLevelType); + ParameterExpression instanceParam = Expression.Parameter(typeof(object)); + UnaryExpression instanceCast = Expression.Convert(instanceParam, loggerType); + ParameterExpression levelParam = Expression.Parameter(typeof(object)); + UnaryExpression levelCast = Expression.Convert(levelParam, logEventLevelType); + MethodCallExpression isEnabledMethodCall = Expression.Call(instanceCast, isEnabledMethodInfo, levelCast); + IsEnabled = Expression.Lambda>(isEnabledMethodCall, instanceParam, levelParam).Compile(); + + // Action Write = + // (logger, level, message, params) => { ((SeriLog.ILoggerILogger)logger).Write(level, message, params); } + MethodInfo writeMethodInfo = loggerType.GetMethodPortable("Write", logEventLevelType, typeof(string), typeof(object[])); + ParameterExpression messageParam = Expression.Parameter(typeof(string)); + ParameterExpression propertyValuesParam = Expression.Parameter(typeof(object[])); + MethodCallExpression writeMethodExp = Expression.Call( + instanceCast, + writeMethodInfo, + levelCast, + messageParam, + propertyValuesParam); + var expression = Expression.Lambda>( + writeMethodExp, + instanceParam, + levelParam, + messageParam, + propertyValuesParam); + Write = expression.Compile(); + + // Action WriteException = + // (logger, level, exception, message) => { ((ILogger)logger).Write(level, exception, message, new object[]); } + MethodInfo writeExceptionMethodInfo = loggerType.GetMethodPortable("Write", + logEventLevelType, + typeof(Exception), + typeof(string), + typeof(object[])); + ParameterExpression exceptionParam = Expression.Parameter(typeof(Exception)); + writeMethodExp = Expression.Call( + instanceCast, + writeExceptionMethodInfo, + levelCast, + exceptionParam, + messageParam, + propertyValuesParam); + WriteException = Expression.Lambda>( + writeMethodExp, + instanceParam, + levelParam, + exceptionParam, + messageParam, + propertyValuesParam).Compile(); + } + + internal SerilogLogger(object logger) + { + _logger = logger; + } + + public bool Log(LogLevel logLevel, Func messageFunc, Exception exception, params object[] formatParameters) + { + var translatedLevel = TranslateLevel(logLevel); + if (messageFunc == null) + { + return IsEnabled(_logger, translatedLevel); + } + + if (!IsEnabled(_logger, translatedLevel)) + { + return false; + } + + if (exception != null) + { + LogException(translatedLevel, messageFunc, exception, formatParameters); + } + else + { + LogMessage(translatedLevel, messageFunc, formatParameters); + } + + return true; + } + + private void LogMessage(object translatedLevel, Func messageFunc, object[] formatParameters) + { + Write(_logger, translatedLevel, messageFunc(), formatParameters); + } + + private void LogException(object logLevel, Func messageFunc, Exception exception, object[] formatParams) + { + WriteException(_logger, logLevel, exception, messageFunc(), formatParams); + } + + private static object TranslateLevel(LogLevel logLevel) + { + switch (logLevel) + { + case LogLevel.Fatal: + return FatalLevel; + case LogLevel.Error: + return ErrorLevel; + case LogLevel.Warn: + return WarningLevel; + case LogLevel.Info: + return InformationLevel; + case LogLevel.Trace: + return VerboseLevel; + default: + return DebugLevel; + } + } + } + } + +#if !LIBLOG_PORTABLE + [ExcludeFromCodeCoverage] +#endif + internal class LoupeLogProvider : LogProviderBase + { + /// + /// The form of the Loupe Log.Write method we're using + /// + internal delegate void WriteDelegate( + int severity, + string logSystem, + int skipFrames, + Exception exception, + bool attributeToException, + int writeMode, + string detailsXml, + string category, + string caption, + string description, + params object[] args + ); + + private static bool s_providerIsAvailableOverride = true; + private readonly WriteDelegate _logWriteDelegate; + + public LoupeLogProvider() + { + if (!IsLoggerAvailable()) + { + throw new InvalidOperationException("Gibraltar.Agent.Log (Loupe) not found"); + } + + _logWriteDelegate = GetLogWriteDelegate(); + } + + /// + /// Gets or sets a value indicating whether [provider is available override]. Used in tests. + /// + /// + /// true if [provider is available override]; otherwise, false. + /// + public static bool ProviderIsAvailableOverride + { + get { return s_providerIsAvailableOverride; } + set { s_providerIsAvailableOverride = value; } + } + + public override Logger GetLogger(string name) + { + return new LoupeLogger(name, _logWriteDelegate).Log; + } + + public static bool IsLoggerAvailable() + { + return ProviderIsAvailableOverride && GetLogManagerType() != null; + } + + private static Type GetLogManagerType() + { + return Type.GetType("Gibraltar.Agent.Log, Gibraltar.Agent"); + } + + private static WriteDelegate GetLogWriteDelegate() + { + Type logManagerType = GetLogManagerType(); + Type logMessageSeverityType = Type.GetType("Gibraltar.Agent.LogMessageSeverity, Gibraltar.Agent"); + Type logWriteModeType = Type.GetType("Gibraltar.Agent.LogWriteMode, Gibraltar.Agent"); + + MethodInfo method = logManagerType.GetMethodPortable( + "Write", + logMessageSeverityType, typeof(string), typeof(int), typeof(Exception), typeof(bool), + logWriteModeType, typeof(string), typeof(string), typeof(string), typeof(string), typeof(object[])); + + var callDelegate = (WriteDelegate)method.CreateDelegate(typeof(WriteDelegate)); + return callDelegate; + } + +#if !LIBLOG_PORTABLE + [ExcludeFromCodeCoverage] +#endif + internal class LoupeLogger + { + private const string LogSystem = "LibLog"; + + private readonly string _category; + private readonly WriteDelegate _logWriteDelegate; + private readonly int _skipLevel; + + internal LoupeLogger(string category, WriteDelegate logWriteDelegate) + { + _category = category; + _logWriteDelegate = logWriteDelegate; +#if DEBUG + _skipLevel = 2; +#else + _skipLevel = 1; +#endif + } + + public bool Log(LogLevel logLevel, Func messageFunc, Exception exception, params object[] formatParameters) + { + if (messageFunc == null) + { + //nothing to log.. + return true; + } + + messageFunc = LogMessageFormatter.SimulateStructuredLogging(messageFunc, formatParameters); + + _logWriteDelegate(ToLogMessageSeverity(logLevel), LogSystem, _skipLevel, exception, true, 0, null, + _category, null, messageFunc.Invoke()); + + return true; + } + + private static int ToLogMessageSeverity(LogLevel logLevel) + { + switch (logLevel) + { + case LogLevel.Trace: + return TraceEventTypeValues.Verbose; + case LogLevel.Debug: + return TraceEventTypeValues.Verbose; + case LogLevel.Info: + return TraceEventTypeValues.Information; + case LogLevel.Warn: + return TraceEventTypeValues.Warning; + case LogLevel.Error: + return TraceEventTypeValues.Error; + case LogLevel.Fatal: + return TraceEventTypeValues.Critical; + default: + throw new ArgumentOutOfRangeException("logLevel"); + } + } + } + } + +#if !LIBLOG_PORTABLE + [ExcludeFromCodeCoverage] +#endif + internal static class TraceEventTypeValues + { + internal static readonly Type Type; + internal static readonly int Verbose; + internal static readonly int Information; + internal static readonly int Warning; + internal static readonly int Error; + internal static readonly int Critical; + + [SuppressMessage("Microsoft.Performance", "CA1810:InitializeReferenceTypeStaticFieldsInline")] + static TraceEventTypeValues() + { + var assembly = typeof(Uri).GetAssemblyPortable(); // This is to get to the System.dll assembly in a PCL compatible way. + if (assembly == null) + { + return; + } + Type = assembly.GetType("System.Diagnostics.TraceEventType"); + if (Type == null) return; + Verbose = (int)Enum.Parse(Type, "Verbose", false); + Information = (int)Enum.Parse(Type, "Information", false); + Warning = (int)Enum.Parse(Type, "Warning", false); + Error = (int)Enum.Parse(Type, "Error", false); + Critical = (int)Enum.Parse(Type, "Critical", false); + } + } + +#if !LIBLOG_PORTABLE + [ExcludeFromCodeCoverage] +#endif + internal static class LogMessageFormatter + { + //private static readonly Regex Pattern = new Regex(@"\{@?\w{1,}\}"); +#if LIBLOG_PORTABLE + private static readonly Regex Pattern = new Regex(@"(?[^\d{][^ }]*)}"); +#else + private static readonly Regex Pattern = new Regex(@"(?[^ :{}]+)(?:[^}]+)?}", RegexOptions.Compiled); +#endif + + /// + /// Some logging frameworks support structured logging, such as serilog. This will allow you to add names to structured data in a format string: + /// For example: Log("Log message to {user}", user). This only works with serilog, but as the user of LibLog, you don't know if serilog is actually + /// used. So, this class simulates that. it will replace any text in {curly braces} with an index number. + /// + /// "Log {message} to {user}" would turn into => "Log {0} to {1}". Then the format parameters are handled using regular .net string.Format. + /// + /// The message builder. + /// The format parameters. + /// + public static Func SimulateStructuredLogging(Func messageBuilder, object[] formatParameters) + { + if (formatParameters == null || formatParameters.Length == 0) + { + return messageBuilder; + } + + return () => + { + string targetMessage = messageBuilder(); + IEnumerable patternMatches; + return FormatStructuredMessage(targetMessage, formatParameters, out patternMatches); + }; + } + + private static string ReplaceFirst(string text, string search, string replace) + { + int pos = text.IndexOf(search, StringComparison.Ordinal); + if (pos < 0) + { + return text; + } + return text.Substring(0, pos) + replace + text.Substring(pos + search.Length); + } + + public static string FormatStructuredMessage(string targetMessage, object[] formatParameters, out IEnumerable patternMatches) + { + if (formatParameters.Length == 0) + { + patternMatches = Enumerable.Empty(); + return targetMessage; + } + + List processedArguments = new List(); + patternMatches = processedArguments; + + foreach (Match match in Pattern.Matches(targetMessage)) + { + var arg = match.Groups["arg"].Value; + + int notUsed; + if (!int.TryParse(arg, out notUsed)) + { + int argumentIndex = processedArguments.IndexOf(arg); + if (argumentIndex == -1) + { + argumentIndex = processedArguments.Count; + processedArguments.Add(arg); + } + + targetMessage = ReplaceFirst(targetMessage, match.Value, + "{" + argumentIndex + match.Groups["format"].Value + "}"); + } + } + try + { + return string.Format(CultureInfo.InvariantCulture, targetMessage, formatParameters); + } + catch (FormatException ex) + { + throw new FormatException("The input string '" + targetMessage + "' could not be formatted using string.Format", ex); + } + } + } + +#if !LIBLOG_PORTABLE + [ExcludeFromCodeCoverage] +#endif + internal static class TypeExtensions + { + internal static ConstructorInfo GetConstructorPortable(this Type type, params Type[] types) + { +#if LIBLOG_PORTABLE + return type.GetTypeInfo().DeclaredConstructors.FirstOrDefault + (constructor => + constructor.GetParameters() + .Select(parameter => parameter.ParameterType) + .SequenceEqual(types)); +#else + return type.GetConstructor(types); +#endif + } + + internal static MethodInfo GetMethodPortable(this Type type, string name) + { +#if LIBLOG_PORTABLE + return type.GetRuntimeMethods().SingleOrDefault(m => m.Name == name); +#else + return type.GetMethod(name); +#endif + } + + internal static MethodInfo GetMethodPortable(this Type type, string name, params Type[] types) + { +#if LIBLOG_PORTABLE + return type.GetRuntimeMethod(name, types); +#else + return type.GetMethod(name, types); +#endif + } + + internal static PropertyInfo GetPropertyPortable(this Type type, string name) + { +#if LIBLOG_PORTABLE + return type.GetRuntimeProperty(name); +#else + return type.GetProperty(name); +#endif + } + + internal static IEnumerable GetFieldsPortable(this Type type) + { +#if LIBLOG_PORTABLE + return type.GetRuntimeFields(); +#else + return type.GetFields(); +#endif + } + + internal static Type GetBaseTypePortable(this Type type) + { +#if LIBLOG_PORTABLE + return type.GetTypeInfo().BaseType; +#else + return type.BaseType; +#endif + } + +#if LIBLOG_PORTABLE + internal static MethodInfo GetGetMethod(this PropertyInfo propertyInfo) + { + return propertyInfo.GetMethod; + } + + internal static MethodInfo GetSetMethod(this PropertyInfo propertyInfo) + { + return propertyInfo.SetMethod; + } +#endif + +#if !LIBLOG_PORTABLE + internal static object CreateDelegate(this MethodInfo methodInfo, Type delegateType) + { + return Delegate.CreateDelegate(delegateType, methodInfo); + } +#endif + + internal static Assembly GetAssemblyPortable(this Type type) + { +#if LIBLOG_PORTABLE + return type.GetTypeInfo().Assembly; +#else + return type.Assembly; +#endif + } + } + +#if !LIBLOG_PORTABLE + [ExcludeFromCodeCoverage] +#endif + internal class DisposableAction : IDisposable + { + private readonly Action _onDispose; + + public DisposableAction(Action onDispose = null) + { + _onDispose = onDispose; + } + + public void Dispose() + { + if (_onDispose != null) + { + _onDispose(); + } + } + } +} diff --git a/src/GitTools.Core/Logging/Extensions/LogExtensions.cs b/src/GitTools.Core/Logging/Extensions/LogExtensions.cs new file mode 100644 index 0000000000..03665e1dea --- /dev/null +++ b/src/GitTools.Core/Logging/Extensions/LogExtensions.cs @@ -0,0 +1,67 @@ +namespace GitTools +{ + using System; + using System.Runtime.InteropServices; + using Logging; + + internal static class LogExtensions + { + /// + /// Writes the specified message as error message and then throws the specified exception. + /// + /// The specified exception must have a constructor that accepts a single string as message. + /// + /// The type of the exception. + /// The log. + /// The message format. + /// The args. + /// + /// + /// This example logs an error and immediately throws the exception: + /// ("This action is not supported"); + /// ]]> + /// + /// + /// The is null. + /// The does not have a constructor accepting a string. + public static TException ErrorAndCreateException(this ILog log, string messageFormat, params object[] args) + where TException : Exception + { + var message = messageFormat ?? string.Empty; + if (args != null && args.Length > 0) + { + message = string.Format(message, args); + } + + if (log != null) + { + log.Error(message); + } + + Exception exception; + + try + { + exception = (Exception)Activator.CreateInstance(typeof(TException), message); + } +#if !NETFX_CORE && !PCL + catch (MissingMethodException) +#else + catch (Exception) +#endif + { + var error = string.Format("Exception type '{0}' does not have a constructor accepting a string", typeof(TException).Name); + + if (log != null) + { + log.Error(error); + } + + throw new NotSupportedException(error); + } + + return (TException)exception; + } + } +} \ No newline at end of file diff --git a/src/GitTools.Testing.Tests/Example.cs b/src/GitTools.Testing.Tests/Example.cs new file mode 100644 index 0000000000..b6ec58aea2 --- /dev/null +++ b/src/GitTools.Testing.Tests/Example.cs @@ -0,0 +1,22 @@ +using Xunit; + +namespace GitTools.Testing.Tests +{ + public class Example + { + [Fact] + public void TheReadmeSample() + { + using (var fixture = new EmptyRepositoryFixture()) + { + fixture.MakeACommit(); + fixture.MakeACommit(); + fixture.MakeATaggedCommit("1.0.0"); + fixture.BranchTo("develop"); + fixture.MakeACommit(); + fixture.Checkout("master"); + fixture.MergeNoFF("develop"); + } + } + } +} \ No newline at end of file diff --git a/src/GitTools.Testing.Tests/GitTools.Testing.Tests.csproj b/src/GitTools.Testing.Tests/GitTools.Testing.Tests.csproj new file mode 100644 index 0000000000..9460afe250 --- /dev/null +++ b/src/GitTools.Testing.Tests/GitTools.Testing.Tests.csproj @@ -0,0 +1,49 @@ + + + + Debug + Any CPU + Library + netcoreapp2.0 + bin\$(Configuration)\ + true + false + + + + + GitTools.Testing + GitTools Contributors + Makes it easy to automate git for testing libraries which interact with git + git tools testing + Copyright GitTools 2015 + https://github.com/GitTools/GitTools.Core + https://github.com/GitTools/GitTools.Core/blob/master/LICENSE + https://raw.github.com/GitTools/GitTools.Core/master/GitTools_logo.png + + + true + full + TRACE;DEBUG + + + pdbonly + true + TRACE + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/GitTools.Testing/App_Packages/LibLog.4.2/LibLog.cs b/src/GitTools.Testing/App_Packages/LibLog.4.2/LibLog.cs new file mode 100644 index 0000000000..8d9ea81839 --- /dev/null +++ b/src/GitTools.Testing/App_Packages/LibLog.4.2/LibLog.cs @@ -0,0 +1,1999 @@ +//=============================================================================== +// 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.Testing.Logging")] +[assembly: SuppressMessage("Microsoft.Design", "CA1026:DefaultParametersShouldNotBeUsed", Scope = "member", Target = "GitTools.Testing.Logging.Logger.#Invoke(GitTools.Testing.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.Testing.LibLog +#else +namespace GitTools.Testing.Logging +#endif +{ + using System.Collections.Generic; + using System.Diagnostics.CodeAnalysis; +#if LIBLOG_PROVIDERS_ONLY + using GitTools.Testing.LibLog.LogProviders; +#else + using GitTools.Testing.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_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 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 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 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 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 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 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_PROVIDERS_ONLY + internal +#else + public +#endif + static class LogProvider + { +#if !LIBLOG_PROVIDERS_ONLY + /// + /// The disable logging environment variable. If the environment variable is set to 'true', then logging + /// will be disabled. + /// + public const string DisableLoggingEnvironmentVariable = "GitTools.Testing_LIBLOG_DISABLE"; + 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. + /// An instance of +#if LIBLOG_PUBLIC + public +#else + internal +#endif + static ILog GetLogger(Type type) + { + return GetLogger(type.FullName); + } + + /// + /// 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 + 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 + 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 !LIBLOG_PORTABLE + var envVar = Environment.GetEnvironmentVariable(LogProvider.DisableLoggingEnvironmentVariable); + + if (envVar != null && envVar.Equals("true", StringComparison.OrdinalIgnoreCase)) + { + return false; + } +#endif + + 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.Testing.LibLog.LogProviders +#else +namespace GitTools.Testing.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; + + 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; + } + } + + 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(); + } + + internal class NLogLogger + { + private readonly dynamic _logger; + + 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(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; + } + + [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; + } + } + } + } + + 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(); + } + + 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 _isEnabledForDelegate; + private readonly Action _logDelegate; + + [SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "ILogger")] + internal Log4NetLogger(dynamic logger) + { + _logger = logger.Logger; + + var logEventLevelType = Type.GetType("log4net.Core.Level, log4net"); + if (logEventLevelType == null) + { + throw new InvalidOperationException("Type log4net.Core.Level was not found."); + } + + var levelFields = logEventLevelType.GetFieldsPortable().ToList(); + _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); + + // Func isEnabledFor = (logger, level) => { return ((log4net.Core.ILogger)logger).IsEnabled(level); } + var loggerType = Type.GetType("log4net.Core.ILogger, log4net"); + if (loggerType == null) + { + throw new InvalidOperationException("Type log4net.Core.ILogger, was not found."); + } + MethodInfo isEnabledMethodInfo = loggerType.GetMethodPortable("IsEnabledFor", logEventLevelType); + ParameterExpression instanceParam = Expression.Parameter(typeof(object)); + UnaryExpression instanceCast = Expression.Convert(instanceParam, loggerType); + ParameterExpression callerStackBoundaryDeclaringTypeParam = Expression.Parameter(typeof(Type)); + ParameterExpression levelParam = Expression.Parameter(typeof(object)); + ParameterExpression messageParam = Expression.Parameter(typeof(string)); + UnaryExpression levelCast = Expression.Convert(levelParam, logEventLevelType); + MethodCallExpression isEnabledMethodCall = Expression.Call(instanceCast, isEnabledMethodInfo, levelCast); + _isEnabledForDelegate = Expression.Lambda>(isEnabledMethodCall, instanceParam, levelParam).Compile(); + + // Action Log = + // (logger, callerStackBoundaryDeclaringType, level, message, exception) => { ((ILogger)logger).Write(callerStackBoundaryDeclaringType, level, message, exception); } + MethodInfo writeExceptionMethodInfo = loggerType.GetMethodPortable("Log", + typeof(Type), + logEventLevelType, + typeof(string), + typeof(Exception)); + ParameterExpression exceptionParam = Expression.Parameter(typeof(Exception)); + var writeMethodExp = Expression.Call( + instanceCast, + writeExceptionMethodInfo, + callerStackBoundaryDeclaringTypeParam, + levelCast, + messageParam, + exceptionParam); + _logDelegate = Expression.Lambda>( + writeMethodExp, + instanceParam, + callerStackBoundaryDeclaringTypeParam, + levelParam, + messageParam, + exceptionParam).Compile(); + } + + public bool Log(LogLevel logLevel, Func messageFunc, Exception exception, params object[] formatParameters) + { + if (messageFunc == null) + { + return IsLogLevelEnable(logLevel); + } + + if (!IsLogLevelEnable(logLevel)) + { + return false; + } + + messageFunc = LogMessageFormatter.SimulateStructuredLogging(messageFunc, formatParameters); + + // determine correct caller - this might change due to jit optimizations with method inlining + if (s_callerStackBoundaryType == null) + { + lock (CallerStackBoundaryTypeSync) + { +#if !LIBLOG_PORTABLE + StackTrace stack = new StackTrace(); + Type thisType = GetType(); + s_callerStackBoundaryType = Type.GetType("LoggerExecutionWrapper"); + for (var i = 1; i < stack.FrameCount; i++) + { + if (!IsInTypeHierarchy(thisType, stack.GetFrame(i).GetMethod().DeclaringType)) + { + s_callerStackBoundaryType = stack.GetFrame(i - 1).GetMethod().DeclaringType; + break; + } + } +#else + s_callerStackBoundaryType = typeof (LoggerExecutionWrapper); +#endif + } + } + + var translatedLevel = TranslateLevel(logLevel); + _logDelegate(_logger, s_callerStackBoundaryType, translatedLevel, messageFunc(), exception); + return true; + } + + private static bool IsInTypeHierarchy(Type currentType, Type checkType) + { + while (currentType != null && currentType != typeof(object)) + { + if (currentType == checkType) + { + return true; + } + currentType = currentType.GetBaseTypePortable(); + } + return false; + } + + private bool IsLogLevelEnable(LogLevel logLevel) + { + var level = TranslateLevel(logLevel); + return _isEnabledForDelegate(_logger, level); + } + + private object TranslateLevel(LogLevel logLevel) + { + switch (logLevel) + { + case LogLevel.Trace: + 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); + } + } + } + } + + internal class EntLibLogProvider : LogProviderBase + { + private const string TypeTemplate = "Microsoft.Practices.EnterpriseLibrary.Logging.{0}, Microsoft.Practices.EnterpriseLibrary.Logging"; + private static bool s_providerIsAvailableOverride = true; + private static readonly Type LogEntryType; + private static readonly Type LoggerType; + private static readonly Type TraceEventTypeType; + private static readonly Action WriteLogEntry; + private static readonly Func ShouldLogEntry; + + [SuppressMessage("Microsoft.Performance", "CA1810:InitializeReferenceTypeStaticFieldsInline")] + static EntLibLogProvider() + { + LogEntryType = Type.GetType(string.Format(CultureInfo.InvariantCulture, TypeTemplate, "LogEntry")); + LoggerType = Type.GetType(string.Format(CultureInfo.InvariantCulture, TypeTemplate, "Logger")); + TraceEventTypeType = TraceEventTypeValues.Type; + if (LogEntryType == null + || TraceEventTypeType == null + || LoggerType == null) + { + return; + } + WriteLogEntry = GetWriteLogEntry(); + ShouldLogEntry = GetShouldLogEntry(); + } + + [SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "EnterpriseLibrary")] + public EntLibLogProvider() + { + if (!IsLoggerAvailable()) + { + throw new InvalidOperationException("Microsoft.Practices.EnterpriseLibrary.Logging.Logger not found"); + } + } + + public static bool ProviderIsAvailableOverride + { + get { return s_providerIsAvailableOverride; } + set { s_providerIsAvailableOverride = value; } + } + + public override Logger GetLogger(string name) + { + return new EntLibLogger(name, WriteLogEntry, ShouldLogEntry).Log; + } + + internal static bool IsLoggerAvailable() + { + return ProviderIsAvailableOverride + && TraceEventTypeType != null + && LogEntryType != null; + } + + private static Action GetWriteLogEntry() + { + // new LogEntry(...) + var logNameParameter = Expression.Parameter(typeof(string), "logName"); + var messageParameter = Expression.Parameter(typeof(string), "message"); + var severityParameter = Expression.Parameter(typeof(int), "severity"); + + MemberInitExpression memberInit = GetWriteLogExpression( + messageParameter, + Expression.Convert(severityParameter, TraceEventTypeType), + logNameParameter); + + //Logger.Write(new LogEntry(....)); + MethodInfo writeLogEntryMethod = LoggerType.GetMethodPortable("Write", LogEntryType); + var writeLogEntryExpression = Expression.Call(writeLogEntryMethod, memberInit); + + return Expression.Lambda>( + writeLogEntryExpression, + logNameParameter, + messageParameter, + severityParameter).Compile(); + } + + private static Func GetShouldLogEntry() + { + // new LogEntry(...) + var logNameParameter = Expression.Parameter(typeof(string), "logName"); + var severityParameter = Expression.Parameter(typeof(int), "severity"); + + MemberInitExpression memberInit = GetWriteLogExpression( + Expression.Constant("***dummy***"), + Expression.Convert(severityParameter, TraceEventTypeType), + logNameParameter); + + //Logger.Write(new LogEntry(....)); + MethodInfo writeLogEntryMethod = LoggerType.GetMethodPortable("ShouldLog", LogEntryType); + var writeLogEntryExpression = Expression.Call(writeLogEntryMethod, memberInit); + + return Expression.Lambda>( + writeLogEntryExpression, + logNameParameter, + severityParameter).Compile(); + } + + private static MemberInitExpression GetWriteLogExpression(Expression message, + Expression severityParameter, ParameterExpression logNameParameter) + { + var entryType = LogEntryType; + MemberInitExpression memberInit = Expression.MemberInit(Expression.New(entryType), + Expression.Bind(entryType.GetPropertyPortable("Message"), message), + Expression.Bind(entryType.GetPropertyPortable("Severity"), severityParameter), + Expression.Bind( + entryType.GetPropertyPortable("TimeStamp"), + Expression.Property(null, typeof (DateTime).GetPropertyPortable("UtcNow"))), + Expression.Bind( + entryType.GetPropertyPortable("Categories"), + Expression.ListInit( + Expression.New(typeof (List)), + typeof (List).GetMethodPortable("Add", typeof (string)), + logNameParameter))); + return memberInit; + } + + internal class EntLibLogger + { + private readonly string _loggerName; + private readonly Action _writeLog; + private readonly Func _shouldLog; + + internal EntLibLogger(string loggerName, Action writeLog, Func shouldLog) + { + _loggerName = loggerName; + _writeLog = writeLog; + _shouldLog = shouldLog; + } + + public bool Log(LogLevel logLevel, Func messageFunc, Exception exception, params object[] formatParameters) + { + var severity = MapSeverity(logLevel); + if (messageFunc == null) + { + return _shouldLog(_loggerName, severity); + } + + + messageFunc = LogMessageFormatter.SimulateStructuredLogging(messageFunc, formatParameters); + if (exception != null) + { + return LogException(logLevel, messageFunc, exception); + } + _writeLog(_loggerName, messageFunc(), severity); + return true; + } + + public bool LogException(LogLevel logLevel, Func messageFunc, Exception exception) + { + var severity = MapSeverity(logLevel); + var message = messageFunc() + Environment.NewLine + exception; + _writeLog(_loggerName, message, severity); + return true; + } + + private static int MapSeverity(LogLevel logLevel) + { + switch (logLevel) + { + case LogLevel.Fatal: + return TraceEventTypeValues.Critical; + case LogLevel.Error: + return TraceEventTypeValues.Error; + case LogLevel.Warn: + return TraceEventTypeValues.Warning; + case LogLevel.Info: + return TraceEventTypeValues.Information; + default: + return TraceEventTypeValues.Verbose; + } + } + } + } + + internal class SerilogLogProvider : LogProviderBase + { + private readonly Func _getLoggerByNameDelegate; + private static bool s_providerIsAvailableOverride = true; + + [SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "Serilog")] + public SerilogLogProvider() + { + if (!IsLoggerAvailable()) + { + throw new InvalidOperationException("Serilog.Log not found"); + } + _getLoggerByNameDelegate = GetForContextMethodCall(); + } + + public static bool ProviderIsAvailableOverride + { + get { return s_providerIsAvailableOverride; } + set { s_providerIsAvailableOverride = value; } + } + + public override Logger GetLogger(string name) + { + return new SerilogLogger(_getLoggerByNameDelegate(name)).Log; + } + + internal static bool IsLoggerAvailable() + { + return ProviderIsAvailableOverride && GetLogManagerType() != null; + } + + protected override OpenNdc GetOpenNdcMethod() + { + return message => GetPushProperty()("NDC", message); + } + + protected override OpenMdc GetOpenMdcMethod() + { + return (key, value) => GetPushProperty()(key, value); + } + + private static Func GetPushProperty() + { + Type ndcContextType = Type.GetType("Serilog.Context.LogContext, Serilog.FullNetFx"); + MethodInfo pushPropertyMethod = ndcContextType.GetMethodPortable( + "PushProperty", + typeof(string), + typeof(object), + typeof(bool)); + ParameterExpression nameParam = Expression.Parameter(typeof(string), "name"); + ParameterExpression valueParam = Expression.Parameter(typeof(object), "value"); + ParameterExpression destructureObjectParam = Expression.Parameter(typeof(bool), "destructureObjects"); + MethodCallExpression pushPropertyMethodCall = Expression + .Call(null, pushPropertyMethod, nameParam, valueParam, destructureObjectParam); + var pushProperty = Expression + .Lambda>( + pushPropertyMethodCall, + nameParam, + valueParam, + destructureObjectParam) + .Compile(); + + return (key, value) => pushProperty(key, value, false); + } + + private static Type GetLogManagerType() + { + return Type.GetType("Serilog.Log, Serilog"); + } + + private static Func GetForContextMethodCall() + { + Type logManagerType = GetLogManagerType(); + MethodInfo method = logManagerType.GetMethodPortable("ForContext", typeof(string), typeof(object), typeof(bool)); + ParameterExpression propertyNameParam = Expression.Parameter(typeof(string), "propertyName"); + ParameterExpression valueParam = Expression.Parameter(typeof(object), "value"); + ParameterExpression destructureObjectsParam = Expression.Parameter(typeof(bool), "destructureObjects"); + MethodCallExpression methodCall = Expression.Call(null, method, new Expression[] + { + propertyNameParam, + valueParam, + destructureObjectsParam + }); + var func = Expression.Lambda>( + methodCall, + propertyNameParam, + valueParam, + destructureObjectsParam) + .Compile(); + return name => func("SourceContext", name, false); + } + + internal class SerilogLogger + { + private readonly object _logger; + private static readonly object DebugLevel; + private static readonly object ErrorLevel; + private static readonly object FatalLevel; + private static readonly object InformationLevel; + private static readonly object VerboseLevel; + private static readonly object WarningLevel; + private static readonly Func IsEnabled; + private static readonly Action Write; + private static readonly Action WriteException; + + [SuppressMessage("Microsoft.Design", "CA1065:DoNotRaiseExceptionsInUnexpectedLocations")] + [SuppressMessage("Microsoft.Performance", "CA1810:InitializeReferenceTypeStaticFieldsInline")] + [SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "ILogger")] + [SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "LogEventLevel")] + [SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "Serilog")] + static SerilogLogger() + { + var logEventLevelType = Type.GetType("Serilog.Events.LogEventLevel, Serilog"); + if (logEventLevelType == null) + { + throw new InvalidOperationException("Type Serilog.Events.LogEventLevel was not found."); + } + DebugLevel = Enum.Parse(logEventLevelType, "Debug", false); + ErrorLevel = Enum.Parse(logEventLevelType, "Error", false); + FatalLevel = Enum.Parse(logEventLevelType, "Fatal", false); + InformationLevel = Enum.Parse(logEventLevelType, "Information", false); + VerboseLevel = Enum.Parse(logEventLevelType, "Verbose", false); + WarningLevel = Enum.Parse(logEventLevelType, "Warning", false); + + // Func isEnabled = (logger, level) => { return ((SeriLog.ILogger)logger).IsEnabled(level); } + var loggerType = Type.GetType("Serilog.ILogger, Serilog"); + if (loggerType == null) + { + throw new InvalidOperationException("Type Serilog.ILogger was not found."); + } + MethodInfo isEnabledMethodInfo = loggerType.GetMethodPortable("IsEnabled", logEventLevelType); + ParameterExpression instanceParam = Expression.Parameter(typeof(object)); + UnaryExpression instanceCast = Expression.Convert(instanceParam, loggerType); + ParameterExpression levelParam = Expression.Parameter(typeof(object)); + UnaryExpression levelCast = Expression.Convert(levelParam, logEventLevelType); + MethodCallExpression isEnabledMethodCall = Expression.Call(instanceCast, isEnabledMethodInfo, levelCast); + IsEnabled = Expression.Lambda>(isEnabledMethodCall, instanceParam, levelParam).Compile(); + + // Action Write = + // (logger, level, message, params) => { ((SeriLog.ILoggerILogger)logger).Write(level, message, params); } + MethodInfo writeMethodInfo = loggerType.GetMethodPortable("Write", logEventLevelType, typeof(string), typeof(object[])); + ParameterExpression messageParam = Expression.Parameter(typeof(string)); + ParameterExpression propertyValuesParam = Expression.Parameter(typeof(object[])); + MethodCallExpression writeMethodExp = Expression.Call( + instanceCast, + writeMethodInfo, + levelCast, + messageParam, + propertyValuesParam); + var expression = Expression.Lambda>( + writeMethodExp, + instanceParam, + levelParam, + messageParam, + propertyValuesParam); + Write = expression.Compile(); + + // Action WriteException = + // (logger, level, exception, message) => { ((ILogger)logger).Write(level, exception, message, new object[]); } + MethodInfo writeExceptionMethodInfo = loggerType.GetMethodPortable("Write", + logEventLevelType, + typeof(Exception), + typeof(string), + typeof(object[])); + ParameterExpression exceptionParam = Expression.Parameter(typeof(Exception)); + writeMethodExp = Expression.Call( + instanceCast, + writeExceptionMethodInfo, + levelCast, + exceptionParam, + messageParam, + propertyValuesParam); + WriteException = Expression.Lambda>( + writeMethodExp, + instanceParam, + levelParam, + exceptionParam, + messageParam, + propertyValuesParam).Compile(); + } + + internal SerilogLogger(object logger) + { + _logger = logger; + } + + public bool Log(LogLevel logLevel, Func messageFunc, Exception exception, params object[] formatParameters) + { + var translatedLevel = TranslateLevel(logLevel); + if (messageFunc == null) + { + return IsEnabled(_logger, translatedLevel); + } + + if (!IsEnabled(_logger, translatedLevel)) + { + return false; + } + + if (exception != null) + { + LogException(translatedLevel, messageFunc, exception, formatParameters); + } + else + { + LogMessage(translatedLevel, messageFunc, formatParameters); + } + + return true; + } + + private void LogMessage(object translatedLevel, Func messageFunc, object[] formatParameters) + { + Write(_logger, translatedLevel, messageFunc(), formatParameters); + } + + private void LogException(object logLevel, Func messageFunc, Exception exception, object[] formatParams) + { + WriteException(_logger, logLevel, exception, messageFunc(), formatParams); + } + + private static object TranslateLevel(LogLevel logLevel) + { + switch (logLevel) + { + case LogLevel.Fatal: + return FatalLevel; + case LogLevel.Error: + return ErrorLevel; + case LogLevel.Warn: + return WarningLevel; + case LogLevel.Info: + return InformationLevel; + case LogLevel.Trace: + return VerboseLevel; + default: + return DebugLevel; + } + } + } + } + + internal class LoupeLogProvider : LogProviderBase + { + /// + /// The form of the Loupe Log.Write method we're using + /// + internal delegate void WriteDelegate( + int severity, + string logSystem, + int skipFrames, + Exception exception, + bool attributeToException, + int writeMode, + string detailsXml, + string category, + string caption, + string description, + params object[] args + ); + + private static bool s_providerIsAvailableOverride = true; + private readonly WriteDelegate _logWriteDelegate; + + public LoupeLogProvider() + { + if (!IsLoggerAvailable()) + { + throw new InvalidOperationException("Gibraltar.Agent.Log (Loupe) not found"); + } + + _logWriteDelegate = GetLogWriteDelegate(); + } + + /// + /// Gets or sets a value indicating whether [provider is available override]. Used in tests. + /// + /// + /// true if [provider is available override]; otherwise, false. + /// + public static bool ProviderIsAvailableOverride + { + get { return s_providerIsAvailableOverride; } + set { s_providerIsAvailableOverride = value; } + } + + public override Logger GetLogger(string name) + { + return new LoupeLogger(name, _logWriteDelegate).Log; + } + + public static bool IsLoggerAvailable() + { + return ProviderIsAvailableOverride && GetLogManagerType() != null; + } + + private static Type GetLogManagerType() + { + return Type.GetType("Gibraltar.Agent.Log, Gibraltar.Agent"); + } + + private static WriteDelegate GetLogWriteDelegate() + { + Type logManagerType = GetLogManagerType(); + Type logMessageSeverityType = Type.GetType("Gibraltar.Agent.LogMessageSeverity, Gibraltar.Agent"); + Type logWriteModeType = Type.GetType("Gibraltar.Agent.LogWriteMode, Gibraltar.Agent"); + + MethodInfo method = logManagerType.GetMethodPortable( + "Write", + logMessageSeverityType, typeof(string), typeof(int), typeof(Exception), typeof(bool), + logWriteModeType, typeof(string), typeof(string), typeof(string), typeof(string), typeof(object[])); + + var callDelegate = (WriteDelegate)method.CreateDelegate(typeof(WriteDelegate)); + return callDelegate; + } + + internal class LoupeLogger + { + private const string LogSystem = "LibLog"; + + private readonly string _category; + private readonly WriteDelegate _logWriteDelegate; + private readonly int _skipLevel; + + internal LoupeLogger(string category, WriteDelegate logWriteDelegate) + { + _category = category; + _logWriteDelegate = logWriteDelegate; +#if DEBUG + _skipLevel = 2; +#else + _skipLevel = 1; +#endif + } + + public bool Log(LogLevel logLevel, Func messageFunc, Exception exception, params object[] formatParameters) + { + if (messageFunc == null) + { + //nothing to log.. + return true; + } + + messageFunc = LogMessageFormatter.SimulateStructuredLogging(messageFunc, formatParameters); + + _logWriteDelegate(ToLogMessageSeverity(logLevel), LogSystem, _skipLevel, exception, true, 0, null, + _category, null, messageFunc.Invoke()); + + return true; + } + + private static int ToLogMessageSeverity(LogLevel logLevel) + { + switch (logLevel) + { + case LogLevel.Trace: + return TraceEventTypeValues.Verbose; + case LogLevel.Debug: + return TraceEventTypeValues.Verbose; + case LogLevel.Info: + return TraceEventTypeValues.Information; + case LogLevel.Warn: + return TraceEventTypeValues.Warning; + case LogLevel.Error: + return TraceEventTypeValues.Error; + case LogLevel.Fatal: + return TraceEventTypeValues.Critical; + default: + throw new ArgumentOutOfRangeException("logLevel"); + } + } + } + } + + internal static class TraceEventTypeValues + { + internal static readonly Type Type; + internal static readonly int Verbose; + internal static readonly int Information; + internal static readonly int Warning; + internal static readonly int Error; + internal static readonly int Critical; + + [SuppressMessage("Microsoft.Performance", "CA1810:InitializeReferenceTypeStaticFieldsInline")] + static TraceEventTypeValues() + { + var assembly = typeof(Uri).GetAssemblyPortable(); // This is to get to the System.dll assembly in a PCL compatible way. + if (assembly == null) + { + return; + } + Type = assembly.GetType("System.Diagnostics.TraceEventType"); + if (Type == null) return; + Verbose = (int)Enum.Parse(Type, "Verbose", false); + Information = (int)Enum.Parse(Type, "Information", false); + Warning = (int)Enum.Parse(Type, "Warning", false); + Error = (int)Enum.Parse(Type, "Error", false); + Critical = (int)Enum.Parse(Type, "Critical", false); + } + } + + internal static class LogMessageFormatter + { + private static readonly Regex Pattern = new Regex(@"\{@?\w{1,}\}"); + + /// + /// Some logging frameworks support structured logging, such as serilog. This will allow you to add names to structured data in a format string: + /// For example: Log("Log message to {user}", user). This only works with serilog, but as the user of LibLog, you don't know if serilog is actually + /// used. So, this class simulates that. it will replace any text in {curly braces} with an index number. + /// + /// "Log {message} to {user}" would turn into => "Log {0} to {1}". Then the format parameters are handled using regular .net string.Format. + /// + /// The message builder. + /// The format parameters. + /// + public static Func SimulateStructuredLogging(Func messageBuilder, object[] formatParameters) + { + if (formatParameters == null || formatParameters.Length == 0) + { + return messageBuilder; + } + + return () => + { + string targetMessage = messageBuilder(); + int argumentIndex = 0; + foreach (Match match in Pattern.Matches(targetMessage)) + { + int notUsed; + if (!int.TryParse(match.Value.Substring(1, match.Value.Length -2), out notUsed)) + { + targetMessage = ReplaceFirst(targetMessage, match.Value, + "{" + argumentIndex++ + "}"); + } + } + try + { + return string.Format(CultureInfo.InvariantCulture, targetMessage, formatParameters); + } + catch (FormatException ex) + { + throw new FormatException("The input string '" + targetMessage + "' could not be formatted using string.Format", ex); + } + }; + } + + private static string ReplaceFirst(string text, string search, string replace) + { + int pos = text.IndexOf(search, StringComparison.Ordinal); + if (pos < 0) + { + return text; + } + return text.Substring(0, pos) + replace + text.Substring(pos + search.Length); + } + } + + internal static class TypeExtensions + { + internal static MethodInfo GetMethodPortable(this Type type, string name) + { +#if LIBLOG_PORTABLE + return type.GetRuntimeMethods().SingleOrDefault(m => m.Name == name); +#else + return type.GetMethod(name); +#endif + } + + internal static MethodInfo GetMethodPortable(this Type type, string name, params Type[] types) + { +#if LIBLOG_PORTABLE + return type.GetRuntimeMethod(name, types); +#else + return type.GetMethod(name, types); +#endif + } + + internal static PropertyInfo GetPropertyPortable(this Type type, string name) + { +#if LIBLOG_PORTABLE + return type.GetRuntimeProperty(name); +#else + return type.GetProperty(name); +#endif + } + + internal static IEnumerable GetFieldsPortable(this Type type) + { +#if LIBLOG_PORTABLE + return type.GetRuntimeFields(); +#else + return type.GetFields(); +#endif + } + + internal static Type GetBaseTypePortable(this Type type) + { +#if LIBLOG_PORTABLE + return type.GetTypeInfo().BaseType; +#else + return type.BaseType; +#endif + } + +#if LIBLOG_PORTABLE + internal static MethodInfo GetGetMethod(this PropertyInfo propertyInfo) + { + return propertyInfo.GetMethod; + } + + internal static MethodInfo GetSetMethod(this PropertyInfo propertyInfo) + { + return propertyInfo.SetMethod; + } +#endif + +#if !LIBLOG_PORTABLE + internal static object CreateDelegate(this MethodInfo methodInfo, Type delegateType) + { + return Delegate.CreateDelegate(delegateType, methodInfo); + } +#endif + + internal static Assembly GetAssemblyPortable(this Type type) + { +#if LIBLOG_PORTABLE + return type.GetTypeInfo().Assembly; +#else + return type.Assembly; +#endif + } + } + + internal class DisposableAction : IDisposable + { + private readonly Action _onDispose; + + public DisposableAction(Action onDispose = null) + { + _onDispose = onDispose; + } + + public void Dispose() + { + if(_onDispose != null) + { + _onDispose(); + } + } + } +} diff --git a/src/GitTools.Testing/Fixtures/BaseGitFlowRepositoryFixture.cs b/src/GitTools.Testing/Fixtures/BaseGitFlowRepositoryFixture.cs new file mode 100644 index 0000000000..02ee5974d4 --- /dev/null +++ b/src/GitTools.Testing/Fixtures/BaseGitFlowRepositoryFixture.cs @@ -0,0 +1,45 @@ + // ReSharper disable once CheckNamespace +namespace GitTools.Testing +{ + using System; + using System.IO; + using LibGit2Sharp; + + /// + /// Creates a repo with a develop branch off master which is a single commit ahead of master + /// + public class BaseGitFlowRepositoryFixture : EmptyRepositoryFixture + { + /// + /// Creates a repo with a develop branch off master which is a single commit ahead of master + /// + /// Master will be tagged with the initial version before branching develop + /// + public BaseGitFlowRepositoryFixture(string initialVersion) : + this(r => r.MakeATaggedCommit(initialVersion)) + { + } + + /// + /// Creates a repo with a develop branch off master which is a single commit ahead of master + /// + /// The initial setup actions will be performed before branching develop + /// + public BaseGitFlowRepositoryFixture(Action initialMasterAction) + { + SetupRepo(initialMasterAction); + } + + void SetupRepo(Action initialMasterAction) + { + var randomFile = Path.Combine(Repository.Info.WorkingDirectory, Guid.NewGuid().ToString()); + File.WriteAllText(randomFile, string.Empty); + Commands.Stage(Repository, randomFile); + + initialMasterAction(Repository); + + Commands.Checkout(Repository, Repository.CreateBranch("develop")); + Repository.MakeACommit(); + } + } +} \ No newline at end of file diff --git a/src/GitTools.Testing/Fixtures/EmptyRepositoryFixture.cs b/src/GitTools.Testing/Fixtures/EmptyRepositoryFixture.cs new file mode 100644 index 0000000000..4f16b1ca36 --- /dev/null +++ b/src/GitTools.Testing/Fixtures/EmptyRepositoryFixture.cs @@ -0,0 +1,21 @@ +// ReSharper disable once CheckNamespace +namespace GitTools.Testing +{ + using System; + using LibGit2Sharp; + + public class EmptyRepositoryFixture : RepositoryFixtureBase + { + public EmptyRepositoryFixture() : base(CreateNewRepository) + { + } + + private static IRepository CreateNewRepository(string path) + { + LibGit2Sharp.Repository.Init(path); + Console.WriteLine("Created git repository at '{0}'", path); + + return new Repository(path); + } + } +} \ No newline at end of file diff --git a/src/GitTools.Testing/Fixtures/LocalRepositoryFixture.cs b/src/GitTools.Testing/Fixtures/LocalRepositoryFixture.cs new file mode 100644 index 0000000000..54de6b1af1 --- /dev/null +++ b/src/GitTools.Testing/Fixtures/LocalRepositoryFixture.cs @@ -0,0 +1,12 @@ +// ReSharper disable once CheckNamespace +namespace GitTools.Testing +{ + using LibGit2Sharp; + + public class LocalRepositoryFixture : RepositoryFixtureBase + { + public LocalRepositoryFixture(IRepository repository) : base(repository) + { + } + } +} \ No newline at end of file diff --git a/src/GitTools.Testing/Fixtures/RemoteRepositoryFixture.cs b/src/GitTools.Testing/Fixtures/RemoteRepositoryFixture.cs new file mode 100644 index 0000000000..1c3e8d6d3a --- /dev/null +++ b/src/GitTools.Testing/Fixtures/RemoteRepositoryFixture.cs @@ -0,0 +1,53 @@ +// ReSharper disable once CheckNamespace +namespace GitTools.Testing +{ + using System; + using LibGit2Sharp; + + /// + /// Creates a remote repository then clones it + /// Remote = Repository + /// Local = LocalRepositoryFixture + /// + public class RemoteRepositoryFixture : RepositoryFixtureBase + { + public RemoteRepositoryFixture(Func builder) + : base(builder) + { + CreateLocalRepository(); + } + + public RemoteRepositoryFixture() : this(CreateNewRepository) + { + } + + /// + /// Fixture pointing at the local repository + /// + public LocalRepositoryFixture LocalRepositoryFixture { get; private set; } + + private static IRepository CreateNewRepository(string path) + { + LibGit2Sharp.Repository.Init(path); + Console.WriteLine("Created git repository at '{0}'", path); + + var repo = new Repository(path); + repo.MakeCommits(5); + return repo; + } + + private void CreateLocalRepository() + { + LocalRepositoryFixture = CloneRepository(); + } + + /// + /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + /// + public override void Dispose() + { + LocalRepositoryFixture.Dispose(); + base.Dispose(); + } + } +} \ No newline at end of file diff --git a/src/GitTools.Testing/Fixtures/RepositoryFixtureBase.cs b/src/GitTools.Testing/Fixtures/RepositoryFixtureBase.cs new file mode 100644 index 0000000000..9c38132291 --- /dev/null +++ b/src/GitTools.Testing/Fixtures/RepositoryFixtureBase.cs @@ -0,0 +1,123 @@ + // ReSharper disable once CheckNamespace + +namespace GitTools.Testing +{ + using System; + using Internal; + using LibGit2Sharp; + using Logging; + + /// + /// Fixture abstracting a git repository + /// + public abstract class RepositoryFixtureBase : IDisposable + { + static readonly ILog Logger = LogProvider.For(); + readonly SequenceDiagram _sequenceDiagram; + + protected RepositoryFixtureBase(Func repoBuilder) + : this(repoBuilder(PathHelper.GetTempPath())) + { + } + + protected RepositoryFixtureBase(IRepository repository) + { + _sequenceDiagram = new SequenceDiagram(); + Repository = repository; + Repository.Config.Set("user.name", "Test"); + Repository.Config.Set("user.email", "test@email.com"); + } + + public IRepository Repository { get; private set; } + + public string RepositoryPath + { + get { return Repository.Info.WorkingDirectory.TrimEnd('\\'); } + } + + public SequenceDiagram SequenceDiagram + { + get { return _sequenceDiagram; } + } + + /// + /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + /// + public virtual void Dispose() + { + Repository.Dispose(); + + try + { + DirectoryHelper.DeleteDirectory(RepositoryPath); + } + catch (Exception e) + { + Logger.InfoFormat("Failed to clean up repository path at {0}. Received exception: {1}", RepositoryPath, + e.Message); + } + + _sequenceDiagram.End(); + Logger.InfoFormat("**Visualisation of test:**"); + Logger.InfoFormat(string.Empty); + Logger.InfoFormat(_sequenceDiagram.GetDiagram()); + } + + public void Checkout(string branch) + { + Commands.Checkout(Repository, branch); + } + + public void MakeATaggedCommit(string tag) + { + MakeACommit(); + ApplyTag(tag); + } + + public void ApplyTag(string tag) + { + _sequenceDiagram.ApplyTag(tag, Repository.Head.FriendlyName); + Repository.ApplyTag(tag); + } + + public void BranchTo(string branchName, string @as = null) + { + _sequenceDiagram.BranchTo(branchName, Repository.Head.FriendlyName, @as); + var branch = Repository.CreateBranch(branchName); + Commands.Checkout(Repository, branch); + } + + public void BranchToFromTag(string branchName, string fromTag, string onBranch, string @as = null) + { + _sequenceDiagram.BranchToFromTag(branchName, fromTag, onBranch, @as); + var branch = Repository.CreateBranch(branchName); + Commands.Checkout(Repository, branch); + } + + public void MakeACommit() + { + var to = Repository.Head.FriendlyName; + _sequenceDiagram.MakeACommit(to); + Repository.MakeACommit(); + } + + /// + /// Merges (no-ff) specified branch into the current HEAD of this repository + /// + public void MergeNoFF(string mergeSource) + { + _sequenceDiagram.Merge(mergeSource, Repository.Head.FriendlyName); + Repository.MergeNoFF(mergeSource, Generate.SignatureNow()); + } + + /// + /// Clones the repository managed by this fixture into another LocalRepositoryFixture + /// + public LocalRepositoryFixture CloneRepository() + { + var localPath = PathHelper.GetTempPath(); + LibGit2Sharp.Repository.Clone(RepositoryPath, localPath); + return new LocalRepositoryFixture(new Repository(localPath)); + } + } +} \ No newline at end of file diff --git a/src/GitTools.Testing/Fixtures/SequenceDiagram.cs b/src/GitTools.Testing/Fixtures/SequenceDiagram.cs new file mode 100644 index 0000000000..afd1263d7f --- /dev/null +++ b/src/GitTools.Testing/Fixtures/SequenceDiagram.cs @@ -0,0 +1,156 @@ +// ReSharper disable once CheckNamespace +namespace GitTools.Testing +{ + using System.Collections.Generic; + using System.Text; + using Internal; + + /// + /// Creates an abstraction over a PlantUML Sequence diagram to draw a sequence diagram of a git repository being created + /// + public class SequenceDiagram + { + readonly Dictionary _participants = new Dictionary(); + readonly StringBuilder _diagramBuilder; + + /// + /// Initializes a new instance of the class. + /// + public SequenceDiagram() + { + _diagramBuilder = new StringBuilder(); + _diagramBuilder.AppendLine("@startuml"); + } + + /// + /// Activates a branch/participant in the sequence diagram + /// + public void Activate(string branch) + { + _diagramBuilder.AppendLineFormat("activate {0}", GetParticipant(branch)); + } + + /// + /// Destroys a branch/participant in the sequence diagram + /// + public void Destroy(string branch) + { + _diagramBuilder.AppendLineFormat("destroy {0}", GetParticipant(branch)); + } + + /// + /// Creates a participant in the sequence diagram + /// + public void Participant(string participant, string @as = null) + { + _participants.Add(participant, @as ?? participant); + if (@as == null) + _diagramBuilder.AppendLineFormat("participant {0}", participant); + else + _diagramBuilder.AppendLineFormat("participant \"{0}\" as {1}", participant, @as); + } + + /// + /// Appends a divider with specified text to the sequence diagram + /// + public void Divider(string text) + { + _diagramBuilder.AppendLineFormat("== {0} ==", text); + } + + /// + /// Appends a note over one or many participants to the sequence diagram + /// + public void NoteOver(string noteText, string startParticipant, string endParticipant = null, string prefix = null, string color = null) + { + _diagramBuilder.AppendLineFormat( + prefix + @"note over {0}{1}{2} + {3} +end note", + GetParticipant(startParticipant), + endParticipant == null ? null : ", " + GetParticipant(endParticipant), + color == null ? null : " " + color, + noteText.Replace("\n", "\n ")); + } + + /// + /// Appends applying a tag to the specified branch/participant to the sequence diagram + /// + public void ApplyTag(string tag, string toBranch) + { + _diagramBuilder.AppendLineFormat("{0} -> {0}: tag {1}", GetParticipant(toBranch), tag); + } + + /// + /// Appends branching from a branch to another branch, @as can override the participant name + /// + public void BranchTo(string branchName, string currentName, string @as) + { + + if (!_participants.ContainsKey(branchName)) + { + _diagramBuilder.Append("create "); + Participant(branchName, @as); + } + + _diagramBuilder.AppendLineFormat( + "{0} -> {1}: branch from {2}", + GetParticipant(currentName), + GetParticipant(branchName), currentName); + } + + /// + /// Appends branching from a tag to a specified branch to the sequence diagram + /// + public void BranchToFromTag(string branchName, string fromTag, string onBranch, string @as) + { + if (!_participants.ContainsKey(branchName)) + { + _diagramBuilder.Append("create "); + Participant(branchName, @as); + } + + _diagramBuilder.AppendLineFormat("{0} -> {1}: branch from tag ({2})", GetParticipant(onBranch), GetParticipant(branchName), fromTag); + } + + /// + /// Appends a commit on the target participant/branch to the sequence diagram + /// + public void MakeACommit(string toParticipant) + { + _diagramBuilder.AppendLineFormat("{0} -> {0}: commit", GetParticipant(toParticipant)); + } + + /// + /// Append a merge to the sequence diagram + /// + public void Merge(string @from, string to) + { + _diagramBuilder.AppendLineFormat("{0} -> {1}: merge", GetParticipant(@from), GetParticipant(to)); + } + + string GetParticipant(string branch) + { + if (_participants.ContainsKey(branch)) + return _participants[branch]; + + return branch; + } + + /// + /// Ends the sequence diagram + /// + public void End() + { + _diagramBuilder.AppendLine("@enduml"); + } + + /// + /// returns the plantUML representation of the Sequence Diagram + /// + public string GetDiagram() + { + return _diagramBuilder.ToString(); + } + } +} \ No newline at end of file diff --git a/src/GitTools.Testing/Generate.cs b/src/GitTools.Testing/Generate.cs new file mode 100644 index 0000000000..b9f9ff80ba --- /dev/null +++ b/src/GitTools.Testing/Generate.cs @@ -0,0 +1,28 @@ +using System; +using LibGit2Sharp; + +namespace GitTools.Testing +{ + /// + /// Static helper class for generating data git needs, like signatures + /// + public static class Generate + { + /// + /// Create a libgit2sharp signature at VirtualTime.Now + /// + public static Signature SignatureNow() + { + var dateTimeOffset = VirtualTime.Now; + return Signature(dateTimeOffset); + } + + /// + /// Creates a libgit2sharp signature at the specified time + /// + public static Signature Signature(DateTimeOffset dateTimeOffset) + { + return new Signature("A. U. Thor", "thor@valhalla.asgard.com", dateTimeOffset); + } + } +} \ No newline at end of file diff --git a/src/GitTools.Testing/GitTestExtensions.cs b/src/GitTools.Testing/GitTestExtensions.cs new file mode 100644 index 0000000000..c66757746d --- /dev/null +++ b/src/GitTools.Testing/GitTestExtensions.cs @@ -0,0 +1,87 @@ +using System; +using System.IO; +using System.Linq; +using LibGit2Sharp; + +namespace GitTools.Testing +{ + public static class GitTestExtensions + { + static int _pad = 1; + + public static Commit MakeACommit(this IRepository repository, string commitMessage = null) + { + return CreateFileAndCommit(repository, Guid.NewGuid().ToString(), commitMessage); + } + + public static void MergeNoFF(this IRepository repository, string branch) + { + MergeNoFF(repository, branch, Generate.SignatureNow()); + } + + public static void MergeNoFF(this IRepository repository, string branch, Signature sig) + { + repository.Merge(repository.Branches[branch], sig, new MergeOptions + { + FastForwardStrategy = FastForwardStrategy.NoFastForward + }); + } + + public static Commit[] MakeCommits(this IRepository repository, int numCommitsToMake) + { + return Enumerable.Range(1, numCommitsToMake) + .Select(x => repository.MakeACommit()) + .ToArray(); + } + + public static Commit CreateFileAndCommit(this IRepository repository, string relativeFileName, string commitMessage = null) + { + var randomFile = Path.Combine(repository.Info.WorkingDirectory, relativeFileName); + if (File.Exists(randomFile)) + { + File.Delete(randomFile); + } + + var totalWidth = 36 + (_pad++ % 10); + var contents = Guid.NewGuid().ToString().PadRight(totalWidth, '.'); + File.WriteAllText(randomFile, contents); + + Commands.Stage(repository, randomFile); + + return repository.Commit(string.Format("Test Commit for file '{0}' - {1}", relativeFileName, commitMessage), + Generate.SignatureNow(), Generate.SignatureNow()); + } + + public static Tag MakeATaggedCommit(this IRepository repository, string tag) + { + var commit = repository.MakeACommit(); + var existingTag = repository.Tags.SingleOrDefault(t => t.FriendlyName == tag); + if (existingTag != null) + return existingTag; + return repository.Tags.Add(tag, commit); + } + + public static Commit CreatePullRequestRef(this IRepository repository, string from, string to, int prNumber = 2, bool normalise = false, bool allowFastFowardMerge = false) + { + Commands.Checkout(repository, repository.Branches[to].Tip); + if (allowFastFowardMerge) + { + repository.Merge(repository.Branches[from], Generate.SignatureNow()); + } + else + { + repository.MergeNoFF(from); + } + var commit = repository.Head.Tip; + repository.Refs.Add("refs/pull/" + prNumber + "/merge", commit.Id); + Commands.Checkout(repository, to); + if (normalise) + { + // Turn the ref into a real branch + Commands.Checkout(repository, repository.Branches.Add("pull/" + prNumber + "/merge", commit)); + } + + return commit; + } + } +} \ No newline at end of file diff --git a/src/GitTools.Testing/GitTools.Testing.csproj b/src/GitTools.Testing/GitTools.Testing.csproj new file mode 100644 index 0000000000..7d458af68d --- /dev/null +++ b/src/GitTools.Testing/GitTools.Testing.csproj @@ -0,0 +1,21 @@ + + + netstandard2.0 + true + false + bin\$(Configuration)\ + $(DefineConstants);LIBLOG_PORTABLE + + + 1591,414 + + + 1591 + + + + + + + + \ No newline at end of file diff --git a/src/GitTools.Testing/Internal/DirectoryHelper.cs b/src/GitTools.Testing/Internal/DirectoryHelper.cs new file mode 100644 index 0000000000..15d414d5c1 --- /dev/null +++ b/src/GitTools.Testing/Internal/DirectoryHelper.cs @@ -0,0 +1,87 @@ +namespace GitTools.Testing.Internal +{ + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.IO; + + static class DirectoryHelper + { + static readonly Dictionary ToRename = new Dictionary + { + {"gitted", ".git"}, + {"gitmodules", ".gitmodules"}, + }; + + public static void CopyFilesRecursively(DirectoryInfo source, DirectoryInfo target) + { + // From http://stackoverflow.com/questions/58744/best-way-to-copy-the-entire-contents-of-a-directory-in-c/58779#58779 + + foreach (var dir in source.GetDirectories()) + { + CopyFilesRecursively(dir, target.CreateSubdirectory(Rename(dir.Name))); + } + foreach (var file in source.GetFiles()) + { + file.CopyTo(Path.Combine(target.FullName, Rename(file.Name))); + } + } + + static string Rename(string name) + { + return ToRename.ContainsKey(name) ? ToRename[name] : name; + } + + public static void DeleteSubDirectories(string parentPath) + { + var dirs = Directory.GetDirectories(parentPath); + foreach (var dir in dirs) + { + DeleteDirectory(dir); + } + } + + public static void DeleteDirectory(string directoryPath) + { + // From http://stackoverflow.com/questions/329355/cannot-delete-directory-with-directory-deletepath-true/329502#329502 + + if (!Directory.Exists(directoryPath)) + { + Trace.WriteLine( + string.Format("Directory '{0}' is missing and can't be removed.", + directoryPath)); + + return; + } + + var files = Directory.GetFiles(directoryPath); + var dirs = Directory.GetDirectories(directoryPath); + + foreach (var file in files) + { + File.SetAttributes(file, FileAttributes.Normal); + File.Delete(file); + } + + foreach (var dir in dirs) + { + DeleteDirectory(dir); + } + + File.SetAttributes(directoryPath, FileAttributes.Normal); + try + { + Directory.Delete(directoryPath, false); + } + catch (IOException) + { + Trace.WriteLine(string.Format("{0}The directory '{1}' could not be deleted!" + + "{0}Most of the time, this is due to an external process accessing the files in the temporary repositories created during the test runs, and keeping a handle on the directory, thus preventing the deletion of those files." + + "{0}Known and common causes include:" + + "{0}- Windows Search Indexer (go to the Indexing Options, in the Windows Control Panel, and exclude the bin folder of LibGit2Sharp.Tests)" + + "{0}- Antivirus (exclude the bin folder of LibGit2Sharp.Tests from the paths scanned by your real-time antivirus){0}", + Environment.NewLine, Path.GetFullPath(directoryPath))); + } + } + } +} \ No newline at end of file diff --git a/src/GitTools.Testing/Internal/PathHelper.cs b/src/GitTools.Testing/Internal/PathHelper.cs new file mode 100644 index 0000000000..bf7b3830b7 --- /dev/null +++ b/src/GitTools.Testing/Internal/PathHelper.cs @@ -0,0 +1,13 @@ +namespace GitTools.Testing.Internal +{ + using System; + using System.IO; + + static class PathHelper + { + public static string GetTempPath() + { + return Path.Combine(Path.GetTempPath(), "TestRepositories", Guid.NewGuid().ToString()); + } + } +} \ No newline at end of file diff --git a/src/GitTools.Testing/Internal/StringBuilderExtensions.cs b/src/GitTools.Testing/Internal/StringBuilderExtensions.cs new file mode 100644 index 0000000000..3430822032 --- /dev/null +++ b/src/GitTools.Testing/Internal/StringBuilderExtensions.cs @@ -0,0 +1,15 @@ +namespace GitTools.Testing.Internal +{ + using System.Text; + using JetBrains.Annotations; + + static class StringBuilderExtensions + { + [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.Testing/VirtualTime.cs b/src/GitTools.Testing/VirtualTime.cs new file mode 100644 index 0000000000..df2a08786f --- /dev/null +++ b/src/GitTools.Testing/VirtualTime.cs @@ -0,0 +1,25 @@ +namespace GitTools.Testing +{ + using System; + + /// + /// VirtualTime starts at an hour before now, then each time it is called increments by a minute + /// Useful when interacting with git to make sure commits and other interactions are not at the same time + /// + public static class VirtualTime + { + static DateTimeOffset _simulatedTime = DateTimeOffset.Now.AddHours(-1); + + /// + /// Increments by 1 minute each time it is called + /// + public static DateTimeOffset Now + { + get + { + _simulatedTime = _simulatedTime.AddMinutes(1); + return _simulatedTime; + } + } + } +} \ No newline at end of file diff --git a/src/GitVersion.sln b/src/GitVersion.sln index c018de2420..5b4a5291b3 100644 --- a/src/GitVersion.sln +++ b/src/GitVersion.sln @@ -27,7 +27,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution ..\GitVersion.yml = ..\GitVersion.yml ..\LICENSE = ..\LICENSE ..\mkdocs.yml = ..\mkdocs.yml - NuGet.config = NuGet.config ..\README.md = ..\README.md EndProjectSection EndProject @@ -68,6 +67,14 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "DockerBase", "DockerBase", Docker\Mono\DockerBase\readme = Docker\Mono\DockerBase\readme EndProjectSection EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GitTools.Testing", "GitTools.Testing\GitTools.Testing.csproj", "{88BBF57C-AB63-49DF-B9D0-4E59C04F40FF}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GitTools.Testing.Tests", "GitTools.Testing.Tests\GitTools.Testing.Tests.csproj", "{17FBDB23-4E6D-4BFF-8562-EF4663493675}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GitTools.Core", "GitTools.Core\GitTools.Core.csproj", "{CF9522CC-A6DA-4815-ABA7-3D0FC062D4AD}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GitTools.Core.Tests", "GitTools.Core.Tests\GitTools.Core.Tests.csproj", "{7BA346F4-6D8A-4AB8-9D7A-B3920EF4647F}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -98,6 +105,22 @@ Global {F7AC0E71-3E9A-4F6D-B986-E004825A48E1}.Debug|Any CPU.Build.0 = Debug|Any CPU {F7AC0E71-3E9A-4F6D-B986-E004825A48E1}.Release|Any CPU.ActiveCfg = Release|Any CPU {F7AC0E71-3E9A-4F6D-B986-E004825A48E1}.Release|Any CPU.Build.0 = Release|Any CPU + {88BBF57C-AB63-49DF-B9D0-4E59C04F40FF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {88BBF57C-AB63-49DF-B9D0-4E59C04F40FF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {88BBF57C-AB63-49DF-B9D0-4E59C04F40FF}.Release|Any CPU.ActiveCfg = Release|Any CPU + {88BBF57C-AB63-49DF-B9D0-4E59C04F40FF}.Release|Any CPU.Build.0 = Release|Any CPU + {17FBDB23-4E6D-4BFF-8562-EF4663493675}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {17FBDB23-4E6D-4BFF-8562-EF4663493675}.Debug|Any CPU.Build.0 = Debug|Any CPU + {17FBDB23-4E6D-4BFF-8562-EF4663493675}.Release|Any CPU.ActiveCfg = Release|Any CPU + {17FBDB23-4E6D-4BFF-8562-EF4663493675}.Release|Any CPU.Build.0 = Release|Any CPU + {CF9522CC-A6DA-4815-ABA7-3D0FC062D4AD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CF9522CC-A6DA-4815-ABA7-3D0FC062D4AD}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CF9522CC-A6DA-4815-ABA7-3D0FC062D4AD}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CF9522CC-A6DA-4815-ABA7-3D0FC062D4AD}.Release|Any CPU.Build.0 = Release|Any CPU + {7BA346F4-6D8A-4AB8-9D7A-B3920EF4647F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7BA346F4-6D8A-4AB8-9D7A-B3920EF4647F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7BA346F4-6D8A-4AB8-9D7A-B3920EF4647F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7BA346F4-6D8A-4AB8-9D7A-B3920EF4647F}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/src/GitVersion.sln.DotSettings b/src/GitVersion.sln.DotSettings index 4d4feefb78..652dd45b98 100644 --- a/src/GitVersion.sln.DotSettings +++ b/src/GitVersion.sln.DotSettings @@ -134,6 +134,7 @@ ALWAYS_ADD ALWAYS_ADD ALWAYS_ADD + NEVER False False False @@ -543,9 +544,14 @@ II.2.12 <HandlesEvent /> <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> <Policy Inspect="True" Prefix="T" Suffix="" Style="AaBb" /> <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + True True + True + True + True True True + True True True True diff --git a/src/GitVersionCore.Tests/GitVersionCore.Tests.csproj b/src/GitVersionCore.Tests/GitVersionCore.Tests.csproj index 83cde8af1c..7fd26ebd2e 100644 --- a/src/GitVersionCore.Tests/GitVersionCore.Tests.csproj +++ b/src/GitVersionCore.Tests/GitVersionCore.Tests.csproj @@ -2,7 +2,6 @@ net461 - Library GitVersionCore.Tests GitVersionCore.Tests @@ -35,8 +34,6 @@ - - @@ -55,13 +52,9 @@ - - - - Designer - - + + diff --git a/src/GitVersionCore.Tests/app.config b/src/GitVersionCore.Tests/app.config deleted file mode 100644 index 97f26416bc..0000000000 --- a/src/GitVersionCore.Tests/app.config +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - diff --git a/src/GitVersionCore/AssemblyInfo.cs b/src/GitVersionCore/AssemblyInfo.cs index b4a21bb9ee..2af36943de 100644 --- a/src/GitVersionCore/AssemblyInfo.cs +++ b/src/GitVersionCore/AssemblyInfo.cs @@ -10,4 +10,4 @@ [assembly: InternalsVisibleTo("GitVersionCore.Tests")] [assembly: InternalsVisibleTo("GitVersionExe.Tests")] -[assembly: AssemblyInformationalVersion("4.0.0-netstandard.1+1538.Branch.feature/netstandard.Sha.91536c107ba91f755f14904fc69965a9ad70fcb0")] +[assembly: AssemblyInformationalVersion("4.0.0-netstandard2.1+1593.Branch.feature/netstandard2.Sha.dd570beaac2f19d03977ba1ef452d579ac58e784")] diff --git a/src/GitVersionCore/BuildServers/AppVeyor.cs b/src/GitVersionCore/BuildServers/AppVeyor.cs index c430d6ab27..d17a060ac9 100644 --- a/src/GitVersionCore/BuildServers/AppVeyor.cs +++ b/src/GitVersionCore/BuildServers/AppVeyor.cs @@ -3,13 +3,6 @@ using System; using System.Net; using System.Text; -#if !NETDESKTOP - using System.Net.Http; - using System.Threading.Tasks; - using System.Net.Http.Headers; - using Newtonsoft.Json; - using System.IO; -#endif public class AppVeyor : BuildServerBase { @@ -20,97 +13,6 @@ public override bool CanApplyToCurrentContext() return !string.IsNullOrEmpty(Environment.GetEnvironmentVariable(EnvironmentVariableName)); } -#if !NETDESKTOP - - - public override string GenerateSetVersionMessage(VersionVariables variables) - { - - var buildNumber = Environment.GetEnvironmentVariable("APPVEYOR_BUILD_NUMBER"); - var restBase = Environment.GetEnvironmentVariable("APPVEYOR_API_URL"); - - var request = (HttpWebRequest)WebRequest.Create(restBase + "api/build"); - request.Method = "PUT"; - - - var data = string.Format("{{ \"version\": \"{0}.build.{1}\" }}", variables.FullSemVer, buildNumber); - var bytes = Encoding.UTF8.GetBytes(data); - if (request.Headers == null) - { - request.Headers = new WebHeaderCollection(); - } - - var bytesLength = bytes.Length; - //request.Headers["Content-Length"] = bytesLength.ToString(); - // request.ContentLength = bytes.Length; - request.ContentType = "application/json"; - - using (var writeStream = request.GetRequestStreamAsync().Result) - { - writeStream.Write(bytes, 0, bytes.Length); - } - - //var result = request.BeginGetRequestStream((asyncResult) => - // { - // using (var writeStream = request.EndGetRequestStream(asyncResult)) - // { - // writeStream.Write(bytes, 0, bytes.Length); - // } - - // }, null); - - // result.AsyncWaitHandle.WaitOne(new TimeSpan(0, 3, 0)); - - using (var response = (HttpWebResponse)request.GetResponseAsync().Result) - { - if (response.StatusCode != HttpStatusCode.OK && response.StatusCode != HttpStatusCode.NoContent) - { - var message = string.Format("Request failed. Received HTTP {0}", response.StatusCode); - return message; - } - } - - return string.Format("Set AppVeyor build number to '{0}'.", variables.FullSemVer); - - } - - - public override string[] GenerateSetParameterMessage(string name, string value) - { - - // var buildNumber = Environment.GetEnvironmentVariable("APPVEYOR_BUILD_NUMBER"); - var restBase = Environment.GetEnvironmentVariable("APPVEYOR_API_URL"); - - var request = (HttpWebRequest)WebRequest.Create(restBase + "api/build/variables"); - request.Method = "POST"; - request.ContentType = "application/json"; - request.Accept = "application/json"; - - var data = string.Format("{{ \"name\": \"GitVersion_{0}\", \"value\": \"{1}\" }}", name, value); - var bytes = Encoding.UTF8.GetBytes(data); - if (request.Headers == null) - { - request.Headers = new WebHeaderCollection(); - } - var bytesLength = bytes.Length; - // No property for content-length - and no Add() method on header collection? WHAAAAT - // request.ContentLength = bytes.Length; - //request.Headers["Content-Length"] = bytesLength.ToString(); - - using (var writeStream = request.GetRequestStreamAsync().Result) - { - writeStream.Write(bytes, 0, bytes.Length); - } - - return new[] - { - string.Format("Adding Environment Variable. name='GitVersion_{0}' value='{1}']", name, value) - }; - } - - -#else - public override string GenerateSetVersionMessage(VersionVariables variables) { var buildNumber = Environment.GetEnvironmentVariable("APPVEYOR_BUILD_NUMBER"); @@ -166,8 +68,5 @@ public override string[] GenerateSetParameterMessage(string name, string value) }; } - -#endif - } } \ No newline at end of file diff --git a/src/GitVersionCore/BuildServers/BuildServerList.cs b/src/GitVersionCore/BuildServers/BuildServerList.cs index 87751cbbf4..dcd4f6f5a8 100644 --- a/src/GitVersionCore/BuildServers/BuildServerList.cs +++ b/src/GitVersionCore/BuildServers/BuildServerList.cs @@ -7,9 +7,7 @@ public static class BuildServerList { static List BuildServers = new List { -#if NETDESKTOP new ContinuaCi(), -#endif new TeamCity(), new AppVeyor(), new MyGet(), diff --git a/src/GitVersionCore/Extensions/ReadEmbeddedResourceExtensions.cs b/src/GitVersionCore/Extensions/ReadEmbeddedResourceExtensions.cs index 4f5ff14587..3cc29a9243 100644 --- a/src/GitVersionCore/Extensions/ReadEmbeddedResourceExtensions.cs +++ b/src/GitVersionCore/Extensions/ReadEmbeddedResourceExtensions.cs @@ -1,7 +1,6 @@ namespace GitVersionCore.Extensions { using System.IO; - using System.Reflection; public static class ReadEmbeddedResourceExtensions { @@ -24,11 +23,8 @@ public static string ReadAsStringFromEmbeddedResource(this string resourceNam public static Stream ReadFromEmbeddedResource(this string resourceName) { -#if NETDESKTOP var assembly = typeof(T).Assembly; -#else - var assembly = typeof(T).GetTypeInfo().Assembly; -#endif + return assembly.GetManifestResourceStream(resourceName); } } diff --git a/src/GitVersionCore/Extensions/TaskHelper.cs b/src/GitVersionCore/Extensions/TaskHelper.cs deleted file mode 100644 index 92cffec0b7..0000000000 --- a/src/GitVersionCore/Extensions/TaskHelper.cs +++ /dev/null @@ -1,29 +0,0 @@ -namespace System.Threading.Tasks -{ - public static class TaskHelper - { - -#if NETDESKTOP - public static Task Delay(int milliseconds) - { - var tcs = new TaskCompletionSource(); - System.Timers.Timer timer = new System.Timers.Timer(); - timer.Elapsed += (obj, args) => - { - tcs.TrySetResult(true); - }; - timer.Interval = (double)milliseconds; - timer.AutoReset = false; - timer.Start(); - return tcs.Task; - } -#else - public static Task Delay(int milliseconds) - { - return Task.Delay(milliseconds); - } -#endif - } - - -} diff --git a/src/GitVersionCore/GitVersionCore.csproj b/src/GitVersionCore/GitVersionCore.csproj index 6cd93c3472..933ee440b4 100644 --- a/src/GitVersionCore/GitVersionCore.csproj +++ b/src/GitVersionCore/GitVersionCore.csproj @@ -1,7 +1,7 @@  - netstandard1.3;net40 + netstandard2.0 Library GitVersion GitVersionCore @@ -27,16 +27,9 @@ false false true + TRACE; - - - TRACE;NET40;NETDESKTOP - - - - TRACE;NETSTANDARD1_3; - - + full false @@ -53,38 +46,13 @@ bin\Release\GitVersionCore.xml - - - - - - - - - - - - - - - - - - All - - - - - - + + + - - - - - + - + @@ -100,10 +68,13 @@ - Designer + + + + diff --git a/src/GitVersionCore/Helpers/EncodingHelper.cs b/src/GitVersionCore/Helpers/EncodingHelper.cs index 8337246f67..9b6f9e3cd3 100644 --- a/src/GitVersionCore/Helpers/EncodingHelper.cs +++ b/src/GitVersionCore/Helpers/EncodingHelper.cs @@ -73,8 +73,6 @@ public static Encoding DetectEncoding(IList bytes) /// An ordered list of encodings and corresponding preambles. private static void ScanEncodings() { - // Might be out of luck finding a replacement for GetEncodings for netcore 1: https://stackoverflow.com/questions/44351507/what-is-net-core-equivalent-of-encoding-getencodings -#if NETDESKTOP var encodings = (Encoding.GetEncodings()); EncodingsWithPreambles = (from info in encodings let encoding = info.GetEncoding() @@ -83,20 +81,6 @@ where preamble.Length > 0 orderby preamble.Length descending select encoding).ToList(); -#else - // GetEncodings not available on netstandard, so just manually adding some common ones. - var encodings = new List() { Encoding.ASCII, Encoding.Unicode, Encoding.UTF32, Encoding.UTF7, Encoding.UTF8 }; - EncodingsWithPreambles = (from info in encodings - let preamble = info.GetPreamble() - where preamble.Length > 0 - orderby preamble.Length descending - select info).ToList(); -#endif - - - - - var encodingWithLongestPreamble = EncodingsWithPreambles.FirstOrDefault(); MaxPreambleLength = encodingWithLongestPreamble == null ? 0 : encodingWithLongestPreamble.GetPreamble().Length; } diff --git a/src/GitVersionCore/Helpers/EnvironmentHelper.cs b/src/GitVersionCore/Helpers/EnvironmentHelper.cs index 9c955dae23..84d35afe0d 100644 --- a/src/GitVersionCore/Helpers/EnvironmentHelper.cs +++ b/src/GitVersionCore/Helpers/EnvironmentHelper.cs @@ -6,11 +6,7 @@ public class EnvironmentHelper { public static string GetEnvironmentVariableForProcess(string envVar) { -#if NETDESKTOP return Environment.GetEnvironmentVariable(envVar, EnvironmentVariableTarget.Process); -#else - return Environment.GetEnvironmentVariable(envVar); -#endif } } } diff --git a/src/GitVersionCore/Helpers/ThreadSleep.cs b/src/GitVersionCore/Helpers/ThreadSleep.cs index f293358199..78bb4a66cf 100644 --- a/src/GitVersionCore/Helpers/ThreadSleep.cs +++ b/src/GitVersionCore/Helpers/ThreadSleep.cs @@ -6,7 +6,7 @@ internal class ThreadSleep : IThreadSleep { public async Task SleepAsync(int milliseconds) { - await TaskHelper.Delay(milliseconds); + await Task.Delay(milliseconds); } } } diff --git a/src/GitVersionCore/OutputVariables/VersionVariables.cs b/src/GitVersionCore/OutputVariables/VersionVariables.cs index d5c4ec0d2e..5a343edb98 100644 --- a/src/GitVersionCore/OutputVariables/VersionVariables.cs +++ b/src/GitVersionCore/OutputVariables/VersionVariables.cs @@ -118,19 +118,9 @@ public static IEnumerable AvailableVariables [ReflectionIgnore] public string this[string variable] { - - get { -#if NETDESKTOP return typeof(VersionVariables).GetProperty(variable).GetValue(this, null) as string; -#else - throw new NotImplementedException(); - // return typeof(VersionVariables).GetTypeInfo().GetProperty(variable).GetValue(this, null) as string; -#endif - - - } } @@ -190,12 +180,7 @@ public bool TryGetValue(string variable, out string variableValue) public bool ContainsKey(string variable) { -#if NETDESKTOP return typeof(VersionVariables).GetProperty(variable) != null; -#else - throw new NotImplementedException(); - // return typeof(VersionVariables).GetTypeInfo().GetProperty(variable) != null; -#endif } sealed class ReflectionIgnoreAttribute : Attribute diff --git a/src/GitVersionCore/Properties/PublishProfiles/FolderProfile.pubxml b/src/GitVersionCore/Properties/PublishProfiles/FolderProfile.pubxml deleted file mode 100644 index 54e7a81f71..0000000000 --- a/src/GitVersionCore/Properties/PublishProfiles/FolderProfile.pubxml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - FileSystem - Release - netstandard1.3 - bin\Release\PublishOutput - - \ No newline at end of file diff --git a/src/GitVersionCore/SearchPath.cs b/src/GitVersionCore/SearchPath.cs index 1fd9535324..9910fa1f8f 100644 --- a/src/GitVersionCore/SearchPath.cs +++ b/src/GitVersionCore/SearchPath.cs @@ -22,21 +22,7 @@ public static void SetSearchPath(string addinDirectoryPath) static string GetProcessorArchitecture() { -#if NETDESKTOP - var is64 = Environment.Is64BitProcess; -#else - var arch = System.Runtime.InteropServices.RuntimeInformation.OSArchitecture; - bool is64 = (arch == System.Runtime.InteropServices.Architecture.X64 || arch == System.Runtime.InteropServices.Architecture.Arm64); - if (arch == System.Runtime.InteropServices.Architecture.X64) - { - return "X64"; - } -#endif - if (is64) - { - return "amd64"; - } - return "x86"; + return Environment.Is64BitProcess ? "amd64" : "x86"; } } } \ No newline at end of file diff --git a/src/GitVersionCore/StringComparerUtils.cs b/src/GitVersionCore/StringComparerUtils.cs index d61d0ab2fe..8b3e491708 100644 --- a/src/GitVersionCore/StringComparerUtils.cs +++ b/src/GitVersionCore/StringComparerUtils.cs @@ -4,16 +4,8 @@ namespace GitVersion { public static class StringComparerUtils { -#if NETDESKTOP public static readonly System.StringComparer IngoreCaseComparer = StringComparer.InvariantCultureIgnoreCase; public static readonly StringComparison IngoreCaseComparison = StringComparison.InvariantCultureIgnoreCase; public static readonly StringComparison CaseSensitiveComparison = StringComparison.InvariantCulture; -#else - public static readonly System.StringComparer IngoreCaseComparer = StringComparer.OrdinalIgnoreCase; - public static readonly StringComparison IngoreCaseComparison = StringComparison.OrdinalIgnoreCase; - public static readonly StringComparison CaseSensitiveComparison = StringComparison.Ordinal; -#endif } - - } diff --git a/src/GitVersionCore/StringFormatWith.cs b/src/GitVersionCore/StringFormatWith.cs index f10232b033..6442b6c6d4 100644 --- a/src/GitVersionCore/StringFormatWith.cs +++ b/src/GitVersionCore/StringFormatWith.cs @@ -103,15 +103,8 @@ static Func CompileDataBinder(Type type, string expr) private static MethodInfo GetMethodInfo(string name, BindingFlags bindingFlags, Type[] types) { -#if NETDESKTOP var methodInfo = typeof(Convert).GetMethod(name, bindingFlags, null, types, null); return methodInfo; -#else - var type = typeof(Convert); - var methodInfo = typeof(Convert).GetMethod(name, types); - return methodInfo; -#endif - //throw new NotImplementedException(); } } } \ No newline at end of file diff --git a/src/GitVersionCore/TemplateManager.cs b/src/GitVersionCore/TemplateManager.cs index 2613dc282d..ab976a56f2 100644 --- a/src/GitVersionCore/TemplateManager.cs +++ b/src/GitVersionCore/TemplateManager.cs @@ -73,12 +73,7 @@ public bool IsSupported(string fileExtension) static IEnumerable GetEmbeddedTemplates(TemplateType templateType, string templateCategory) { - Assembly assy = null; -#if NETDESKTOP - assy = typeof(TemplateManager).Assembly; -#else - assy = typeof(TemplateManager).GetTypeInfo().Assembly; -#endif + Assembly assy = typeof(TemplateManager).Assembly; foreach (var name in assy.GetManifestResourceNames()) { diff --git a/src/GitVersionCore/app.config b/src/GitVersionCore/app.config deleted file mode 100644 index 5fafb00cde..0000000000 --- a/src/GitVersionCore/app.config +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/src/GitVersionExe.Tests/GitVersionExe.Tests.csproj b/src/GitVersionExe.Tests/GitVersionExe.Tests.csproj index d2a5c3aa2a..fadd3e270f 100644 --- a/src/GitVersionExe.Tests/GitVersionExe.Tests.csproj +++ b/src/GitVersionExe.Tests/GitVersionExe.Tests.csproj @@ -38,8 +38,6 @@ - - @@ -60,11 +58,12 @@ + + - diff --git a/src/GitVersionExe.Tests/app.config b/src/GitVersionExe.Tests/app.config deleted file mode 100644 index 2f21c15a6d..0000000000 --- a/src/GitVersionExe.Tests/app.config +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - - - - - - - diff --git a/src/GitVersionExe/ArgumentParser.cs b/src/GitVersionExe/ArgumentParser.cs index a4fec8f437..978a9f9c6f 100644 --- a/src/GitVersionExe/ArgumentParser.cs +++ b/src/GitVersionExe/ArgumentParser.cs @@ -128,7 +128,6 @@ public static Arguments ParseArguments(List commandLineArguments) continue; } -#if NETDESKTOP if (name.IsSwitch("exec")) { EnsureArgumentValueCount(values); @@ -168,9 +167,6 @@ public static Arguments ParseArguments(List commandLineArguments) continue; } -#endif - - if (name.IsSwitch("updateAssemblyInfo")) { if (value.IsTrue()) diff --git a/src/GitVersionExe/Arguments.cs b/src/GitVersionExe/Arguments.cs index ad21176a46..fb0871b43b 100644 --- a/src/GitVersionExe/Arguments.cs +++ b/src/GitVersionExe/Arguments.cs @@ -26,21 +26,19 @@ public Arguments() public string DynamicRepositoryLocation; public bool Init; -#if NETDESKTOP public bool Diag; -#endif public bool IsVersion; public bool IsHelp; public string LogFilePath; public string ShowVariable; public OutputType Output; -#if NETDESKTOP + public string Proj; public string ProjArgs; public string Exec; public string ExecArgs; -#endif + public bool UpdateAssemblyInfo; public ISet UpdateAssemblyInfoFileName; public bool EnsureAssemblyInfo; diff --git a/src/GitVersionExe/AssemblyInfo.cs b/src/GitVersionExe/AssemblyInfo.cs index 8830673801..82773b1ecf 100644 --- a/src/GitVersionExe/AssemblyInfo.cs +++ b/src/GitVersionExe/AssemblyInfo.cs @@ -9,4 +9,4 @@ [assembly: InternalsVisibleTo("AcceptanceTests")] [assembly: InternalsVisibleTo("GitVersionExe.Tests")] -[assembly: AssemblyInformationalVersion("4.0.0-netstandard.1+1538.Branch.feature/netstandard.Sha.91536c107ba91f755f14904fc69965a9ad70fcb0")] +[assembly: AssemblyInformationalVersion("4.0.0-netstandard2.1+1593.Branch.feature/netstandard2.Sha.dd570beaac2f19d03977ba1ef452d579ac58e784")] diff --git a/src/GitVersionExe/GitVersion.Tool.csproj b/src/GitVersionExe/GitVersion.Tool.csproj new file mode 100644 index 0000000000..05f12dfd9c --- /dev/null +++ b/src/GitVersionExe/GitVersion.Tool.csproj @@ -0,0 +1,14 @@ + + + true + netcoreapp2.1 + dotnet-gitversion + + + + + + GitVersion.CommandLine.DotNetCore.Tool + + + diff --git a/src/GitVersionExe/GitVersionExe.csproj b/src/GitVersionExe/GitVersionExe.csproj index 96ad53b903..cfa374c5cd 100644 --- a/src/GitVersionExe/GitVersionExe.csproj +++ b/src/GitVersionExe/GitVersionExe.csproj @@ -1,4 +1,4 @@ - + Debug AnyCPU @@ -6,9 +6,9 @@ Exe GitVersion GitVersion - net40;netcoreapp20 + net461;netcoreapp2.0 $(SolutionDir)..\build\ - + false false false @@ -16,14 +16,14 @@ false true - + true full false bin\Debug\ DEBUG;TRACE - + bin\Debug\GitVersion.xml 1591 @@ -32,21 +32,16 @@ true bin\Release\ TRACE - + bin\Release\GitVersion.xml 1591 AnyCPU - - NET40;NETDESKTOP - - - - - - - + + + + @@ -58,7 +53,6 @@ - @@ -79,13 +73,14 @@ - - - + + + - + + - + @@ -124,7 +119,7 @@ - + @@ -167,5 +162,5 @@ - - \ No newline at end of file + + diff --git a/src/GitVersionExe/Program.cs b/src/GitVersionExe/Program.cs index 540d71d24f..f64db7c623 100644 --- a/src/GitVersionExe/Program.cs +++ b/src/GitVersionExe/Program.cs @@ -70,23 +70,21 @@ static int VerifyArgumentsAndRun() HelpWriter.Write(); return 0; } -#if NETDESKTOP + if (arguments.Diag) { arguments.NoCache = true; arguments.Output = OutputType.BuildServer; } -#endif ConfigureLogging(arguments); -#if NETDESKTOP if (arguments.Diag) { Logger.WriteInfo("Dumping commit graph: "); GitTools.LibGitExtensions.DumpGraph(arguments.TargetPath, Logger.WriteInfo, 100); } -#endif + if (!Directory.Exists(arguments.TargetPath)) { Logger.WriteWarning(string.Format("The working directory '{0}' does not exist.", arguments.TargetPath)); @@ -108,12 +106,11 @@ static int VerifyArgumentsAndRun() return 0; } -#if NETDESKTOP if (!string.IsNullOrEmpty(arguments.Proj) || !string.IsNullOrEmpty(arguments.Exec)) { arguments.Output = OutputType.BuildServer; } -#endif + SpecifiedArgumentRunner.Run(arguments, fileSystem); } catch (WarningException exception) @@ -135,9 +132,7 @@ static int VerifyArgumentsAndRun() try { -#if NETDESKTOP GitTools.LibGitExtensions.DumpGraph(arguments.TargetPath, Logger.WriteInfo, 100); -#endif } catch (Exception dumpGraphException) { diff --git a/src/GitVersionExe/SpecifiedArgumentRunner.cs b/src/GitVersionExe/SpecifiedArgumentRunner.cs index b4876b2022..357853326b 100644 --- a/src/GitVersionExe/SpecifiedArgumentRunner.cs +++ b/src/GitVersionExe/SpecifiedArgumentRunner.cs @@ -64,10 +64,10 @@ public static void Run(Arguments arguments, IFileSystem fileSystem) } var execRun = false; var msbuildRun = false; -#if NETDESKTOP + execRun = RunExecCommandIfNeeded(arguments, targetPath, variables); msbuildRun = RunMsBuildIfNeeded(arguments, targetPath, variables); -#endif + if (!execRun && !msbuildRun) { assemblyInfoUpdater.CommitChanges(); @@ -82,7 +82,6 @@ public static void Run(Arguments arguments, IFileSystem fileSystem) } } -#if NETDESKTOP static bool RunMsBuildIfNeeded(Arguments args, string workingDirectory, VersionVariables variables) { @@ -118,8 +117,6 @@ static bool RunExecCommandIfNeeded(Arguments args, string workingDirectory, Vers return true; } -#endif - static KeyValuePair[] GetEnvironmentalVariables(VersionVariables variables) { return variables diff --git a/src/GitVersionExe/app.config b/src/GitVersionExe/app.config deleted file mode 100644 index 87ccfc557c..0000000000 --- a/src/GitVersionExe/app.config +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/src/GitVersionTask.Tests/GitVersionTask.Tests.csproj b/src/GitVersionTask.Tests/GitVersionTask.Tests.csproj index f60db5e352..151b6016bb 100644 --- a/src/GitVersionTask.Tests/GitVersionTask.Tests.csproj +++ b/src/GitVersionTask.Tests/GitVersionTask.Tests.csproj @@ -41,12 +41,11 @@ - - + @@ -70,15 +69,12 @@ - - - - + diff --git a/src/GitVersionTask.Tests/app.config b/src/GitVersionTask.Tests/app.config deleted file mode 100644 index c39d7cb3ce..0000000000 --- a/src/GitVersionTask.Tests/app.config +++ /dev/null @@ -1,23 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/GitVersionTask/GitVersionTask.csproj b/src/GitVersionTask/GitVersionTask.csproj index 8da0bd549f..d8202886e6 100644 --- a/src/GitVersionTask/GitVersionTask.csproj +++ b/src/GitVersionTask/GitVersionTask.csproj @@ -9,7 +9,7 @@ Library GitVersionTask GitVersionTask - net461;netstandard1.5 + net461;netstandard2.0 GitVersionTask @@ -29,7 +29,7 @@ NugetAssets\GitVersionTask.nuspec 0.0.1-alpha-0001 - version=$(PackageVersion);configuration=$(Configuration);utilpackversion=$(PackageVersion_UtilPackNuGetMSBuild);gittoolscoreversion=$(PackageVersion_GitToolsCore);yamldotnetversion=$(PackageVersion_YamlDotNet) + version=$(PackageVersion);configuration=$(Configuration);utilpackversion=$(PackageVersion_UtilPackNuGetMSBuild);yamldotnetversion=$(PackageVersion_YamlDotNet) @@ -73,9 +73,6 @@ - - All - All @@ -96,11 +93,11 @@ - + diff --git a/src/GitVersionTask/NugetAssets/GitVersionTask.nuspec b/src/GitVersionTask/NugetAssets/GitVersionTask.nuspec index 160e456e42..269fc2ca1d 100644 --- a/src/GitVersionTask/NugetAssets/GitVersionTask.nuspec +++ b/src/GitVersionTask/NugetAssets/GitVersionTask.nuspec @@ -19,9 +19,8 @@ - + - diff --git a/src/GitVersionTask/app.config b/src/GitVersionTask/app.config deleted file mode 100644 index a01b86a142..0000000000 --- a/src/GitVersionTask/app.config +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/src/NuGet.config b/src/NuGet.Config similarity index 66% rename from src/NuGet.config rename to src/NuGet.Config index 20d8e217a4..3f0e003403 100644 --- a/src/NuGet.config +++ b/src/NuGet.Config @@ -2,6 +2,5 @@ - \ No newline at end of file