Skip to content

Commit

Permalink
feat: implement a bitbucket link builder (#39)
Browse files Browse the repository at this point in the history
* feat: implement a bitbucket link builder

* chore: correct domain for test

* fix: support com and org domains for bitbucket
  • Loading branch information
saintedlama committed Jan 5, 2022
1 parent 28b67ec commit 60df2da
Show file tree
Hide file tree
Showing 3 changed files with 275 additions and 0 deletions.
198 changes: 198 additions & 0 deletions Versionize.Tests/Changelog/BitbucketLinkBuilderTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
using System;
using System.Data;
using System.Linq;
using LibGit2Sharp;
using NuGet.Versioning;
using Shouldly;
using Versionize.Tests.TestSupport;
using Xunit;

namespace Versionize.Changelog.Tests
{
public class BitBucketLinkBuilderTests
{
private readonly string sshOrgPushUrl = "git@bitbucket.org:mobiloitteinc/dotnet-codebase.git";
private readonly string sshComPushUrl = "git@bitbucket.com:mobiloitteinc/dotnet-codebase.git";
private readonly string httpsOrgPushUrl = "https://saintedlama@bitbucket.org/mobiloitteinc/dotnet-codebase.git";
private readonly string httpsComPushUrl = "https://saintedlama@bitbucket.com/mobiloitteinc/dotnet-codebase.git";

[Fact]
public void ShouldThrowIfUrlIsNoRecognizedSshOrHttpsUrl()
{
Should.Throw<InvalidOperationException>(() => new BitbucketLinkBuilder("bitbucket.org"));
}

[Fact]
public void ShouldThrowIfUrlIsNoValidHttpsCloneUrl()
{
Should.Throw<InvalidOperationException>(() => new BitbucketLinkBuilder("https://saintedlama@bitbucket.org/"));
}

[Fact]
public void ShouldThrowIfUrlIsNoValidSshCloneUrl()
{
Should.Throw<InvalidOperationException>(() => new BitbucketLinkBuilder("git@bitbucket.org:mobiloitteinc"));
}

[Fact]
public void ShouldCreateAnOrgBitbucketUrlBuilderForHTTPSPushUrls()
{
var repo = SetupRepositoryWithRemote("origin", httpsOrgPushUrl);
var linkBuilder = LinkBuilderFactory.CreateFor(repo);

linkBuilder.ShouldBeAssignableTo<BitbucketLinkBuilder>();
}

[Fact]
public void ShouldCreateAComBitbucketUrlBuilderForHTTPSPushUrls()
{
var repo = SetupRepositoryWithRemote("origin", httpsComPushUrl);
var linkBuilder = LinkBuilderFactory.CreateFor(repo);

linkBuilder.ShouldBeAssignableTo<BitbucketLinkBuilder>();
}

[Fact]
public void ShouldCreateAnOrgBitbucketUrlBuilderForSSHPushUrls()
{
var repo = SetupRepositoryWithRemote("origin", sshOrgPushUrl);
var linkBuilder = LinkBuilderFactory.CreateFor(repo);

linkBuilder.ShouldBeAssignableTo<BitbucketLinkBuilder>();
}

[Fact]
public void ShouldCreateAComBitbucketUrlBuilderForSSHPushUrls()
{
var repo = SetupRepositoryWithRemote("origin", sshComPushUrl);
var linkBuilder = LinkBuilderFactory.CreateFor(repo);

linkBuilder.ShouldBeAssignableTo<BitbucketLinkBuilder>();
}

[Fact]
public void ShouldPickFirstRemoteInCaseNoOriginWasFound()
{
var repo = SetupRepositoryWithRemote("some", sshOrgPushUrl);
var linkBuilder = LinkBuilderFactory.CreateFor(repo);

linkBuilder.ShouldBeAssignableTo<BitbucketLinkBuilder>();
}

[Fact]
public void ShouldFallbackToNoopInCaseNoBitbucketPushUrlWasDefined()
{
var repo = SetupRepositoryWithRemote("origin", "https://hostmeister.com/saintedlama/versionize.git");
var linkBuilder = LinkBuilderFactory.CreateFor(repo);

linkBuilder.ShouldBeAssignableTo<PlainLinkBuilder>();
}

[Fact]
public void ShouldBuildAnOrgSSHCommitLink()
{
var commit = new ConventionalCommit
{
Sha = "734713bc047d87bf7eac9674765ae793478c50d3"
};

var linkBuilder = new BitbucketLinkBuilder(sshOrgPushUrl);
var link = linkBuilder.BuildCommitLink(commit);

link.ShouldBe("https://bitbucket.org/mobiloitteinc/dotnet-codebase/commits/734713bc047d87bf7eac9674765ae793478c50d3");
}

[Fact]
public void ShouldBuildAComSSHCommitLink()
{
var commit = new ConventionalCommit
{
Sha = "734713bc047d87bf7eac9674765ae793478c50d3"
};

var linkBuilder = new BitbucketLinkBuilder(sshComPushUrl);
var link = linkBuilder.BuildCommitLink(commit);

link.ShouldBe("https://bitbucket.com/mobiloitteinc/dotnet-codebase/commits/734713bc047d87bf7eac9674765ae793478c50d3");
}

[Fact]
public void ShouldBuildAnOrgHTTPSCommitLink()
{
var commit = new ConventionalCommit
{
Sha = "734713bc047d87bf7eac9674765ae793478c50d3"
};

var linkBuilder = new BitbucketLinkBuilder(httpsOrgPushUrl);
var link = linkBuilder.BuildCommitLink(commit);

link.ShouldBe("https://bitbucket.org/mobiloitteinc/dotnet-codebase/commits/734713bc047d87bf7eac9674765ae793478c50d3");
}

[Fact]
public void ShouldBuildAComHTTPSCommitLink()
{
var commit = new ConventionalCommit
{
Sha = "734713bc047d87bf7eac9674765ae793478c50d3"
};

var linkBuilder = new BitbucketLinkBuilder(httpsComPushUrl);
var link = linkBuilder.BuildCommitLink(commit);

link.ShouldBe("https://bitbucket.com/mobiloitteinc/dotnet-codebase/commits/734713bc047d87bf7eac9674765ae793478c50d3");
}

[Fact]
public void ShouldBuildAnOrgSSHTagLink()
{
var linkBuilder = new BitbucketLinkBuilder(sshOrgPushUrl);
var link = linkBuilder.BuildVersionTagLink(new SemanticVersion(1, 0, 0));

link.ShouldBe("https://bitbucket.org/mobiloitteinc/dotnet-codebase/src/v1.0.0");
}

[Fact]
public void ShouldBuildAComSSHTagLink()
{
var linkBuilder = new BitbucketLinkBuilder(sshComPushUrl);
var link = linkBuilder.BuildVersionTagLink(new SemanticVersion(1, 0, 0));

link.ShouldBe("https://bitbucket.com/mobiloitteinc/dotnet-codebase/src/v1.0.0");
}

[Fact]
public void ShouldBuildAnOrgHTTPSTagLink()
{
var linkBuilder = new BitbucketLinkBuilder(httpsOrgPushUrl);
var link = linkBuilder.BuildVersionTagLink(new SemanticVersion(1, 0, 0));

link.ShouldBe("https://bitbucket.org/mobiloitteinc/dotnet-codebase/src/v1.0.0");
}

[Fact]
public void ShouldBuildAComHTTPSTagLink()
{
var linkBuilder = new BitbucketLinkBuilder(httpsComPushUrl);
var link = linkBuilder.BuildVersionTagLink(new SemanticVersion(1, 0, 0));

link.ShouldBe("https://bitbucket.com/mobiloitteinc/dotnet-codebase/src/v1.0.0");
}

private static Repository SetupRepositoryWithRemote(string remoteName, string pushUrl)
{
var workingDirectory = TempDir.Create();
var repo = TempRepository.Create(workingDirectory);

foreach (var existingRemoteName in repo.Network.Remotes.Select(remote => remote.Name))
{
repo.Network.Remotes.Remove(existingRemoteName);
}

repo.Network.Remotes.Add(remoteName, pushUrl);

return repo;
}
}
}
73 changes: 73 additions & 0 deletions Versionize/Changelog/BitbucketLinkBuilder.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
using System;
using System.Text.RegularExpressions;
using Version = NuGet.Versioning.SemanticVersion;

namespace Versionize.Changelog
{
// TODO: Accept both .org and .com extensions
public class BitbucketLinkBuilder : IChangelogLinkBuilder
{
private const string OrgSshPrefix = "git@bitbucket.org:";
private const string ComSshPrefix = "git@bitbucket.com:";

private readonly string _organization;
private readonly string _repository;
private readonly string _domain;

public BitbucketLinkBuilder(string pushUrl)
{
if (pushUrl.StartsWith(OrgSshPrefix) || pushUrl.StartsWith(ComSshPrefix))
{
var httpsPattern = new Regex("^git@bitbucket\\.(?<domain>org|com):(?<organization>.*?)/(?<repository>.*?)(?:\\.git)?$");
var matches = httpsPattern.Match(pushUrl);

if (!matches.Success)
{
throw new InvalidOperationException($"Remote url {pushUrl} is not recognized as valid Bitbucket SSH pattern");
}

_organization = matches.Groups["organization"].Value;
_repository = matches.Groups["repository"].Value;
_domain = matches.Groups["domain"].Value;
}
else if (IsHttpsPushUrl(pushUrl))
{
var httpsPattern = new Regex("^https://.*?bitbucket\\.(?<domain>org|com)/(?<organization>.*?)/(?<repository>.*?)(?:\\.git)?$");
var matches = httpsPattern.Match(pushUrl);

if (!matches.Success)
{
throw new InvalidOperationException($"Remote url {pushUrl} is not recognized as valid Bitbucket HTTPS pattern");
}

_organization = matches.Groups["organization"].Value;
_repository = matches.Groups["repository"].Value;
_domain = matches.Groups["domain"].Value;
}
else
{
throw new InvalidOperationException($"Remote url {pushUrl} is not recognized as Bitbucket SSH or HTTPS url");
}
}

public static bool IsPushUrl(string pushUrl)
{
return pushUrl.StartsWith(ComSshPrefix) || pushUrl.StartsWith(OrgSshPrefix) || IsHttpsPushUrl(pushUrl);
}

public string BuildVersionTagLink(Version version)
{
return $"https://bitbucket.{_domain}/{_organization}/{_repository}/src/v{version}";
}

public string BuildCommitLink(ConventionalCommit commit)
{
return $"https://bitbucket.{_domain}/{_organization}/{_repository}/commits/{commit.Sha}";
}

private static bool IsHttpsPushUrl(string pushUrl)
{
return new Regex("^https://.*?@bitbucket\\.(org|com)/.*$").IsMatch(pushUrl);
}
}
}
4 changes: 4 additions & 0 deletions Versionize/Changelog/LinkBuilderFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ public static IChangelogLinkBuilder CreateFor(Repository repository)
{
return new AzureLinkBuilder(origin.PushUrl);
}
else if (BitbucketLinkBuilder.IsPushUrl(origin.PushUrl))
{
return new BitbucketLinkBuilder(origin.PushUrl);
}

return new PlainLinkBuilder();
}
Expand Down

0 comments on commit 60df2da

Please sign in to comment.