From 11a7b7d36239a7de03a8b856ee24be7e90bc9a5b Mon Sep 17 00:00:00 2001 From: Geert van Horrik Date: Sun, 5 Oct 2014 15:38:28 +0200 Subject: [PATCH 1/3] Improved support for dynamic repositories --- GitVersion.sln.GhostDoc.xml | 1 + GitVersionCore/BuildServers/GitHelper.cs | 18 +++- .../{ => Extensions}/ExtensionMethods.cs | 2 +- .../Extensions/ExtensionMethods.git.cs | 86 +++++++++++++++++++ GitVersionCore/GitFlow/BranchClassifier.cs | 33 +++++-- .../GitHubFlow/GitHubFlowVersionFinder.cs | 2 +- GitVersionCore/GitVersionCore.csproj | 5 +- GitVersionCore/LibGitExtensions.cs | 5 +- GitVersionCore/SemanticVersion.cs | 7 ++ GitVersionExe/GitPreparer.cs | 67 ++++++++++++--- 10 files changed, 197 insertions(+), 29 deletions(-) rename GitVersionCore/{ => Extensions}/ExtensionMethods.cs (98%) create mode 100644 GitVersionCore/Extensions/ExtensionMethods.git.cs diff --git a/GitVersion.sln.GhostDoc.xml b/GitVersion.sln.GhostDoc.xml index f671bde190..81bf4da447 100644 --- a/GitVersion.sln.GhostDoc.xml +++ b/GitVersion.sln.GhostDoc.xml @@ -23,6 +23,7 @@ false false true + true false diff --git a/GitVersionCore/BuildServers/GitHelper.cs b/GitVersionCore/BuildServers/GitHelper.cs index f434efaca7..7616126c69 100644 --- a/GitVersionCore/BuildServers/GitHelper.cs +++ b/GitVersionCore/BuildServers/GitHelper.cs @@ -73,13 +73,25 @@ public static bool LooksLikeAValidPullRequestNumber(string issueNumber) public static string ExtractIssueNumber(string mergeMessage) { + // Dynamic: refs/heads/pr/5 // Github Message: refs/heads/pull/5/merge // Stash Message: refs/heads/pull-requests/5/merge - var regex = new Regex(MergeMessageRegexPattern); - var match = regex.Match(mergeMessage); + // Note by @GeertvanHorrik: sorry, I suck at regex so did a quick hack, feel free to replace by regex + if (mergeMessage.Contains("refs/heads/pr/")) + { + var issueNumber = mergeMessage.Replace("refs/heads/pr/", string.Empty); + return issueNumber; + } + else + { + var regex = new Regex(MergeMessageRegexPattern); + var match = regex.Match(mergeMessage); - return match.Groups["issuenumber"].Value; + var issueNumber = match.Groups["issuenumber"].Value; + + return issueNumber; + } } static void AddMissingRefSpecs(Repository repo, Remote remote) diff --git a/GitVersionCore/ExtensionMethods.cs b/GitVersionCore/Extensions/ExtensionMethods.cs similarity index 98% rename from GitVersionCore/ExtensionMethods.cs rename to GitVersionCore/Extensions/ExtensionMethods.cs index dc658d7928..55c36d2381 100644 --- a/GitVersionCore/ExtensionMethods.cs +++ b/GitVersionCore/Extensions/ExtensionMethods.cs @@ -6,7 +6,7 @@ namespace GitVersion using System.Text.RegularExpressions; using JetBrains.Annotations; - static class ExtensionMethods + static partial class ExtensionMethods { public static bool IsOdd(this int number) { diff --git a/GitVersionCore/Extensions/ExtensionMethods.git.cs b/GitVersionCore/Extensions/ExtensionMethods.git.cs new file mode 100644 index 0000000000..9fb298023c --- /dev/null +++ b/GitVersionCore/Extensions/ExtensionMethods.git.cs @@ -0,0 +1,86 @@ +namespace GitVersion +{ + using System; + + static partial class ExtensionMethods + { + public static string GetCanonicalBranchName(this string branchName) + { + if (branchName.IsPullRequest()) + { + branchName = branchName.Replace("pull-requests", "pull"); + branchName = branchName.Replace("pr", "pull"); + + return string.Format("refs/{0}/head", branchName); + } + + return string.Format("refs/heads/{0}", branchName); + } + + public static bool IsHotfix(this string branchName) + { + return branchName.StartsWith("hotfix-") || branchName.StartsWith("hotfix/"); + } + + public static string GetHotfixSuffix(this string branchName) + { + return branchName.TrimStart("hotfix-").TrimStart("hotfix/"); + } + + public static bool IsRelease(this string branchName) + { + return branchName.StartsWith("release-") || branchName.StartsWith("release/"); + } + + public static string GetReleaseSuffix(this string branchName) + { + return branchName.TrimStart("release-").TrimStart("release/"); + } + + public static string GetUnknownBranchSuffix(this string branchName) + { + var unknownBranchSuffix = branchName.Split('-', '/'); + if (unknownBranchSuffix.Length == 1) + return branchName; + return unknownBranchSuffix[1]; + } + + public static string GetSuffix(this string branchName, BranchType branchType) + { + switch (branchType) + { + case BranchType.Hotfix: + return branchName.GetHotfixSuffix(); + + case BranchType.Release: + return branchName.GetReleaseSuffix(); + + case BranchType.Unknown: + return branchName.GetUnknownBranchSuffix(); + + default: + throw new NotSupportedException(string.Format("Unexpected branch type {0}.", branchType)); + } + } + + public static bool IsDevelop(this string branchName) + { + return branchName == "develop"; + } + + public static bool IsMaster(this string branchName) + { + return branchName == "master"; + } + + public static bool IsPullRequest(this string branchName) + { + return branchName.Contains("pull/") || branchName.Contains("pull-requests/") || branchName.Contains("pr/"); + } + + public static bool IsSupport(this string branchName) + { + return branchName.ToLower().StartsWith("support-"); + } + } +} diff --git a/GitVersionCore/GitFlow/BranchClassifier.cs b/GitVersionCore/GitFlow/BranchClassifier.cs index de728a6951..5c7c3c7101 100644 --- a/GitVersionCore/GitFlow/BranchClassifier.cs +++ b/GitVersionCore/GitFlow/BranchClassifier.cs @@ -4,35 +4,54 @@ namespace GitVersion static class BranchClassifier { - public static bool IsHotfix(this Branch branch) { - return branch.Name.StartsWith("hotfix-") || branch.Name.StartsWith("hotfix/"); + return branch.Name.IsHotfix(); + } + + public static string GetHotfixSuffix(this Branch branch) + { + return branch.Name.GetHotfixSuffix(); } public static bool IsRelease(this Branch branch) { - return branch.Name.StartsWith("release-") || branch.Name.StartsWith("release/"); + return branch.Name.IsRelease(); + } + + public static string GetReleaseSuffix(this Branch branch) + { + return branch.Name.GetReleaseSuffix(); + } + + public static string GetUnknownBranchSuffix(this Branch branch) + { + return branch.Name.GetUnknownBranchSuffix(); + } + + public static string GetSuffix(this Branch branch, BranchType branchType) + { + return branch.CanonicalName.GetSuffix(branchType); } public static bool IsDevelop(this Branch branch) { - return branch.Name == "develop"; + return branch.Name.IsDevelop(); } public static bool IsMaster(this Branch branch) { - return branch.Name == "master"; + return branch.Name.IsMaster(); } public static bool IsPullRequest(this Branch branch) { - return branch.CanonicalName.Contains("/pull/") || branch.CanonicalName.Contains("/pull-requests/"); + return branch.CanonicalName.IsPullRequest(); } public static bool IsSupport(this Branch branch) { - return branch.Name.ToLower().StartsWith("support-"); + return branch.Name.IsSupport(); } } } diff --git a/GitVersionCore/GitHubFlow/GitHubFlowVersionFinder.cs b/GitVersionCore/GitHubFlow/GitHubFlowVersionFinder.cs index 66beb34564..eeaedb16b2 100644 --- a/GitVersionCore/GitHubFlow/GitHubFlowVersionFinder.cs +++ b/GitVersionCore/GitHubFlow/GitHubFlowVersionFinder.cs @@ -4,7 +4,7 @@ public class GitHubFlowVersionFinder { public SemanticVersion FindVersion(GitVersionContext context) { - var repositoryDirectory = context.Repository.Info.WorkingDirectory; + var repositoryDirectory = context.Repository.GetRepositoryDirectory(); var lastTaggedReleaseFinder = new LastTaggedReleaseFinder(context); var nextVersionTxtFileFinder = new NextVersionTxtFileFinder(repositoryDirectory); var nextSemverCalculator = new NextSemverCalculator(nextVersionTxtFileFinder, lastTaggedReleaseFinder, context); diff --git a/GitVersionCore/GitVersionCore.csproj b/GitVersionCore/GitVersionCore.csproj index f89842d377..e3073e75f3 100644 --- a/GitVersionCore/GitVersionCore.csproj +++ b/GitVersionCore/GitVersionCore.csproj @@ -63,13 +63,15 @@ + + - + @@ -110,7 +112,6 @@ - diff --git a/GitVersionCore/LibGitExtensions.cs b/GitVersionCore/LibGitExtensions.cs index 7bc4f92f89..2385fbeb05 100644 --- a/GitVersionCore/LibGitExtensions.cs +++ b/GitVersionCore/LibGitExtensions.cs @@ -61,15 +61,16 @@ public static bool IsDetachedHead(this Branch branch) return branch.CanonicalName.Equals("(no branch)", StringComparison.OrdinalIgnoreCase); } - public static string GetRepositoryDirectory(this IRepository repository) + public static string GetRepositoryDirectory(this IRepository repository, bool omitGitPostFix = true) { var gitDirectory = repository.Info.Path; gitDirectory = gitDirectory.TrimEnd('\\'); - if (gitDirectory.EndsWith(".git")) + if (omitGitPostFix && gitDirectory.EndsWith(".git")) { gitDirectory = gitDirectory.Substring(0, gitDirectory.Length - ".git".Length); + gitDirectory = gitDirectory.TrimEnd('\\'); } return gitDirectory; diff --git a/GitVersionCore/SemanticVersion.cs b/GitVersionCore/SemanticVersion.cs index a1f1bd83f6..578f672ae5 100644 --- a/GitVersionCore/SemanticVersion.cs +++ b/GitVersionCore/SemanticVersion.cs @@ -5,6 +5,8 @@ namespace GitVersion public class SemanticVersion : IFormattable, IComparable { + public static SemanticVersion Empty = new SemanticVersion(); + static Regex ParseSemVer = new Regex( @"[vV]?(?(?\d+)(\.(?\d+))?(\.(?\d+))?)(\.(?\d+))?(-(?[^\+]*))?(\+(?.*))?", RegexOptions.Compiled); @@ -34,6 +36,11 @@ public bool Equals(SemanticVersion obj) BuildMetaData == obj.BuildMetaData; } + public bool IsEmpty() + { + return Equals(Empty); + } + public static bool operator ==(SemanticVersion v1, SemanticVersion v2) { if (ReferenceEquals(v1, null)) diff --git a/GitVersionExe/GitPreparer.cs b/GitVersionExe/GitPreparer.cs index fa1ea63394..1cab401a5e 100644 --- a/GitVersionExe/GitPreparer.cs +++ b/GitVersionExe/GitPreparer.cs @@ -1,6 +1,7 @@ namespace GitVersion { using System.IO; + using System.Linq; using LibGit2Sharp; public class GitPreparer @@ -33,12 +34,13 @@ public string Prepare() string GetGitInfoFromUrl() { - var gitDirectory = Path.Combine(arguments.TargetPath, "_dynamicrepository", ".git"); - if (Directory.Exists(gitDirectory)) + var gitRootDirectory = Path.Combine(arguments.TargetPath, "_dynamicrepository"); + var gitDirectory = Path.Combine(gitRootDirectory, ".git"); + if (Directory.Exists(gitRootDirectory)) { - Logger.WriteInfo(string.Format("Deleting existing .git folder from '{0}' to force new checkout from url", gitDirectory)); + Logger.WriteInfo(string.Format("Deleting existing .git folder from '{0}' to force new checkout from url", gitRootDirectory)); - DeleteHelper.DeleteGitRepository(gitDirectory); + DeleteHelper.DeleteGitRepository(gitRootDirectory); } Credentials credentials = null; @@ -48,16 +50,21 @@ string GetGitInfoFromUrl() Logger.WriteInfo(string.Format("Setting up credentials using name '{0}'", authentication.Username)); credentials = new UsernamePasswordCredentials - { - Username = authentication.Username, - Password = authentication.Password + { + Username = authentication.Username, + Password = authentication.Password }; } Logger.WriteInfo(string.Format("Retrieving git info from url '{0}'", arguments.TargetUrl)); - Repository.Clone(arguments.TargetUrl, gitDirectory, - new CloneOptions { IsBare = true, Checkout = false, Credentials = credentials}); + Repository.Clone(arguments.TargetUrl, gitDirectory, + new CloneOptions + { + IsBare = true, + Checkout = false, + CredentialsProvider = (url, usernameFromUrl, types) => credentials + }); if (!string.IsNullOrWhiteSpace(arguments.TargetBranch)) { @@ -66,12 +73,31 @@ string GetGitInfoFromUrl() using (var repository = new Repository(gitDirectory)) { - var targetBranchName = string.Format("refs/heads/{0}", arguments.TargetBranch); - if (!string.Equals(repository.Head.CanonicalName, targetBranchName)) + Reference newHead = null; + + var localReference = GetLocalReference(repository, arguments.TargetBranch); + if (localReference != null) { - Logger.WriteInfo(string.Format("Switching to branch '{0}'", arguments.TargetBranch)); + newHead = localReference; + } - repository.Refs.UpdateTarget("HEAD", targetBranchName); + if (newHead == null) + { + var remoteReference = GetRemoteReference(repository, arguments.TargetBranch, arguments.TargetUrl); + if (remoteReference != null) + { + repository.Network.Fetch(arguments.TargetUrl, new[] + { + string.Format("{0}:{1}", remoteReference.CanonicalName, arguments.TargetBranch) + }); + + newHead = repository.Refs[string.Format("refs/heads/{0}", arguments.TargetBranch)]; + } + } + + if (newHead != null) + { + repository.Refs.UpdateTarget(repository.Refs.Head, newHead); } repository.CheckoutFilesIfExist("NextVersion.txt"); @@ -82,5 +108,20 @@ string GetGitInfoFromUrl() return gitDirectory; } + + private static Reference GetLocalReference(Repository repository, string branchName) + { + var targetBranchName = branchName.GetCanonicalBranchName(); + + return repository.Refs.FirstOrDefault(localRef => string.Equals(localRef.CanonicalName, targetBranchName)); + } + + private static DirectReference GetRemoteReference(Repository repository, string branchName, string repositoryUrl) + { + var targetBranchName = branchName.GetCanonicalBranchName(); + var remoteReferences = repository.Network.ListReferences(repositoryUrl); + + return remoteReferences.FirstOrDefault(remoteRef => string.Equals(remoteRef.CanonicalName, targetBranchName)); + } } } \ No newline at end of file From 3473451b7ed81a1a017e0e3798d586273baaa6e8 Mon Sep 17 00:00:00 2001 From: Geert van Horrik Date: Sun, 5 Oct 2014 16:12:54 +0200 Subject: [PATCH 2/3] Added log line back --- GitVersionExe/GitPreparer.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/GitVersionExe/GitPreparer.cs b/GitVersionExe/GitPreparer.cs index 1cab401a5e..6d6cab9a9f 100644 --- a/GitVersionExe/GitPreparer.cs +++ b/GitVersionExe/GitPreparer.cs @@ -97,6 +97,8 @@ string GetGitInfoFromUrl() if (newHead != null) { + Logger.WriteInfo(string.Format("Switching to branch '{0}'", arguments.TargetBranch)); + repository.Refs.UpdateTarget(repository.Refs.Head, newHead); } From 360d7fd15761148c297a8795f941a92eaafa387f Mon Sep 17 00:00:00 2001 From: Geert van Horrik Date: Thu, 4 Dec 2014 12:27:21 +0100 Subject: [PATCH 3/3] Added unit tests for GitPreparer --- GitVersionExe.Tests/GitPreparerTests.cs | 71 +++++++++++++++++++ .../GitVersionExe.Tests.csproj | 1 + 2 files changed, 72 insertions(+) create mode 100644 GitVersionExe.Tests/GitPreparerTests.cs diff --git a/GitVersionExe.Tests/GitPreparerTests.cs b/GitVersionExe.Tests/GitPreparerTests.cs new file mode 100644 index 0000000000..0a1eb6ab94 --- /dev/null +++ b/GitVersionExe.Tests/GitPreparerTests.cs @@ -0,0 +1,71 @@ +namespace GitVersionExe.Tests +{ + using System.IO; + using GitVersion; + using LibGit2Sharp; + using NUnit.Framework; + + [TestFixture] + public class GitPreparerTests + { + public GitPreparerTests() + { + Logger.WriteInfo = s => { }; + Logger.WriteWarning = s => { }; + Logger.WriteError = s => { }; + } + + const string RemoteRepositoryUrl = "https://github.com/ParticularLabs/GitVersion.git"; + const string DefaultBranchName = "master"; + const string SpecificBranchName = "gh-pages"; + + [Explicit] + [TestCase(null, DefaultBranchName)] + [TestCase(SpecificBranchName, SpecificBranchName)] + public void WorksCorrectlyWithRemoteRepository(string branchName, string expectedBranchName) + { + var tempDir = Path.GetTempPath(); + + var arguments = new Arguments + { + TargetPath = tempDir, + TargetUrl = RemoteRepositoryUrl + }; + + if (!string.IsNullOrWhiteSpace(branchName)) + { + arguments.TargetBranch = branchName; + } + + var gitPreparer = new GitPreparer(arguments); + var dynamicRepositoryPath = gitPreparer.Prepare(); + + Assert.AreEqual(Path.Combine(tempDir, "_dynamicrepository", ".git"), dynamicRepositoryPath); + Assert.IsTrue(gitPreparer.IsDynamicGitRepository); + + using (var repository = new Repository(dynamicRepositoryPath)) + { + var currentBranch = repository.Head.CanonicalName; + + Assert.IsTrue(currentBranch.EndsWith(expectedBranchName)); + } + } + + [Test] + public void WorksCorrectlyWithLocalRepository() + { + var tempDir = Path.GetTempPath(); + + var arguments = new Arguments + { + TargetPath = tempDir + }; + + var gitPreparer = new GitPreparer(arguments); + var dynamicRepositoryPath = gitPreparer.Prepare(); + + Assert.AreEqual(null, dynamicRepositoryPath); + Assert.IsFalse(gitPreparer.IsDynamicGitRepository); + } + } +} diff --git a/GitVersionExe.Tests/GitVersionExe.Tests.csproj b/GitVersionExe.Tests/GitVersionExe.Tests.csproj index 7aa9e365ce..df0e9e7533 100644 --- a/GitVersionExe.Tests/GitVersionExe.Tests.csproj +++ b/GitVersionExe.Tests/GitVersionExe.Tests.csproj @@ -76,6 +76,7 @@ +