diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 0ddd0ad..bb90f69 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -2,8 +2,8 @@ name: CI
on:
push:
- branches-ignore:
- - "dependabot/**"
+ branches:
+ - "master"
tags-ignore:
- "**"
diff --git a/shared/handler-schema.json b/shared/handler-schema.json
index bc6438c..af2fb8d 100644
--- a/shared/handler-schema.json
+++ b/shared/handler-schema.json
@@ -28,20 +28,28 @@
"items": {
"type": "object",
"properties": {
- "pattern": {
+ "remotePattern": {
"type": "string",
"description": "A regular expression to match on a remote URL. The captured groups are provided to the `http` and `ssh` templates."
},
"http": {
"$ref": "#/definitions/template",
- "description": "The template to build the HTTP(S) URL of the remote server.\n\nCaptured groups from `pattern` are made available via the `match` variable."
+ "description": "The template to build the HTTP(S) remote URL.\n\nCaptured groups from `pattern` are made available via the `match` variable."
},
"ssh": {
"$ref": "#/definitions/template",
- "description": "The template to build the SSH URL of the remote server.\n\nCaptured groups from `pattern` are made available via the `match` variable."
+ "description": "The template to build the SSH remote URL.\n\nCaptured groups from `pattern` are made available via the `match` variable."
+ },
+ "webPattern": {
+ "type": "string",
+ "description": "A regular expression to match on a web interface URL. The captured groups are provided to the `http` and `ssh` templates."
+ },
+ "web": {
+ "$ref": "#/definitions/template",
+ "description": "The template to build the web interface URL.\n\nCaptured groups from `pattern` are made available via the `match` variable."
}
},
- "required": ["pattern", "http", "ssh"],
+ "required": ["remotePattern", "http", "ssh"],
"additionalProperties": false
}
},
diff --git a/shared/handlers/azure-dev-ops-cloud.json b/shared/handlers/azure-dev-ops-cloud.json
index 202441f..744313b 100644
--- a/shared/handlers/azure-dev-ops-cloud.json
+++ b/shared/handlers/azure-dev-ops-cloud.json
@@ -3,14 +3,15 @@
"name": "Azure Dev Ops Cloud",
"server": [
{
- "pattern": "^https:\\/\\/(?:.+@)?dev\\.azure\\.com\\/([^\\/]+)\\/([^\\/]+)\\/_git\\/.+",
+ "remotePattern": "^https:\\/\\/(?:.+@)?dev\\.azure\\.com\\/([^\\/]+)\\/([^\\/]+)\\/_git\\/.+",
"http": "https://dev.azure.com/{{ match[1] }}/{{ match[2] }}/_git",
"ssh": "git@ssh.dev.azure.com:v3/{{ match[1] }}/{{ match[2] }}"
},
{
- "pattern": "^(?:git@)?ssh\\.dev\\.azure\\.com:v3\\/([^\\/]+)\\/([^\\/]+)\\/.+$",
+ "remotePattern": "^(?:git@)?ssh\\.dev\\.azure\\.com:v3\\/([^\\/]+)\\/([^\\/]+)\\/.+$",
"http": "https://dev.azure.com/{{ match[1] }}/{{ match[2] }}/_git",
- "ssh": "git@ssh.dev.azure.com:v3/{{ match[1] }}/{{ match[2] }}"
+ "ssh": "git@ssh.dev.azure.com:v3/{{ match[1] }}/{{ match[2] }}",
+ "webPattern": "^ONLY MATCH TO SSH REMOTE URLS$"
}
],
"branchRef": "abbreviated",
diff --git a/shared/handlers/github.json b/shared/handlers/github.json
index ec60ae3..21eacd7 100644
--- a/shared/handlers/github.json
+++ b/shared/handlers/github.json
@@ -3,14 +3,15 @@
"name": "GitHub",
"server": [
{
- "pattern": "^https:\\/\\/github.(?:com|dev)",
+ "remotePattern": "^https:\\/\\/github.(?:com|dev)",
"http": "https://github.com",
"ssh": "git@github.com"
},
{
- "pattern": "^(?:git@)?github\\.com",
+ "remotePattern": "^(?:git@)?github\\.com",
"http": "https://github.com",
- "ssh": "git@github.com"
+ "ssh": "git@github.com",
+ "webPattern": "^ONLY MATCH TO SSH REMOTE URLS$"
}
],
"branchRef": "abbreviated",
diff --git a/shared/handlers/gitiles.json b/shared/handlers/gitiles.json
index a98f097..b785ea6 100644
--- a/shared/handlers/gitiles.json
+++ b/shared/handlers/gitiles.json
@@ -54,6 +54,22 @@
"remote": "https://git.company.com:1368/plugins/gitiles/foo/bar.git",
"result": "https://git.company.com:1368/plugins/gitiles/foo/bar/+/{{ commit }}/src/file.txt"
},
+ "misc": [
+ {
+ "name": "Web URL is different to clone URL",
+ "settings": {
+ "gitiles": [
+ {
+ "http": "https://git.company.com/a",
+ "ssh": "ssh://git.company.com:29418",
+ "web": "https://git.company.com/plugins/gitiles"
+ }
+ ]
+ },
+ "remote": "https://git.company.com/a/foo/bar.git",
+ "result": "https://git.company.com/plugins/gitiles/foo/bar/+/master/src/file.txt"
+ }
+ ],
"selection": {
"remote": "https://git.company.com:1368/plugins/gitiles/foo/bar.git",
"point": {
diff --git a/shared/handlers/visual-studio-team-services.json b/shared/handlers/visual-studio-team-services.json
index 6431d2d..3bf08c9 100644
--- a/shared/handlers/visual-studio-team-services.json
+++ b/shared/handlers/visual-studio-team-services.json
@@ -3,12 +3,13 @@
"name": "Visual Studio Team Services",
"server": [
{
- "pattern": "^([^.]+)@vs-ssh\\.visualstudio\\.com:22(?:/(.+))?/_ssh/.+$",
+ "remotePattern": "^([^.]+)@vs-ssh\\.visualstudio\\.com:22(?:/(.+))?/_ssh/.+$",
"http": "https://{{ match[1] }}.visualstudio.com{% if match[2] %}/{{ match[2] }}{% endif %}/_git",
- "ssh": "{{ match[1] }}@vs-ssh.visualstudio.com:22{% if match[2] %}/{{ match[2] }}{% endif %}/_ssh"
+ "ssh": "{{ match[1] }}@vs-ssh.visualstudio.com:22{% if match[2] %}/{{ match[2] }}{% endif %}/_ssh",
+ "webPattern": "^ONLY MATCH TO SSH REMOTE URLS$"
},
{
- "pattern": "^https://([^.]+).visualstudio.com(?:/(.+))?/_git/.+$",
+ "remotePattern": "^https://([^.]+).visualstudio.com(?:/(.+))?/_git/.+$",
"http": "https://{{ match[1] }}.visualstudio.com{% if match[2] %}/{{ match[2] }}{% endif %}/_git",
"ssh": "{{ match[1] }}@vs-ssh.visualstudio.com:22{% if match[2] %}/{{ match[2] }}{% endif %}/_ssh"
}
diff --git a/visual-studio/source/GitWebLinks/Options/Gitiles/GitilesOptionsControl.xaml b/visual-studio/source/GitWebLinks/Options/Gitiles/GitilesOptionsControl.xaml
index 4479ed6..bd04977 100644
--- a/visual-studio/source/GitWebLinks/Options/Gitiles/GitilesOptionsControl.xaml
+++ b/visual-studio/source/GitWebLinks/Options/Gitiles/GitilesOptionsControl.xaml
@@ -23,8 +23,10 @@
diff --git a/visual-studio/source/GitWebLinks/Options/ServerListItem.cs b/visual-studio/source/GitWebLinks/Options/ServerListItem.cs
index 0ccebf6..f0f829e 100644
--- a/visual-studio/source/GitWebLinks/Options/ServerListItem.cs
+++ b/visual-studio/source/GitWebLinks/Options/ServerListItem.cs
@@ -9,4 +9,7 @@ public class ServerListItem {
public string? Ssh { get; set; } = "";
+
+ public string? Web { get; set; } = "";
+
}
diff --git a/visual-studio/source/GitWebLinks/Options/ServerOptionsPageBase.cs b/visual-studio/source/GitWebLinks/Options/ServerOptionsPageBase.cs
index 8aec620..d696a14 100644
--- a/visual-studio/source/GitWebLinks/Options/ServerOptionsPageBase.cs
+++ b/visual-studio/source/GitWebLinks/Options/ServerOptionsPageBase.cs
@@ -15,7 +15,13 @@ public abstract class ServerOptionsPageBase : OptionsPageBase {
internal IReadOnlyList GetServers() {
- return Servers.Select((x) => new StaticServer(x.Http ?? "", x.Ssh)).ToList();
+ return Servers.Select(
+ (x) => new StaticServer(
+ x.Http ?? "",
+ string.IsNullOrEmpty(x.Ssh) ? null : x.Ssh,
+ string.IsNullOrEmpty(x.Web) ? null : x.Web
+ )
+ ).ToList();
}
@@ -38,7 +44,11 @@ public string JsonServers {
protected static string SerializeServers(IEnumerable servers) {
return JsonConvert.SerializeObject(
- servers.Select((x) => new ServerListItem { Http = x.Http ?? "", Ssh = x.Ssh ?? "" })
+ servers.Select((x) => new ServerListItem {
+ Http = x.Http ?? "",
+ Ssh = x.Ssh,
+ Web = x.Web
+ })
);
}
diff --git a/visual-studio/source/GitWebLinks/Properties/AssemblyInfo.cs b/visual-studio/source/GitWebLinks/Properties/AssemblyInfo.cs
index fb7a26c..0f2f74a 100644
--- a/visual-studio/source/GitWebLinks/Properties/AssemblyInfo.cs
+++ b/visual-studio/source/GitWebLinks/Properties/AssemblyInfo.cs
@@ -13,6 +13,6 @@
[assembly: AssemblyCulture("")]
[assembly: ComVisible(false)]
[assembly: NeutralResourcesLanguage("en-US")]
-[assembly: AssemblyVersion("2.11.0.0")]
-[assembly: AssemblyFileVersion("2.11.0.0")]
+[assembly: AssemblyVersion("2.12.0.0")]
+[assembly: AssemblyFileVersion("2.12.0.0")]
[assembly: ThemeInfo(ResourceDictionaryLocation.None, ResourceDictionaryLocation.SourceAssembly)]
diff --git a/visual-studio/source/GitWebLinks/Services/DefinitionProvider.Json.cs b/visual-studio/source/GitWebLinks/Services/DefinitionProvider.Json.cs
index 5abd1ec..cfba75f 100644
--- a/visual-studio/source/GitWebLinks/Services/DefinitionProvider.Json.cs
+++ b/visual-studio/source/GitWebLinks/Services/DefinitionProvider.Json.cs
@@ -39,7 +39,7 @@ private class JsonHandlerDefinition {
private class JsonServer {
- public string? Pattern { get; set; }
+ public string? RemotePattern { get; set; }
public string Http { get; set; } = "";
@@ -47,6 +47,12 @@ private class JsonServer {
public string Ssh { get; set; } = "";
+
+ public string? WebPattern { get; set; }
+
+
+ public string? Web { get; set; }
+
}
@@ -89,6 +95,9 @@ private class JsonReverseServerSettings {
public string Ssh { get; set; } = "";
+
+ public string? Web { get; set; }
+
}
diff --git a/visual-studio/source/GitWebLinks/Services/DefinitionProvider.cs b/visual-studio/source/GitWebLinks/Services/DefinitionProvider.cs
index 35a6d73..b67363f 100644
--- a/visual-studio/source/GitWebLinks/Services/DefinitionProvider.cs
+++ b/visual-studio/source/GitWebLinks/Services/DefinitionProvider.cs
@@ -94,10 +94,18 @@ private static IReadOnlyList ParseServers(IReadOnlyList jso
servers = new List();
foreach (var server in json) {
- if (server.Pattern is not null) {
- servers.Add(new DynamicServer(new Regex(server.Pattern), Template.Parse(server.Http), Template.Parse(server.Ssh)));
+ if (server.RemotePattern is not null) {
+ servers.Add(
+ new DynamicServer(
+ new Regex(server.RemotePattern),
+ Template.Parse(server.Http),
+ Template.Parse(server.Ssh),
+ (server.WebPattern is not null) ? new Regex(server.WebPattern) : null,
+ (server.Web is not null) ? Template.Parse(server.Web) : null
+ )
+ );
} else {
- servers.Add(new StaticServer(server.Http, server.Ssh));
+ servers.Add(new StaticServer(server.Http, server.Ssh, server.Web));
}
}
@@ -117,7 +125,8 @@ private static ReverseSettings ParseReverseSettings(JsonReverseSettings json) {
json.FileMayStartWithBranch,
new ReverseServerSettings(
Template.Parse(json.Server.Http),
- Template.Parse(json.Server.Ssh)
+ Template.Parse(json.Server.Ssh),
+ (json.Server.Web is not null) ? Template.Parse(json.Server.Web) : null
),
new ReverseSelectionSettings(
Template.Parse(json.Selection.StartLine),
diff --git a/visual-studio/source/GitWebLinks/Services/ILinkHandler.cs b/visual-studio/source/GitWebLinks/Services/ILinkHandler.cs
index 567a77c..ea9221b 100644
--- a/visual-studio/source/GitWebLinks/Services/ILinkHandler.cs
+++ b/visual-studio/source/GitWebLinks/Services/ILinkHandler.cs
@@ -18,6 +18,6 @@ public interface ILinkHandler {
Task GetUrlInfoAsync(string url, bool strict);
- Task IsMatchAsync(string remoteUrl);
+ Task HandlesRemoteUrlAsync(string remoteUrl);
}
diff --git a/visual-studio/source/GitWebLinks/Services/LinkHandler.cs b/visual-studio/source/GitWebLinks/Services/LinkHandler.cs
index 87c13b4..937ef85 100644
--- a/visual-studio/source/GitWebLinks/Services/LinkHandler.cs
+++ b/visual-studio/source/GitWebLinks/Services/LinkHandler.cs
@@ -38,8 +38,8 @@ public LinkHandler(HandlerDefinition definition, ISettings settings, Git git) {
public string Name => _definition.Name;
- public async Task IsMatchAsync(string remoteUrl) {
- return await _server.MatchAsync(UrlHelpers.Normalize(remoteUrl)) is not null;
+ public async Task HandlesRemoteUrlAsync(string remoteUrl) {
+ return await _server.MatchRemoteUrlAsync(UrlHelpers.Normalize(remoteUrl)) is not null;
}
@@ -48,7 +48,7 @@ public async Task CreateUrlAsync(Repository repository, FileInf
throw new InvalidOperationException("The repository must have a remote.");
}
- string remote;
+ string remoteUrl;
StaticServer address;
string refValue;
RefType refType;
@@ -86,15 +86,15 @@ public async Task CreateUrlAsync(Repository repository, FileInf
// Adjust the remote URL so that it's in a
// standard format that we can manipulate.
- remote = UrlHelpers.Normalize(repository.Remote.Url);
+ remoteUrl = UrlHelpers.Normalize(repository.Remote.Url);
- address = await GetAddressAsync(remote);
+ address = await GetAddressAsync(remoteUrl);
relativePath = GetRelativePath(repository.Root, file.FilePath);
data = TemplateData
.Create()
- .Add("base", address.Http)
- .Add("repository", GetRepositoryPath(remote, address))
+ .Add("base", address.Web ?? address.Http)
+ .Add("repository", GetRepositoryPath(remoteUrl, address))
.Add("ref", refValue)
.Add("commit", await GetRefAsync(LinkType.Commit, repository.Root, repository.Remote))
.Add("file", relativePath)
@@ -151,11 +151,11 @@ private static string ApplyModifications(string url, IReadOnlyList GetAddressAsync(string remote) {
+ private async Task GetAddressAsync(string remoteUrl) {
StaticServer? address;
- address = await _server.MatchAsync(remote);
+ address = await _server.MatchRemoteUrlAsync(remoteUrl);
if (address is null) {
throw new InvalidOperationException("Could not find a matching address.");
@@ -168,11 +168,13 @@ private async Task GetAddressAsync(string remote) {
private static StaticServer NormalizeServerUrls(StaticServer address) {
string http;
string? ssh;
+ string? web;
http = UrlHelpers.Normalize(address.Http);
ssh = address.Ssh is not null ? UrlHelpers.Normalize(address.Ssh) : null;
+ web = address.Web is not null ? UrlHelpers.Normalize(address.Web) : null;
- return new StaticServer(http, ssh);
+ return new StaticServer(http, ssh, web);
}
@@ -298,7 +300,7 @@ private string GetRelativePath(string from, string to) {
// If the file is a symbolic link, or is under a directory that's a
// symbolic link, then we want to resolve the path to the real file
- // because the sybmolic link won't be in the Git repository.
+ // because the symbolic link won't be in the Git repository.
if (IsSymbolicLink(to, from)) {
try {
to = GetRealPath(to);
@@ -449,13 +451,13 @@ private static bool TryGetFinalPathNameByHandle(IntPtr handle, int bufferSize, o
}
- public async Task GetUrlInfoAsync(string url, bool strict) {
+ public async Task GetUrlInfoAsync(string webUrl, bool strict) {
StaticServer? address;
Match match;
// See if the URL matches the server address for the handler.
- address = await _server.MatchAsync(url);
+ address = await _server.MatchWebUrlAsync(webUrl);
// If we are performing a strict match, then the
// URL must match to this handler's server.
@@ -467,7 +469,7 @@ private static bool TryGetFinalPathNameByHandle(IntPtr handle, int bufferSize, o
address = NormalizeServerUrls(address);
}
- match = _definition.Reverse.Pattern.Match(url);
+ match = _definition.Reverse.Pattern.Match(webUrl);
if (match.Success) {
Hash hash;
@@ -480,6 +482,7 @@ private static bool TryGetFinalPathNameByHandle(IntPtr handle, int bufferSize, o
.Create()
.Add("http", address?.Http)
.Add("ssh", address?.Ssh)
+ .Add("web", address?.Web)
.Add(match)
.ToHash();
@@ -487,7 +490,8 @@ private static bool TryGetFinalPathNameByHandle(IntPtr handle, int bufferSize, o
server = new StaticServer(
_definition.Reverse.Server.Http.Render(hash),
- _definition.Reverse.Server.Ssh.Render(hash)
+ _definition.Reverse.Server.Ssh.Render(hash),
+ _definition.Reverse.Server.Web?.Render(hash)
);
selection = new PartialSelectedRange(
diff --git a/visual-studio/source/GitWebLinks/Services/LinkHandlerProvider.cs b/visual-studio/source/GitWebLinks/Services/LinkHandlerProvider.cs
index 9b7b91a..eb47cae 100644
--- a/visual-studio/source/GitWebLinks/Services/LinkHandlerProvider.cs
+++ b/visual-studio/source/GitWebLinks/Services/LinkHandlerProvider.cs
@@ -33,7 +33,7 @@ public LinkHandlerProvider(ISettings settings, Git git, ILogger logger) {
foreach (ILinkHandler handler in _handlers) {
await _logger.LogAsync($"Testing '{handler.Name}");
- if (await handler.IsMatchAsync(repository.Remote.Url)) {
+ if (await handler.HandlesRemoteUrlAsync(repository.Remote.Url)) {
await _logger.LogAsync($"Handler '{handler.Name}' is a match.");
return handler;
}
@@ -44,23 +44,23 @@ public LinkHandlerProvider(ISettings settings, Git git, ILogger logger) {
}
- public async Task> GetUrlInfoAsync(string url) {
+ public async Task> GetUrlInfoAsync(string webUrl) {
IReadOnlyCollection output;
- await _logger.LogAsync($"Finding file info for URL '{url}'.");
- output = await InternalGetUrlInfoAsync(url, true);
+ await _logger.LogAsync($"Finding file info for URL '{webUrl}'.");
+ output = await InternalGetUrlInfoAsync(webUrl, true);
if (output.Count == 0) {
await _logger.LogAsync("No strict matches found. Trying again with loose matching.");
- output = await InternalGetUrlInfoAsync(url, false);
+ output = await InternalGetUrlInfoAsync(webUrl, false);
}
return output;
}
- private async Task> InternalGetUrlInfoAsync(string url, bool strict) {
+ private async Task> InternalGetUrlInfoAsync(string webUrl, bool strict) {
List output;
@@ -70,7 +70,7 @@ private async Task> InternalGetUrlInfoAsync(string
UrlInfo? info;
- info = await handler.GetUrlInfoAsync(url, strict);
+ info = await handler.GetUrlInfoAsync(webUrl, strict);
if (info is not null) {
await _logger.LogAsync($"The handler '{handler.Name}' mapped the file to '{info}'.");
diff --git a/visual-studio/source/GitWebLinks/Types/DynamicServer.cs b/visual-studio/source/GitWebLinks/Types/DynamicServer.cs
index 30028cf..9c0056f 100644
--- a/visual-studio/source/GitWebLinks/Types/DynamicServer.cs
+++ b/visual-studio/source/GitWebLinks/Types/DynamicServer.cs
@@ -7,14 +7,16 @@ namespace GitWebLinks;
public class DynamicServer : IServer {
- public DynamicServer(Regex pattern, Template http, Template ssh) {
- Pattern = pattern;
+ public DynamicServer(Regex remotePattern, Template http, Template ssh, Regex? webPattern, Template? web) {
+ RemotePattern = remotePattern;
Http = http;
Ssh = ssh;
+ WebPattern = webPattern;
+ Web = web;
}
- public Regex Pattern { get; }
+ public Regex RemotePattern { get; }
public Template Http { get; }
@@ -22,4 +24,10 @@ public DynamicServer(Regex pattern, Template http, Template ssh) {
public Template Ssh { get; }
+
+ public Regex? WebPattern { get; }
+
+
+ public Template? Web { get; }
+
}
diff --git a/visual-studio/source/GitWebLinks/Types/RemoteServer.cs b/visual-studio/source/GitWebLinks/Types/RemoteServer.cs
index 0660138..59d7557 100644
--- a/visual-studio/source/GitWebLinks/Types/RemoteServer.cs
+++ b/visual-studio/source/GitWebLinks/Types/RemoteServer.cs
@@ -11,11 +11,11 @@ namespace GitWebLinks;
public class RemoteServer {
- private readonly List _matchers;
+ private readonly List _matchers;
public RemoteServer(IServer server) {
- _matchers = new List { CreateMatcher(server) };
+ _matchers = new List { CreateMatcher(server) };
}
@@ -25,68 +25,85 @@ public RemoteServer(IEnumerable servers) {
public RemoteServer(Func>> serverFactory) {
- _matchers = new List { CreateLazyStaticServerMatcher(serverFactory) };
+ _matchers = new List { CreateLazyStaticServerMatcher(serverFactory) };
}
- private static AsyncMatcher CreateMatcher(IServer server) {
- Matcher matcher;
-
-
+ private static Matcher CreateMatcher(IServer server) {
if (server is DynamicServer dynamicServer) {
- matcher = CreateDynamicServerMatcher(dynamicServer);
+ return CreateDynamicServerMatcher(dynamicServer);
} else {
- matcher = CreateStaticServerMatcher((StaticServer)server);
+ return CreateStaticServerMatcher((StaticServer)server);
}
-
- return (x) => Task.FromResult(matcher(x));
}
private static Matcher CreateDynamicServerMatcher(DynamicServer server) {
- return (url) => {
- Match match;
+ return new Matcher(
+ Create(server.RemotePattern),
+ Create(server.WebPattern ?? server.RemotePattern)
+ );
+ UrlMatcher Create(Regex pattern) {
+ return (url) => {
+ Match match;
+ StaticServer? result;
- match = server.Pattern.Match(url);
- if (match.Success) {
- Hash hash;
+ match = pattern.Match(url);
+ if (match.Success) {
+ Hash hash;
- // The URL matched the pattern. Render the templates to get the HTTP
- // and SSH URLs, making the match available for the templates to use.
- hash = TemplateData.Create().Add(match).ToHash();
- return new StaticServer(
- server.Http.Render(hash),
- server.Ssh.Render(hash)
- );
- }
+ // The URL matched the pattern. Render the templates to get the HTTP
+ // and SSH URLs, making the match available for the templates to use.
+ hash = TemplateData.Create().Add(match).ToHash();
- return null;
- };
+ result = new StaticServer(
+ server.Http.Render(hash),
+ server.Ssh.Render(hash),
+ server.Web?.Render(hash)
+ );
+
+ } else {
+ result = null;
+ }
+
+ return Task.FromResult(result);
+ };
+ }
}
private static Matcher CreateStaticServerMatcher(StaticServer server) {
- return (url) => IsMatch(url, server) ? server : null;
+ return new Matcher(
+ (url) => Task.FromResult(IsRemoteMatch(url, server) ? server : null),
+ (url) => Task.FromResult(IsWebMatch(url, server) ? server : null)
+ );
}
- private static AsyncMatcher CreateLazyStaticServerMatcher(Func>> factory) {
- return async (url) => (await factory()).Where((x) => IsMatch(url, x)).FirstOrDefault();
+ private static Matcher CreateLazyStaticServerMatcher(Func>> factory) {
+ return new Matcher(
+ Create(IsRemoteMatch),
+ Create(IsWebMatch)
+ );
+
+ UrlMatcher Create(Func test) {
+ return async (url) => (await factory()).Where((x) => test(url, x)).FirstOrDefault();
+ }
}
- private static bool IsMatch(string url, StaticServer server) {
- url = UrlHelpers.Normalize(url);
+ private static bool IsRemoteMatch(string remoteUrl, StaticServer server) {
+ remoteUrl = UrlHelpers.Normalize(remoteUrl);
- if (url.StartsWith(UrlHelpers.Normalize(server.Http), StringComparison.Ordinal)) {
+ if (remoteUrl.StartsWith(UrlHelpers.Normalize(server.Http), StringComparison.Ordinal)) {
return true;
}
- if ((server.Ssh is not null) && url.StartsWith(UrlHelpers.Normalize(server.Ssh), StringComparison.Ordinal)) {
+ if ((server.Ssh is not null) && remoteUrl.StartsWith(UrlHelpers.Normalize(server.Ssh), StringComparison.Ordinal)) {
return true;
}
@@ -94,12 +111,32 @@ private static bool IsMatch(string url, StaticServer server) {
}
- public async Task MatchAsync(string url) {
- foreach (AsyncMatcher matcher in _matchers) {
+ private static bool IsWebMatch(string webUrl, StaticServer server) {
+ return UrlHelpers
+ .Normalize(webUrl)
+ .StartsWith(UrlHelpers.Normalize(server.Web ?? server.Http), StringComparison.Ordinal);
+ }
+
+
+ public Task MatchRemoteUrlAsync(string remoteUrl) {
+ return MatchUrlAsync(remoteUrl, static (x) => x.Remote);
+ }
+
+
+ public Task MatchWebUrlAsync(string webUrl) {
+ return MatchUrlAsync(webUrl, static (x) => x.Web);
+ }
+
+
+ private async Task MatchUrlAsync(
+ string url,
+ Func selectUrlMatcher
+ ) {
+ foreach (Matcher matcher in _matchers) {
StaticServer? server;
- server = await matcher(url);
+ server = await selectUrlMatcher(matcher)(url);
if (server is not null) {
return server;
@@ -110,9 +147,22 @@ private static bool IsMatch(string url, StaticServer server) {
}
- private delegate Task AsyncMatcher(string url);
+ private delegate Task UrlMatcher(string url);
+
+ private class Matcher {
- private delegate StaticServer? Matcher(string url);
+ public Matcher(UrlMatcher remote, UrlMatcher web) {
+ Remote = remote;
+ Web = web;
+ }
+
+
+ public UrlMatcher Remote { get; }
+
+
+ public UrlMatcher Web { get; }
+
+ }
}
diff --git a/visual-studio/source/GitWebLinks/Types/ReverseServerSettings.cs b/visual-studio/source/GitWebLinks/Types/ReverseServerSettings.cs
index e3e11fa..7739188 100644
--- a/visual-studio/source/GitWebLinks/Types/ReverseServerSettings.cs
+++ b/visual-studio/source/GitWebLinks/Types/ReverseServerSettings.cs
@@ -6,9 +6,10 @@ namespace GitWebLinks;
public class ReverseServerSettings {
- public ReverseServerSettings(Template http, Template ssh) {
+ public ReverseServerSettings(Template http, Template ssh, Template? web) {
Http = http;
Ssh = ssh;
+ Web = web;
}
@@ -17,4 +18,7 @@ public ReverseServerSettings(Template http, Template ssh) {
public Template Ssh { get; }
+
+ public Template? Web { get; }
+
}
diff --git a/visual-studio/source/GitWebLinks/Types/StaticServer.cs b/visual-studio/source/GitWebLinks/Types/StaticServer.cs
index 8669741..787f319 100644
--- a/visual-studio/source/GitWebLinks/Types/StaticServer.cs
+++ b/visual-studio/source/GitWebLinks/Types/StaticServer.cs
@@ -4,9 +4,10 @@ namespace GitWebLinks;
public class StaticServer : IServer {
- public StaticServer(string http, string? ssh) {
+ public StaticServer(string http, string? ssh, string? web) {
Http = http;
Ssh = ssh;
+ Web = web;
}
@@ -15,4 +16,7 @@ public StaticServer(string http, string? ssh) {
public string? Ssh { get; }
+
+ public string? Web { get; }
+
}
diff --git a/visual-studio/source/GitWebLinks/UI/Controls/ServerDataGrid.cs b/visual-studio/source/GitWebLinks/UI/Controls/ServerDataGrid.cs
index 150ff5e..7cd7735 100644
--- a/visual-studio/source/GitWebLinks/UI/Controls/ServerDataGrid.cs
+++ b/visual-studio/source/GitWebLinks/UI/Controls/ServerDataGrid.cs
@@ -1,10 +1,16 @@
+#nullable enable
+
using System.Windows;
using System.Windows.Controls;
+using System.Windows.Data;
namespace GitWebLinks;
public class ServerDataGrid : ItemsControl {
+ private DataGrid? _dataGrid;
+
+
static ServerDataGrid() {
DefaultStyleKeyProperty.OverrideMetadata(
typeof(ServerDataGrid),
@@ -29,6 +35,33 @@ static ServerDataGrid() {
);
+ public static readonly DependencyProperty WebExampleProperty = DependencyProperty.Register(
+ nameof(WebExample),
+ typeof(string),
+ typeof(ServerDataGrid),
+ new FrameworkPropertyMetadata("")
+ );
+
+
+ public static readonly DependencyProperty HasWebAddressProperty = DependencyProperty.Register(
+ nameof(HasWebAddress),
+ typeof(bool),
+ typeof(ServerDataGrid),
+ new FrameworkPropertyMetadata(false, OnHasWebAddressChanged)
+ );
+
+
+ private static readonly DependencyPropertyKey WebExampleVisibilityPropertyKey = DependencyProperty.RegisterReadOnly(
+ nameof(WebExampleVisibility),
+ typeof(Visibility),
+ typeof(ServerDataGrid),
+ new FrameworkPropertyMetadata(Visibility.Collapsed)
+ );
+
+
+ public static readonly DependencyProperty WebExampleVisibilityProperty = WebExampleVisibilityPropertyKey.DependencyProperty;
+
+
public string HttpExample {
get { return (string)GetValue(HttpExampleProperty); }
set { SetValue(HttpExampleProperty, value); }
@@ -40,4 +73,77 @@ public string SshExample {
set { SetValue(SshExampleProperty, value); }
}
+
+ public string WebExample {
+ get { return (string)GetValue(WebExampleProperty); }
+ set { SetValue(WebExampleProperty, value); }
+ }
+
+
+ public bool HasWebAddress {
+ get { return (bool)GetValue(HasWebAddressProperty); }
+ set { SetValue(HasWebAddressProperty, value); }
+ }
+
+
+ public bool WebExampleVisibility {
+ get { return (bool)GetValue(WebExampleVisibilityProperty); }
+ }
+
+
+ private static void OnHasWebAddressChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
+ d.SetValue(
+ WebExampleVisibilityPropertyKey,
+ (bool)e.NewValue ? Visibility.Visible : Visibility.Collapsed
+ );
+
+ (d as ServerDataGrid)?.ApplyColumns();
+ }
+
+
+ public override void OnApplyTemplate() {
+ base.OnApplyTemplate();
+
+ _dataGrid = GetTemplateChild("PART_DataGrid") as DataGrid;
+ ApplyColumns();
+ }
+
+ private void ApplyColumns() {
+ if (_dataGrid is not null) {
+ _dataGrid.Columns.Clear();
+
+ _dataGrid.Columns.Add(
+ new DataGridTextColumn {
+ Width = new DataGridLength(1, DataGridLengthUnitType.Star),
+ Header = "HTTP URL",
+ Binding = new Binding(nameof(ServerListItem.Http)) {
+ UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged
+ }
+ }
+ );
+
+ _dataGrid.Columns.Add(
+ new DataGridTextColumn {
+ Width = new DataGridLength(1, DataGridLengthUnitType.Star),
+ Header = "SSH URL",
+ Binding = new Binding(nameof(ServerListItem.Ssh)) {
+ UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged
+ }
+ }
+ );
+
+ if (HasWebAddress) {
+ _dataGrid.Columns.Add(
+ new DataGridTextColumn {
+ Width = new DataGridLength(1, DataGridLengthUnitType.Star),
+ Header = "Web URL",
+ Binding = new Binding(nameof(ServerListItem.Web)) {
+ UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged
+ }
+ }
+ );
+ }
+ }
+ }
+
}
diff --git a/visual-studio/source/GitWebLinks/UI/Controls/ServerDataGrid.xaml b/visual-studio/source/GitWebLinks/UI/Controls/ServerDataGrid.xaml
index 53df5b2..94ab2fd 100644
--- a/visual-studio/source/GitWebLinks/UI/Controls/ServerDataGrid.xaml
+++ b/visual-studio/source/GitWebLinks/UI/Controls/ServerDataGrid.xaml
@@ -14,6 +14,7 @@
-
-
-
-
-
-
-
+ />
@@ -54,12 +40,15 @@
+
+
@@ -70,6 +59,10 @@
+
+
+
+
diff --git a/visual-studio/source/GitWebLinks/source.extension.cs b/visual-studio/source/GitWebLinks/source.extension.cs
index 90a1712..1b4b96c 100644
--- a/visual-studio/source/GitWebLinks/source.extension.cs
+++ b/visual-studio/source/GitWebLinks/source.extension.cs
@@ -11,7 +11,7 @@ internal sealed partial class Vsix
public const string Name = "Git Web Links";
public const string Description = @"Copy links to files in their online Git repositories.";
public const string Language = "en-US";
- public const string Version = "2.11.0";
+ public const string Version = "2.12.0";
public const string Author = "reduckted";
public const string Tags = "";
}
diff --git a/visual-studio/source/GitWebLinks/source.extension.vsixmanifest b/visual-studio/source/GitWebLinks/source.extension.vsixmanifest
index 8880199..1f613af 100644
--- a/visual-studio/source/GitWebLinks/source.extension.vsixmanifest
+++ b/visual-studio/source/GitWebLinks/source.extension.vsixmanifest
@@ -1,7 +1,7 @@
-
+
Git Web Links
Copy links to files in their online Git repositories.
https://github.com/reduckted/GitWebLinks
diff --git a/visual-studio/tests/GitWebLinks.UnitTests/Handlers/HandlerTests.cs b/visual-studio/tests/GitWebLinks.UnitTests/Handlers/HandlerTests.cs
index 2d8ce92..ce4edee 100644
--- a/visual-studio/tests/GitWebLinks.UnitTests/Handlers/HandlerTests.cs
+++ b/visual-studio/tests/GitWebLinks.UnitTests/Handlers/HandlerTests.cs
@@ -560,8 +560,8 @@ private void ApplySettings(Dictionary settings) {
private static List CreateStaticServers(JToken value) {
- return Convert(value, new[] { new { Http = "", Ssh = "" } })
- .Select((x) => new StaticServer(x.Http, x.Ssh))
+ return Convert(value, new[] { new { Http = "", Ssh = "", Web = "" } })
+ .Select((x) => new StaticServer(x.Http, x.Ssh, x.Web))
.ToList();
static T Convert(JToken value, T witness) => value.ToObject()!;
diff --git a/visual-studio/tests/GitWebLinks.UnitTests/Services/LinkHandlerTests.cs b/visual-studio/tests/GitWebLinks.UnitTests/Services/LinkHandlerTests.cs
index c3a3a6f..a54c196 100644
--- a/visual-studio/tests/GitWebLinks.UnitTests/Services/LinkHandlerTests.cs
+++ b/visual-studio/tests/GitWebLinks.UnitTests/Services/LinkHandlerTests.cs
@@ -243,7 +243,7 @@ public async Task ShouldHandleTheMatchingServerHttpAddressEndingWithSlash() {
"http://example.com | foo/bar",
await CreateUrlAsync(
new PartialHandlerDefinition {
- Server = new StaticServer("http://example.com/", ""),
+ Server = new StaticServer("http://example.com/", "", null),
Url = "{{ base }} | {{ repository }}"
},
new LinkTargetPreset(null)
@@ -262,7 +262,7 @@ public async Task ShouldHandleTheMatchingServerHttpAddressNotEndingWithSlash() {
"http://example.com | foo/bar",
await CreateUrlAsync(
new PartialHandlerDefinition {
- Server = new StaticServer("http://example.com", ""),
+ Server = new StaticServer("http://example.com", "", null),
Url = "{{ base }} | {{ repository }}"
},
new LinkTargetPreset(null)
@@ -281,7 +281,7 @@ public async Task ShouldHandleTheMatchingServerSshAddressEndingWithSlash() {
"http://example.com | foo/bar",
await CreateUrlAsync(
new PartialHandlerDefinition {
- Server = new StaticServer("http://example.com", "ssh://git@example.com/"),
+ Server = new StaticServer("http://example.com", "ssh://git@example.com/", null),
Url = "{{ base }} | {{ repository }}"
},
new LinkTargetPreset(null)
@@ -300,7 +300,7 @@ public async Task ShouldHandleTheMatchingServerSshAddressNotEndingWithSlash() {
"http://example.com | foo/bar",
await CreateUrlAsync(
new PartialHandlerDefinition {
- Server = new StaticServer("http://example.com", "ssh://git@example.com"),
+ Server = new StaticServer("http://example.com", "ssh://git@example.com", null),
Url = "{{ base }} | {{ repository }}"
},
new LinkTargetPreset(null)
@@ -319,7 +319,7 @@ public async Task ShouldHandleTheMatchingServerSshAddressNotEndingWithColon() {
"http://example.com | foo/bar",
await CreateUrlAsync(
new PartialHandlerDefinition {
- Server = new StaticServer("http://example.com/", "ssh://git@example.com"),
+ Server = new StaticServer("http://example.com/", "ssh://git@example.com", null),
Url = "{{ base }} | {{ repository }}"
},
new LinkTargetPreset(null)
@@ -338,7 +338,7 @@ public async Task ShouldHandleTheMatchingServerSshAddressEndingWithColon() {
"http://example.com | foo/bar",
await CreateUrlAsync(
new PartialHandlerDefinition {
- Server = new StaticServer("http://example.com/", "ssh://git@example.com:"),
+ Server = new StaticServer("http://example.com/", "ssh://git@example.com:", null),
Url = "{{ base }} | {{ repository }}"
},
new LinkTargetPreset(null)
@@ -357,7 +357,7 @@ public async Task ShouldTrimDotGitFromTheEndOfTheRepositoryPath() {
"http://example.com | foo/bar",
await CreateUrlAsync(
new PartialHandlerDefinition {
- Server = new StaticServer("http://example.com", ""),
+ Server = new StaticServer("http://example.com", "", null),
Url = "{{ base }} | {{ repository }}"
},
new LinkTargetPreset(null)
@@ -376,7 +376,7 @@ public async Task ShouldHandleSshUrlWithProtocol() {
"http://example.com",
await CreateUrlAsync(
new PartialHandlerDefinition {
- Server = new StaticServer("http://example.com/", "ssh://git@example.com"),
+ Server = new StaticServer("http://example.com/", "ssh://git@example.com", null),
Url = "{{ base }}"
},
new LinkTargetPreset(null)
@@ -395,7 +395,7 @@ public async Task ShouldHandleSshUrlWithoutProtocol() {
"http://example.com",
await CreateUrlAsync(
new PartialHandlerDefinition {
- Server = new StaticServer("http://example.com/", "git@example.com"),
+ Server = new StaticServer("http://example.com/", "git@example.com", null),
Url = "{{ base }}"
},
new LinkTargetPreset(null)
@@ -414,7 +414,7 @@ public async Task ShouldHandleSshWithGitAt() {
"http://example.com",
await CreateUrlAsync(
new PartialHandlerDefinition {
- Server = new StaticServer("http://example.com/", "git@example.com"),
+ Server = new StaticServer("http://example.com/", "git@example.com", null),
Url = "{{ base }}"
},
new LinkTargetPreset(null)
@@ -433,7 +433,7 @@ public async Task ShouldHandleSshWithoutGitAt() {
"http://example.com",
await CreateUrlAsync(
new PartialHandlerDefinition {
- Server = new StaticServer("http://example.com/", "example.com"),
+ Server = new StaticServer("http://example.com/", "example.com", null),
Url = "{{ base }}"
},
new LinkTargetPreset(null)
@@ -442,6 +442,26 @@ await CreateUrlAsync(
}
+ [Fact]
+ public async Task ShouldUseTheWebAddressFromTheMatchingServer() {
+ SetRemoteUrl("http://example.com/foo/bar");
+
+ await SetupRepositoryAsync(RootDirectory);
+
+ Assert.Equal(
+ "http://web.example.com | foo/bar",
+ await CreateUrlAsync(
+ new PartialHandlerDefinition {
+ Server = new StaticServer("http://example.com/", "", "http://web.example.com/"),
+ Url = "{{ base }} | {{ repository }}"
+ },
+ new LinkTargetPreset(null)
+ )
+ );
+ }
+
+
+
[Fact]
public async Task ShouldUseTheRealPathForFilesUnderDirectoryThatIsSymbolicLink() {
string real;
@@ -634,10 +654,10 @@ private LinkHandler CreateHandler(PartialHandlerDefinition definition) {
new Regex(""),
EmptyTemplate,
false,
- new ReverseServerSettings(EmptyTemplate, EmptyTemplate),
+ new ReverseServerSettings(EmptyTemplate, EmptyTemplate, null),
new ReverseSelectionSettings(EmptyTemplate, null, null, null)
),
- new[] { definition.Server ?? new StaticServer("http://example.com", "ssh://example.com") }
+ new[] { definition.Server ?? new StaticServer("http://example.com", "ssh://example.com", null) }
),
_settings,
Git
@@ -665,6 +685,9 @@ private class PartialHandlerDefinition {
public class GetUrlInfoAsyncMethod : RepositoryTestBase {
+ private StaticServer _server = new("http://example.com", "ssh://example.com", null);
+
+
static GetUrlInfoAsyncMethod() {
TemplateEngine.Initialize();
}
@@ -711,7 +734,7 @@ public async Task ShouldReturnTheInfoWhenThePatternMatchesTheUrl() {
Assert.Equal(
new UrlInfo(
"bar.txt",
- new StaticServer("http", "ssh"),
+ new StaticServer("http", "ssh", null),
new PartialSelectedRange(10, 20, 30, 40)),
await GetUrlInfoAsync(
new PartialReverseSettings {
@@ -719,7 +742,8 @@ await GetUrlInfoAsync(
File = "{{ match.groups.file }}",
Server = new ReverseServerSettings(
Template.Parse("http"),
- Template.Parse("ssh")
+ Template.Parse("ssh"),
+ null
),
Selection = new ReverseSelectionSettings(
Template.Parse("10"),
@@ -741,7 +765,7 @@ public async Task ShouldHandleInvalidSelectionProperties() {
Assert.Equal(
new UrlInfo(
"bar.txt",
- new StaticServer("http", "ssh"),
+ new StaticServer("http", "ssh", null),
new PartialSelectedRange(10, null, null, null)
),
await GetUrlInfoAsync(
@@ -749,15 +773,16 @@ await GetUrlInfoAsync(
Pattern = "http://example\\.com/[^/]+/(?.+)",
File = "{{ match.groups.file }}",
Server = new ReverseServerSettings(
- Template.Parse("http"),
- Template.Parse("ssh")
- ),
+ Template.Parse("http"),
+ Template.Parse("ssh"),
+ null
+ ),
Selection = new ReverseSelectionSettings(
- Template.Parse("10"),
- Template.Parse("x"),
- Template.Parse(""),
- null
- )
+ Template.Parse("10"),
+ Template.Parse("x"),
+ Template.Parse(""),
+ null
+ )
},
"http://example.com/foo/bar.txt",
false
@@ -772,7 +797,7 @@ public async Task ShouldProvideTheMatchingServerInfoToTheTemplates() {
Assert.Equal(
new UrlInfo(
"",
- new StaticServer("http://example.com", "example.com"),
+ new StaticServer("http://example.com", "example.com", null),
new PartialSelectedRange(null, null, null, null)
),
await GetUrlInfoAsync(
@@ -780,7 +805,8 @@ await GetUrlInfoAsync(
Pattern = "http://example\\.com/.+",
Server = new ReverseServerSettings(
Template.Parse("{{ http }}"),
- Template.Parse("{{ ssh }}")
+ Template.Parse("{{ ssh }}"),
+ null
)
},
"http://example.com/foo/bar.txt",
@@ -791,6 +817,36 @@ await GetUrlInfoAsync(
}
+ [Fact]
+ public async Task ShouldUseTheWebTemplateWhenThereIsOne() {
+ _server = new StaticServer("http://example.com", "ssh://example.com", "http://web.example.com");
+
+ Assert.Equal(
+ new UrlInfo(
+ "",
+ new StaticServer(
+ "http://example.com",
+ "example.com",
+ "http://web.example.com"
+ ),
+ new PartialSelectedRange(null, null, null, null)
+ ),
+ await GetUrlInfoAsync(
+ new PartialReverseSettings {
+ Pattern = "http://(web\\.)?example\\.com/.+",
+ Server = new ReverseServerSettings(
+ Template.Parse("{{ http }}"),
+ Template.Parse("{{ ssh }}"),
+ Template.Parse("{{ web }}")
+ )
+ },
+ "http://web.example.com/foo/bar.txt",
+ false
+ ),
+ UrlInfoComparer.Instance
+ );
+ }
+
private async Task GetUrlInfoAsync(PartialReverseSettings settings, string url, bool strict) {
return await CreateHandler(settings).GetUrlInfoAsync(url, strict);
@@ -810,10 +866,10 @@ private LinkHandler CreateHandler(PartialReverseSettings reverse) {
new Regex(reverse.Pattern ?? ""),
Template.Parse(reverse.File ?? ""),
false,
- reverse.Server ?? new ReverseServerSettings(EmptyTemplate, EmptyTemplate),
+ reverse.Server ?? new ReverseServerSettings(EmptyTemplate, EmptyTemplate, null),
reverse.Selection ?? new ReverseSelectionSettings(EmptyTemplate, null, null, null)
),
- new[] { new StaticServer("http://example.com", "ssh://example.com") }
+ new[] { _server }
),
Substitute.For(),
Git
diff --git a/visual-studio/tests/GitWebLinks.UnitTests/Types/RemoteServerTests.cs b/visual-studio/tests/GitWebLinks.UnitTests/Types/RemoteServerTests.cs
index c099d61..eea8211 100644
--- a/visual-studio/tests/GitWebLinks.UnitTests/Types/RemoteServerTests.cs
+++ b/visual-studio/tests/GitWebLinks.UnitTests/Types/RemoteServerTests.cs
@@ -5,329 +5,477 @@ namespace GitWebLinks;
public static class RemoteServerTests {
- public class SingleStaticServer {
+ public class SingleStaticServer : TestBase {
- private readonly RemoteServer _server = new(
- new StaticServer(
- "http://example.com:8000",
- "ssh://git@example.com:9000"
+ public SingleStaticServer() : base(
+ new(
+ new StaticServer(
+ "http://example.com:8000",
+ "ssh://git@example.com:9000",
+ null
+ )
)
- );
+ ) { }
[Fact]
public async Task ShouldReturnNullWhenThereIsNoMatch() {
- Assert.Null(await _server.MatchAsync("http://example.com:10000/foo/bar"));
+ Url = "http://example.com:10000/foo/bar";
+ await MatchAsync(null);
}
[Fact]
public async Task ShouldReturnTheServerWhenMatchingToTheHttpAddress() {
- Assert.Equal(
- new StaticServer("http://example.com:8000", "ssh://git@example.com:9000"),
- await _server.MatchAsync("http://example.com:8000/foo/bar"),
- StaticServerComparer.Instance
- );
+ Url = "http://example.com:8000/foo/bar";
+ await MatchAsync(new StaticServer("http://example.com:8000", "ssh://git@example.com:9000", null));
}
[Fact]
public async Task ShouldReturnTheServerwhenMatchingToTheSshAddressWithTheSshProtocol() {
- Assert.Equal(
- new StaticServer("http://example.com:8000", "ssh://git@example.com:9000"),
- await _server.MatchAsync("ssh://git@example.com:9000/foo/bar"),
- StaticServerComparer.Instance
+ Url = "ssh://git@example.com:9000/foo/bar";
+ await MatchAsync(
+ new StaticServer("http://example.com:8000", "ssh://git@example.com:9000", null),
+ null
);
}
[Fact]
public async Task ShouldReturnTheServerWhenMatchingToTheSshAddressWithoutTheSshProtocol() {
- Assert.Equal(
- new StaticServer("http://example.com:8000", "ssh://git@example.com:9000"),
- await _server.MatchAsync("git@example.com:9000/foo/bar"),
- StaticServerComparer.Instance
+ Url = "git@example.com:9000/foo/bar";
+ await MatchAsync(
+ new StaticServer("http://example.com:8000", "ssh://git@example.com:9000", null),
+ null
+ );
+ }
+
+
+ [Fact]
+ public async Task ShouldMatchTheWebAddressWhenThereIsAWebAddress() {
+ Server = new RemoteServer(
+ new StaticServer(
+ "http://example.com:8000",
+ "ssh://git@example.com:9000",
+ "http://other.com:8000"
+ )
+ );
+
+ Url = "http://other.com:8000/foo/bar";
+
+ await MatchAsync(
+ null,
+ new StaticServer(
+ "http://example.com:8000",
+ "ssh://git@example.com:9000",
+ "http://other.com:8000"
+ )
);
}
}
- public class MultipleStaticServers {
+ public class MultipleStaticServers : TestBase {
- private readonly RemoteServer _server = new(
- new IServer[] {
- new StaticServer("http://example.com:8000","ssh://git@example.com:9000"),
- new StaticServer("http://test.com:6000","ssh://git@test.com:7000")
- }
- );
+ public MultipleStaticServers() : base(
+ new(
+ new IServer[] {
+ new StaticServer("http://example.com:8000","ssh://git@example.com:9000", null),
+ new StaticServer("http://test.com:6000","ssh://git@test.com:7000", null)
+ }
+ )
+ ) { }
[Fact]
public async Task ShouldReturnNullWhenThereIsNoMatch() {
- Assert.Null(await _server.MatchAsync("http://example.com:10000/foo/bar"));
+ Url = "http://example.com:10000/foo/bar";
+ await MatchAsync(null);
}
[Fact]
public async Task ShouldReturnTheMatchingServerWhenMatchingToTheHttpAddress() {
- Assert.Equal(
- new StaticServer("http://example.com:8000", "ssh://git@example.com:9000"),
- await _server.MatchAsync("http://example.com:8000/foo/bar"),
- StaticServerComparer.Instance
+ Url = "http://example.com:8000/foo/bar";
+ await MatchAsync(
+ new StaticServer("http://example.com:8000", "ssh://git@example.com:9000", null)
);
- Assert.Equal(
- new StaticServer("http://test.com:6000", "ssh://git@test.com:7000"),
- await _server.MatchAsync("http://test.com:6000/foo/bar"),
- StaticServerComparer.Instance
+ Url = "http://test.com:6000/foo/bar";
+ await MatchAsync(
+ new StaticServer("http://test.com:6000", "ssh://git@test.com:7000", null)
);
}
[Fact]
public async Task ShouldReturnTheMatchingServerWhenMatchingToTheSshAddressWithTheSshProtocol() {
- Assert.Equal(
- new StaticServer("http://example.com:8000", "ssh://git@example.com:9000"),
- await _server.MatchAsync("ssh://git@example.com:9000/foo/bar"),
- StaticServerComparer.Instance
+ Url = "ssh://git@example.com:9000/foo/bar";
+ await MatchAsync(
+ new StaticServer("http://example.com:8000", "ssh://git@example.com:9000", null),
+ null
);
- Assert.Equal(
- new StaticServer("http://test.com:6000", "ssh://git@test.com:7000"),
- await _server.MatchAsync("ssh://git@test.com:7000/foo/bar"),
- StaticServerComparer.Instance
+ Url = "ssh://git@test.com:7000/foo/bar";
+ await MatchAsync(
+ new StaticServer("http://test.com:6000", "ssh://git@test.com:7000", null),
+ null
);
}
[Fact]
public async Task ShouldReturnTheMatchingServerWhenMatchingToTheSshAddressWithoutTheSshProtocol() {
- Assert.Equal(
- new StaticServer("http://example.com:8000", "ssh://git@example.com:9000"),
- await _server.MatchAsync("git@example.com:9000/foo/bar"),
- StaticServerComparer.Instance
+ Url = ("git@example.com:9000/foo/bar");
+ await MatchAsync(
+ new StaticServer("http://example.com:8000", "ssh://git@example.com:9000", null),
+ null
);
- Assert.Equal(
- new StaticServer("http://test.com:6000", "ssh://git@test.com:7000"),
- await _server.MatchAsync("git@test.com:7000/foo/bar"),
- StaticServerComparer.Instance
+ Url = ("git@test.com:7000/foo/bar");
+ await MatchAsync(
+ new StaticServer("http://test.com:6000", "ssh://git@test.com:7000", null),
+ null
);
}
+
+ [Fact]
+ public async Task ShouldMatchTheWebAddressWhenThereIsAWebAddress() {
+ Server = new RemoteServer(
+ new IServer[] {
+ new StaticServer(
+ "http://example.com:8000",
+ "ssh://git@example.com:9000",
+ "http://web.example.com"
+ ),
+ new StaticServer(
+ "http://test.com:6000",
+ "ssh://git@test.com:7000",
+ "http://web.test.com"
+ )
+ }
+ );
+
+ Url = "http://web.example.com/foo/bar";
+ await MatchAsync(
+ null,
+ new StaticServer(
+ "http://example.com:8000",
+ "ssh://git@example.com:9000",
+ "http://web.example.com"
+ )
+ );
+
+ Url = "http://web.test.com/foo/bar";
+ await MatchAsync(
+ null,
+ new StaticServer(
+ "http://test.com:6000",
+ "ssh://git@test.com:7000",
+ "http://web.test.com"
+ )
+ );
+ }
}
- public class SingleDynamicServer {
+ public class SingleDynamicServer : TestBase {
- private readonly RemoteServer _server = new(
- new DynamicServer(
- new Regex("http://(.+)\\.example\\.com:8000"),
- Template.Parse("http://example.com:8000/repos/{{ match[1] }}"),
- Template.Parse("ssh://git@example.com:9000/_{{ match[1] }}")
+ public SingleDynamicServer() : base(
+ new RemoteServer(
+ new DynamicServer(
+ new Regex("http://(.+)\\.example\\.com:8000"),
+ Template.Parse("http://example.com:8000/repos/{{ match[1] }}"),
+ Template.Parse("ssh://git@example.com:9000/_{{ match[1] }}"),
+ null,
+ null
+ )
)
- );
+ ) { }
[Fact]
public async Task ShouldReturnNullWhenThereIsNoMatch() {
- Assert.Null(await _server.MatchAsync("http://example.com:8000/foo/bar"));
+ Url = "http://example.com:8000/foo/bar";
+ await MatchAsync(null);
}
[Fact]
public async Task ShouldCreateTheDetailsOfTheMatchingServer() {
- Assert.Equal(
- new StaticServer("http://example.com:8000/repos/foo", "ssh://git@example.com:9000/_foo"),
- await _server.MatchAsync("http://foo.example.com:8000/bar/meep"),
- StaticServerComparer.Instance
+ Url = "http://foo.example.com:8000/bar/meep";
+ await MatchAsync(
+ new StaticServer("http://example.com:8000/repos/foo", "ssh://git@example.com:9000/_foo", null)
);
}
- }
-
-
- public class MultipleDynamicServers {
-
- private readonly RemoteServer _server = new(
- new IServer[] {
+ [Fact]
+ public async Task ShouldMatchTheWebAddressWhenThereIsAWebAddress() {
+ Server = new RemoteServer(
new DynamicServer(
new Regex("http://(.+)\\.example\\.com:8000"),
Template.Parse("http://example.com:8000/repos/{{ match[1] }}"),
- Template.Parse("ssh://git@example.com:9000/_{{ match[1] }}")
- ),
- new DynamicServer(
- new Regex("ssh://git@example\\.com:9000/_([^/]+)"),
- Template.Parse("http://example.com:8000/repos/{{ match[1] }}"),
- Template.Parse("ssh://git@example.com:9000/_{{ match[1] }}")
+ Template.Parse("ssh://git@example.com:9000/_{{ match[1] }}"),
+ new Regex("http://(.+)\\.test\\.com:8000"),
+ Template.Parse("http://test.com:8000/repos/{{ match[1] }}")
)
- }
- );
+ );
+
+ Url = "http://foo.test.com:8000/bar/meep";
+ await MatchAsync(
+ null,
+ new StaticServer(
+ "http://example.com:8000/repos/foo",
+ "ssh://git@example.com:9000/_foo",
+ "http://test.com:8000/repos/foo"
+ )
+ );
+ }
+
+ }
+
+
+ public class MultipleDynamicServers : TestBase {
+
+ public MultipleDynamicServers() : base(
+ new RemoteServer(
+ new IServer[] {
+ new DynamicServer(
+ new Regex("http://(.+)\\.example\\.com:8000"),
+ Template.Parse("http://example.com:8000/repos/{{ match[1] }}"),
+ Template.Parse("ssh://git@example.com:9000/_{{ match[1] }}"),
+ null,
+ null
+ ),
+ new DynamicServer(
+ new Regex("ssh://git@example\\.com:9000/_([^/]+)"),
+ Template.Parse("http://example.com:8000/repos/{{ match[1] }}"),
+ Template.Parse("ssh://git@example.com:9000/_{{ match[1] }}"),
+ new Regex("^$"), // This server should only match SSH remote URLs.
+ null
+ )
+ }
+ )
+ ) { }
[Fact]
public async Task ShouldReturnNullWhenThereIsNoMatch() {
- Assert.Null(await _server.MatchAsync("http://example.com:8000/foo/bar"));
+ Url = "http://example.com:8000/foo/bar";
+ await MatchAsync(null);
}
[Fact]
public async Task ShouldCreateTheDetailsOfTheMatchingServer() {
- Assert.Equal(
- new StaticServer("http://example.com:8000/repos/foo", "ssh://git@example.com:9000/_foo"),
- await _server.MatchAsync("http://foo.example.com:8000/bar/meep"),
- StaticServerComparer.Instance
+ Url = "http://foo.example.com:8000/bar/meep";
+ await MatchAsync(
+ new StaticServer("http://example.com:8000/repos/foo", "ssh://git@example.com:9000/_foo", null)
);
- Assert.Equal(
- new StaticServer("http://example.com:8000/repos/foo", "ssh://git@example.com:9000/_foo"),
- await _server.MatchAsync("ssh://git@example.com:9000/_foo/bar"),
- StaticServerComparer.Instance
+ Url = "ssh://git@example.com:9000/_foo/bar";
+ await MatchAsync(
+ new StaticServer("http://example.com:8000/repos/foo", "ssh://git@example.com:9000/_foo", null),
+ null
);
}
- }
+ [Fact]
+ public async Task ShouldMatchTheWebAddressWhenThereIsAWebAddress() {
+ Server = new RemoteServer(
+ new IServer[] {
+ new DynamicServer(
+ new Regex("http://(.+)\\.example\\.com:8000"),
+ Template.Parse("http://example.com:8000/repos/{{ match[1] }}"),
+ Template.Parse("ssh://git@example.com:9000/_{{ match[1] }}"),
+ new Regex("http://(.+)\\.test\\.com:8000"),
+ Template.Parse("http://test.com:8000/repos/{{ match[1] }}")
+ ),
+ new DynamicServer(
+ new Regex("ssh://git@example\\.com:9000/_([^/]+)"),
+ Template.Parse("http://example.com:8000/repos/{{ match[1] }}"),
+ Template.Parse("ssh://git@example.com:9000/_{{ match[1] }}"),
+ new Regex("http://(.+)\\.other\\.com:8000"),
+ Template.Parse("http://other.com:8000/repos/{{ match[1] }}")
+ )
+ }
+ );
- public class MixedStaticAndDynamicServers {
+ Url = "http://foo.test.com:8000/bar/meep";
+ await MatchAsync(
+ null,
+ new StaticServer(
+ "http://example.com:8000/repos/foo",
+ "ssh://git@example.com:9000/_foo",
+ "http://test.com:8000/repos/foo"
+ )
+ );
- private readonly RemoteServer _server = new(
- new IServer[] {
- new DynamicServer(
- new Regex("http://(.+)\\.example\\.com:8000"),
- Template.Parse("http://example.com:8000/repos/{{ match[1] }}"),
- Template.Parse("ssh://git@example.com:9000/_{{ match[1] }}")
- ),
+ Url = "http://foo.other.com:8000/bar/meep";
+ await MatchAsync(
+ null,
new StaticServer(
- "http://example.com:10000",
- "ssh://git@example.com:11000"
+ "http://example.com:8000/repos/foo",
+ "ssh://git@example.com:9000/_foo",
+ "http://other.com:8000/repos/foo"
)
- }
- );
+ );
+ }
+
+ }
+
+
+ public class MixedStaticAndDynamicServers : TestBase {
+
+ public MixedStaticAndDynamicServers() : base(
+ new RemoteServer(
+ new IServer[] {
+ new DynamicServer(
+ new Regex("http://(.+)\\.example\\.com:8000"),
+ Template.Parse("http://example.com:8000/repos/{{ match[1] }}"),
+ Template.Parse("ssh://git@example.com:9000/_{{ match[1] }}"),
+ null,
+ null
+ ),
+ new StaticServer(
+ "http://example.com:10000",
+ "ssh://git@example.com:11000",
+ null
+ )
+ }
+ )
+ ) { }
[Fact]
public async Task ShouldReturnNullWhenThereIsNoMatch() {
- Assert.Null(await _server.MatchAsync("http://example.com:7000/foo/bar"));
+ Url = "http://example.com:7000/foo/bar";
+ await MatchAsync(null);
}
[Fact]
public async Task ShouldReturnTheMatchingServerWhenMatchingToTheStaticServer() {
- Assert.Equal(
- new StaticServer("http://example.com:10000", "ssh://git@example.com:11000"),
- await _server.MatchAsync("http://example.com:10000/foo/bar"),
- StaticServerComparer.Instance
+ Url = "http://example.com:10000/foo/bar";
+ await MatchAsync(
+ new StaticServer("http://example.com:10000", "ssh://git@example.com:11000", null)
);
}
[Fact]
public async Task ShouldCreateTheDetailsOfTheMatchingServerWhenMatchingToTheDynamicServer() {
- Assert.Equal(
- new StaticServer("http://example.com:8000/repos/foo", "ssh://git@example.com:9000/_foo"),
- await _server.MatchAsync("http://foo.example.com:8000/bar/meep"),
- StaticServerComparer.Instance
+ Url = "http://foo.example.com:8000/bar/meep";
+ await MatchAsync(
+ new StaticServer("http://example.com:8000/repos/foo", "ssh://git@example.com:9000/_foo", null)
);
}
}
- public class StaticServerFactory {
+ public class StaticServerFactory : TestBase {
private IEnumerable _source;
- private readonly RemoteServer _server;
- public StaticServerFactory() {
+ public StaticServerFactory() : base(new RemoteServer(new StaticServer("", null, null))) {
_source = new[] {
- new StaticServer("http://example.com:8000","ssh://git@example.com:9000"),
- new StaticServer("http://test.com:6000","ssh://git@test.com:7000")
+ new StaticServer("http://example.com:8000","ssh://git@example.com:9000", null),
+ new StaticServer("http://test.com:6000","ssh://git@test.com:7000", "http://web.test.com")
};
-
- _server = new RemoteServer(() => Task.FromResult(_source));
+ Server = new RemoteServer(() => Task.FromResult(_source));
}
[Fact]
public async Task ShouldReturnNullWhenThereIsNoMatch() {
- Assert.Null(await _server.MatchAsync("http://example.com:9000/foo/bar"));
+ Url = "http://example.com:9000/foo/bar";
+ await MatchAsync(null);
}
[Fact]
public async Task ShouldReturnTheMatchingServerWhenMatchingToTheHttpAddress() {
- Assert.Equal(
- new StaticServer("http://example.com:8000", "ssh://git@example.com:9000"),
- await _server.MatchAsync("http://example.com:8000/foo/bar"),
- StaticServerComparer.Instance
+ Url = "http://example.com:8000/foo/bar";
+ await MatchAsync(
+ new StaticServer("http://example.com:8000", "ssh://git@example.com:9000", null)
);
- Assert.Equal(
- new StaticServer("http://test.com:6000", "ssh://git@test.com:7000"),
- await _server.MatchAsync("http://test.com:6000/foo/bar"),
- StaticServerComparer.Instance
+ Url = "http://test.com:6000/foo/bar";
+ await MatchAsync(
+ new StaticServer("http://test.com:6000", "ssh://git@test.com:7000", "http://web.test.com"),
+ null
+ );
+
+ Url = "http://web.test.com/foo/bar";
+ await MatchAsync(
+ null,
+ new StaticServer(
+ "http://test.com:6000",
+ "ssh://git@test.com:7000",
+ "http://web.test.com"
+ )
);
}
[Fact]
public async Task ShouldReturnTheMatchingServerWhenMatchingToTheSshAddress() {
- Assert.Equal(
- new StaticServer("http://example.com:8000", "ssh://git@example.com:9000"),
- await _server.MatchAsync("ssh://git@example.com:9000/foo/bar"),
- StaticServerComparer.Instance
+ Url = "ssh://git@example.com:9000/foo/bar";
+ await MatchAsync(
+ new StaticServer("http://example.com:8000", "ssh://git@example.com:9000", null),
+ null
);
- Assert.Equal(
- new StaticServer("http://test.com:6000", "ssh://git@test.com:7000"),
- await _server.MatchAsync("ssh://git@test.com:7000/foo/bar"),
- StaticServerComparer.Instance
+ Url = "ssh://git@test.com:7000/foo/bar";
+ await MatchAsync(
+ new StaticServer("http://test.com:6000", "ssh://git@test.com:7000", "http://web.test.com"),
+ null
);
}
[Fact]
public async Task ShouldReturnTheMatchingServerWhenTheRemoteUrlIsAnHttpAddresAndTheServerHasNoSshUrl() {
- _source = new[] { new StaticServer("http://example.com:8000", null) };
+ _source = new[] { new StaticServer("http://example.com:8000", null, null) };
- Assert.Equal(
- new StaticServer("http://example.com:8000", null),
- await _server.MatchAsync("http://example.com:8000/foo/bar"),
- StaticServerComparer.Instance
+ Url = "http://example.com:8000/foo/bar";
+ await MatchAsync(
+ new StaticServer("http://example.com:8000", null, null)
);
}
[Fact]
public async Task ShouldNotReturnMatchWhenTheRemoteUrlIsAnSshAddressAndTheServerHNoSshURL() {
- _source = new[] { new StaticServer("http://example.com:8000", null) };
+ _source = new[] { new StaticServer("http://example.com:8000", null, null) };
- Assert.Null(await _server.MatchAsync("ssh://git@test.com:7000/foo/bar"));
+ Url = "ssh://git@test.com:7000/foo/bar";
+ await MatchAsync(null);
}
[Fact]
public async Task ShouldNotCacheTheServersReturnedFromTheFactory() {
- Assert.Equal(
- new StaticServer("http://example.com:8000", "ssh://git@example.com:9000"),
- await _server.MatchAsync("http://example.com:8000/foo/bar"),
- StaticServerComparer.Instance
+ Url = "http://example.com:8000/foo/bar";
+ await MatchAsync(
+ new StaticServer("http://example.com:8000", "ssh://git@example.com:9000", null)
);
- _source = new[] { new StaticServer("http://test.com:6000", "ssh://git@test.com:7000") };
+ _source = new[] { new StaticServer("http://test.com:6000", "ssh://git@test.com:7000", null) };
- Assert.Null(await _server.MatchAsync("http://example.com:8000/foo/bar"));
+ await MatchAsync(null);
- _source = new[] { new StaticServer("http://example.com:8000", "ssh://git@example.com:9000") };
+ _source = new[] { new StaticServer("http://example.com:8000", "ssh://git@example.com:9000", null) };
- Assert.Equal(
- new StaticServer("http://example.com:8000", "ssh://git@example.com:9000"),
- await _server.MatchAsync("http://example.com:8000/foo/bar"),
- StaticServerComparer.Instance
+ await MatchAsync(
+ new StaticServer("http://example.com:8000", "ssh://git@example.com:9000", null)
);
}
@@ -359,4 +507,42 @@ public int GetHashCode(StaticServer? obj) {
}
+
+ public abstract class TestBase {
+
+ protected TestBase(RemoteServer defaultServer) {
+ Server = defaultServer;
+ }
+
+
+ protected RemoteServer Server { get; set; }
+
+
+ protected string Url { get; set; } = "";
+
+
+ protected async Task MatchAsync(StaticServer? expectedMatch) {
+ await MatchAsync(expectedMatch, expectedMatch);
+ }
+
+
+ protected async Task MatchAsync(
+ StaticServer? expectedRemoteMatch,
+ StaticServer? expectedWebMatch
+ ) {
+ Assert.Equal(
+ expectedRemoteMatch,
+ await Server.MatchRemoteUrlAsync(Url),
+ StaticServerComparer.Instance
+ );
+
+ Assert.Equal(
+ expectedWebMatch,
+ await Server.MatchWebUrlAsync(Url),
+ StaticServerComparer.Instance
+ );
+ }
+
+ }
+
}
diff --git a/vscode/package-lock.json b/vscode/package-lock.json
index 9c6178d..e192ed8 100644
--- a/vscode/package-lock.json
+++ b/vscode/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "vscode-gitweblinks",
- "version": "2.11.0",
+ "version": "2.12.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "vscode-gitweblinks",
- "version": "2.11.0",
+ "version": "2.12.0",
"license": "MIT",
"dependencies": {
"liquidjs": "10.16.7",
diff --git a/vscode/package.json b/vscode/package.json
index 47e4f38..4799c2b 100644
--- a/vscode/package.json
+++ b/vscode/package.json
@@ -2,7 +2,7 @@
"name": "vscode-gitweblinks",
"displayName": "Git Web Links for VS Code",
"description": "Copy links to files in their online Git repositories",
- "version": "2.11.0",
+ "version": "2.12.0",
"publisher": "reduckted",
"homepage": "https://github.com/reduckted/GitWebLinks",
"repository": {
@@ -340,6 +340,10 @@
"ssh": {
"type": "string",
"description": "The SSH URL for remotes. For example:\nssh://git.mygitiles.com:29418"
+ },
+ "web": {
+ "type": "string",
+ "description": "The URL for the web interface if it is different to the `http` URL used for cloning. For example:\nhttps://mygitiles.com/plugins/gitiles"
}
},
"required": [
diff --git a/vscode/src/commands/go-to-file-command.ts b/vscode/src/commands/go-to-file-command.ts
index b6d5699..08531d9 100644
--- a/vscode/src/commands/go-to-file-command.ts
+++ b/vscode/src/commands/go-to-file-command.ts
@@ -241,7 +241,7 @@ export class GoToFileCommand {
// be because the remote URLs that we determined aren't quite correct,
// or perhaps the URL comes from a fork of the repository.
//
- // We'll use the existance of the URI in the repository to determine
+ // We'll use the existence of the URI in the repository to determine
// whether this repository *could* be a match. If the URI does not exist
// in the repository, this this repository is not a match for the URI.
//
@@ -274,7 +274,7 @@ export class GoToFileCommand {
*/
private isMatchingRepository(repository: Repository, server: StaticServer): boolean {
if (repository.remote) {
- if (new RemoteServer(server).match(repository.remote.url)) {
+ if (new RemoteServer(server).matchRemoteUrl(repository.remote.url)) {
return true;
}
}
diff --git a/vscode/src/link-handler-provider.ts b/vscode/src/link-handler-provider.ts
index aa53431..3cc45bf 100644
--- a/vscode/src/link-handler-provider.ts
+++ b/vscode/src/link-handler-provider.ts
@@ -30,7 +30,7 @@ export class LinkHandlerProvider {
for (let handler of this.handlers) {
log("Testing '%s'.", handler.name);
- if (handler.isMatch(repository.remote.url)) {
+ if (handler.handlesRemoteUrl(repository.remote.url)) {
log("Handler '%s' is a match.", handler.name);
return handler;
}
diff --git a/vscode/src/link-handler.ts b/vscode/src/link-handler.ts
index 43a8197..f15afe5 100644
--- a/vscode/src/link-handler.ts
+++ b/vscode/src/link-handler.ts
@@ -17,6 +17,7 @@ import {
FileInfo,
LinkOptions,
LinkType,
+ Mutable,
RepositoryWithRemote,
SelectedRange,
UrlInfo
@@ -72,7 +73,10 @@ export class LinkHandler {
file: parseTemplate(definition.reverse.file),
server: {
http: parseTemplate(definition.reverse.server.http),
- ssh: parseTemplate(definition.reverse.server.ssh)
+ ssh: parseTemplate(definition.reverse.server.ssh),
+ web: definition.reverse.server.web
+ ? parseTemplate(definition.reverse.server.web)
+ : undefined
},
selection: {
startLine: parseTemplate(definition.reverse.selection.startLine),
@@ -96,8 +100,8 @@ export class LinkHandler {
* @param remoteUrl The remote URL to check.
* @returns True if this handler handles the given remote URL; otherwise, false.
*/
- public isMatch(remoteUrl: string): boolean {
- return this.server.match(normalizeUrl(remoteUrl)) !== undefined;
+ public handlesRemoteUrl(remoteUrl: string): boolean {
+ return this.server.matchRemoteUrl(normalizeUrl(remoteUrl)) !== undefined;
}
/**
@@ -150,7 +154,7 @@ export class LinkHandler {
relativePath = await this.getRelativePath(repository.root, file.filePath);
data = {
- base: address.http,
+ base: address.web ?? address.http,
repository: this.getRepositoryPath(remote, address),
ref,
commit: await this.getRef('commit', repository),
@@ -214,7 +218,7 @@ export class LinkHandler {
private getAddress(remote: string): StaticServer {
let address: StaticServer | undefined;
- address = this.server.match(remote);
+ address = this.server.matchRemoteUrl(remote);
if (!address) {
throw new Error('Could not find a matching address.');
@@ -232,11 +236,13 @@ export class LinkHandler {
private normalizeServerUrls(address: StaticServer): StaticServer {
let http: string;
let ssh: string | undefined;
+ let web: string | undefined;
http = normalizeUrl(address.http);
ssh = address.ssh ? normalizeUrl(address.ssh) : undefined;
+ web = address.web ? normalizeUrl(address.web) : undefined;
- return { http, ssh };
+ return { http, ssh, web };
}
/**
@@ -385,7 +391,7 @@ export class LinkHandler {
private async getRelativePath(from: string, to: string): Promise {
// If the file is a symbolic link, or is under a directory that's a
// symbolic link, then we want to resolve the path to the real file
- // because the sybmolic link won't be in the Git repository.
+ // because the symbolic link won't be in the Git repository.
if (await this.isSymbolicLink(to, from)) {
try {
to = await fs.realpath(to);
@@ -457,16 +463,16 @@ export class LinkHandler {
/**
* Gets information about the given URL.
*
- * @param url The URL to get the information from.
+ * @param webUrl The web interface URL to get the information from.
* @param strict Whether to require the URL to match the server address of the handler.
* @returns The URL information, or `undefined` if the information could not be determined.
*/
- public getUrlInfo(url: string, strict: boolean): UrlInfo | undefined {
+ public getUrlInfo(webUrl: string, strict: boolean): UrlInfo | undefined {
let address: StaticServer | undefined;
let match: RegExpExecArray | null;
// See if the URL matches the server address for the handler.
- address = this.server.match(url);
+ address = this.server.matchWebUrl(webUrl);
// If we are performing a strict match, then the
// URL must match to this handler's server.
@@ -478,18 +484,19 @@ export class LinkHandler {
address = this.normalizeServerUrls(address);
}
- match = this.reverse.pattern.exec(url);
+ match = this.reverse.pattern.exec(webUrl);
if (match) {
let data: FileData;
let file: string;
- let server: StaticServer;
+ let server: Mutable;
let selection: Partial;
data = {
match,
http: address?.http,
- ssh: address?.ssh
+ ssh: address?.ssh,
+ web: address?.web
};
file = this.reverse.file.render(data);
@@ -499,6 +506,10 @@ export class LinkHandler {
ssh: this.reverse.server.ssh.render(data)
};
+ if (this.reverse.server.web) {
+ server.web = this.reverse.server.web.render(data);
+ }
+
selection = {
startLine: this.tryParseNumber(this.reverse.selection.startLine.render(data)),
endLine: this.tryParseNumber(this.reverse.selection.endLine?.render(data)),
@@ -599,6 +610,8 @@ interface FileData {
readonly http?: string;
readonly ssh?: string;
+
+ readonly web?: string;
}
/**
diff --git a/vscode/src/remote-server.ts b/vscode/src/remote-server.ts
index 0691cee..444e2bb 100644
--- a/vscode/src/remote-server.ts
+++ b/vscode/src/remote-server.ts
@@ -1,6 +1,7 @@
import { log } from './log';
import { DynamicServer, StaticServer } from './schema';
import { ParsedTemplate, parseTemplate } from './templates';
+import { Mutable } from './types';
import { normalizeUrl } from './utilities';
/**
@@ -31,17 +32,41 @@ export class RemoteServer {
}
}
+ /**
+ * Tests if this server is a match for the given remote URL.
+ *
+ * @param remoteUrl The remote URL to test against.
+ * @returns The server address if the URL is a match; otherwise, `undefined`.
+ */
+ public matchRemoteUrl(remoteUrl: string): StaticServer | undefined {
+ return this.matchUrl(remoteUrl, (x) => x.remote);
+ }
+
+ /**
+ * Tests if this server is a match for the given web interface URL.
+ *
+ * @param webUrl The web interface URL to test against.
+ * @returns The server address if the URL is a match; otherwise, `undefined`.
+ */
+ public matchWebUrl(webUrl: string): StaticServer | undefined {
+ return this.matchUrl(webUrl, (x) => x.web);
+ }
+
/**
* Tests if this server is a match for the given URL.
*
* @param url The URL to test against.
+ * @param selectUrlMatcher A function to select the matcher to use from a `Matcher`.
* @returns The server address if the URL is a match; otherwise, `undefined`.
*/
- public match(url: string): StaticServer | undefined {
+ private matchUrl(
+ url: string,
+ selectUrlMatcher: (matcher: Matcher) => UrlMatcher
+ ): StaticServer | undefined {
for (let matcher of this.matchers) {
let server: StaticServer | undefined;
- server = matcher(url);
+ server = selectUrlMatcher(matcher)(url);
if (server) {
return server;
@@ -59,7 +84,7 @@ export class RemoteServer {
* @returns The matcher function.
*/
function createMatcher(server: StaticServer | DynamicServer): Matcher {
- if ('pattern' in server) {
+ if ('remotePattern' in server) {
return createDynamicServerMatcher(server);
} else {
return createStaticServerMatcher(server);
@@ -73,40 +98,78 @@ function createMatcher(server: StaticServer | DynamicServer): Matcher {
* @returns The matcher for the server.
*/
function createDynamicServerMatcher(server: DynamicServer): Matcher {
- let pattern: RegExp;
+ let remotePattern: RegExp;
+ let webPattern: RegExp;
let httpTemplate: ParsedTemplate;
let sshTemplate: ParsedTemplate;
+ let webTemplate: ParsedTemplate | undefined;
- // The pattern is a regular expression, so parse
- // it once instead of each time we execute.
+ // The patterns are regular expressions, so parse
+ // them once instead of each time we execute.
try {
- pattern = new RegExp(server.pattern);
+ remotePattern = new RegExp(server.remotePattern);
} catch (ex) {
- log("Invalid dynamic server pattern '%s': %s", server.pattern, ex);
- return () => undefined;
+ log("Invalid dynamic server remote pattern '%s': %s", server.remotePattern, ex);
+ return { remote: () => undefined, web: () => undefined };
+ }
+
+ if (server.webPattern) {
+ try {
+ webPattern = new RegExp(server.webPattern);
+ } catch (ex) {
+ log("Invalid dynamic server web pattern '%s': %s", server.remotePattern, ex);
+ return { remote: () => undefined, web: () => undefined };
+ }
+ } else {
+ webPattern = remotePattern;
}
// Parse the templates now so we don't
// have to do it each time we execute.
httpTemplate = parseTemplate(server.http);
sshTemplate = parseTemplate(server.ssh);
+ webTemplate = server.web ? parseTemplate(server.web) : undefined;
- return (url) => {
- let match: RegExpMatchArray | null;
+ return {
+ remote: create(remotePattern),
+ web: create(webPattern)
+ };
- match = pattern.exec(url);
+ /**
+ * Creates a matcher function.
+ *
+ * @param pattern The pattern to test with.
+ * @returns The matcher function.
+ */
+ function create(pattern: RegExp): Matcher['remote'] {
+ return (url) => {
+ let match: RegExpMatchArray | null;
- if (match) {
- // The URL matched the pattern. Render the templates to get the HTTP
- // and SSH URLs, making the match available for the templates to use.
- return {
- http: httpTemplate.render({ match }),
- ssh: sshTemplate.render({ match })
- };
- }
+ match = pattern.exec(url);
- return undefined;
- };
+ if (match) {
+ let http: string;
+ let server: Mutable;
+
+ http = httpTemplate.render({ match });
+
+ // The URL matched the pattern. Render the templates to get the
+ // URLs, making the match available for the templates to use.
+ server = {
+ http,
+ ssh: sshTemplate.render({ match })
+ };
+
+ if (webTemplate) {
+ server.web = webTemplate?.render({ match });
+ }
+
+ return server;
+ }
+
+ return undefined;
+ };
+ }
}
/**
@@ -116,30 +179,44 @@ function createDynamicServerMatcher(server: DynamicServer): Matcher {
* @returns The matcher function.
*/
function createStaticServerMatcher(server: StaticServer): Matcher {
- return (url) => (isMatch(url, server) ? server : undefined);
+ return {
+ remote: (url) => (isRemoteMatch(url, server) ? server : undefined),
+ web: (url) => (isWebMatch(url, server) ? server : undefined)
+ };
}
/**
- * Determines whether the given URL matches the given server definition.
+ * Determines whether the given remote URL matches the given server definition.
*
- * @param url The URL.
+ * @param remoteUrl The remote URL.
* @param server The server definition.
* @returns True if the URL matches the server; otherwise, false.
*/
-function isMatch(url: string, server: StaticServer): boolean {
- url = normalizeUrl(url);
+function isRemoteMatch(remoteUrl: string, server: StaticServer): boolean {
+ remoteUrl = normalizeUrl(remoteUrl);
- if (url.startsWith(normalizeUrl(server.http))) {
+ if (remoteUrl.startsWith(normalizeUrl(server.http))) {
return true;
}
- if (server.ssh && url.startsWith(normalizeUrl(server.ssh))) {
+ if (server.ssh && remoteUrl.startsWith(normalizeUrl(server.ssh))) {
return true;
}
return false;
}
+/**
+ * Determines whether the given web interface URL matches the given server definition.
+ *
+ * @param webUrl The web interface URL.
+ * @param server The server definition.
+ * @returns True if the URL matches the server; otherwise, false.
+ */
+function isWebMatch(webUrl: string, server: StaticServer): boolean {
+ return normalizeUrl(webUrl).startsWith(normalizeUrl(server.web ?? server.http));
+}
+
/**
* Creates a matcher function that fetches the static server definitions when invoked.
*
@@ -147,21 +224,36 @@ function isMatch(url: string, server: StaticServer): boolean {
* @returns The matcher function.
*/
function createLazyStaticServerMatcher(factory: StaticServerFactory): Matcher {
- return (url) => {
- let servers: StaticServer[];
-
- servers = factory();
+ return {
+ remote: create(isRemoteMatch),
+ web: create(isWebMatch)
+ };
- for (let server of servers) {
- if (isMatch(url, server)) {
- return server;
+ /**
+ * Creates a matcher function.
+ *
+ * @param test The function to test a match.
+ * @returns The matcher function.
+ */
+ function create(test: typeof isWebMatch | typeof isRemoteMatch): Matcher['remote'] {
+ return (url) => {
+ for (let server of factory()) {
+ if (test(url, server)) {
+ return server;
+ }
}
- }
- return undefined;
- };
+ return undefined;
+ };
+ }
}
type StaticServerFactory = () => StaticServer[];
-type Matcher = (url: string) => StaticServer | undefined;
+type UrlMatcher = (url: string) => StaticServer | undefined;
+
+interface Matcher {
+ readonly remote: UrlMatcher;
+
+ readonly web: UrlMatcher;
+}
diff --git a/vscode/src/schema.ts b/vscode/src/schema.ts
index 5815265..3294f5e 100644
--- a/vscode/src/schema.ts
+++ b/vscode/src/schema.ts
@@ -125,14 +125,20 @@ export interface ReverseSettings {
*/
export interface ReverseServerSettings {
/**
- * The template to produce the HTTP server URL.
+ * The template to produce the HTTP remote URL.
*/
readonly http: Template;
/**
- * The template to produce the SSH server URL.
+ * The template to produce the SSH remote URL.
*/
readonly ssh: Template;
+
+ /**
+ * The template to produce the URL for the web interface.
+ * When this is not specified, the `http` template is used.
+ */
+ readonly web?: Template | undefined;
}
/**
@@ -175,14 +181,20 @@ export type Server = StaticServer | DynamicServer[];
*/
export interface StaticServer {
/**
- * The HTTP(S) URL of the remote server.
+ * The HTTP(S) remote URL.
*/
readonly http: string;
/**
- * The SSH URL of the remote server.
+ * The SSH remote URL.
*/
readonly ssh: string | undefined;
+
+ /**
+ * The HTTP(S) URL of the web interface. When not specified,
+ * the `http` property defines the URL of the web interface.
+ */
+ readonly web?: string | undefined;
}
/**
@@ -192,17 +204,27 @@ export interface DynamicServer {
/**
* A regular expression to match on a remote URL.
*/
- readonly pattern: string;
+ readonly remotePattern: string;
/**
- * The template to build the HTTP(S) URL of the remote server.
+ * The template to build the HTTP(S) remote URL.
*/
readonly http: Template;
/**
- * The template to build the SSH URL of the remote server.
+ * The template to build the SSH remote URL.
*/
readonly ssh: Template;
+
+ /**
+ * A regular expression to match on a web interface URL.
+ */
+ readonly webPattern?: string | undefined;
+
+ /**
+ * The template to build the URL of the web interface.
+ */
+ readonly web?: Template | undefined;
}
/**
diff --git a/vscode/src/types.ts b/vscode/src/types.ts
index 9cc236d..0c75344 100644
--- a/vscode/src/types.ts
+++ b/vscode/src/types.ts
@@ -147,3 +147,10 @@ export interface UrlInfo {
*/
selection: Partial;
}
+
+/**
+ * Makes all properties on an object read/write.
+ */
+export type Mutable = {
+ -readonly [P in keyof T]: T[P];
+};
diff --git a/vscode/test/link-handler.test.ts b/vscode/test/link-handler.test.ts
index 15acf61..36442ce 100644
--- a/vscode/test/link-handler.test.ts
+++ b/vscode/test/link-handler.test.ts
@@ -6,7 +6,7 @@ import * as sinon from 'sinon';
import { git } from '../src/git';
import { LinkHandler } from '../src/link-handler';
import { NoRemoteHeadError } from '../src/no-remote-head-error';
-import { HandlerDefinition, ReverseSettings } from '../src/schema';
+import { HandlerDefinition, ReverseSettings, StaticServer } from '../src/schema';
import { Settings } from '../src/settings';
import { LinkOptions, LinkType, RepositoryWithRemote, UrlInfo } from '../src/types';
import { isErrorCode } from '../src/utilities';
@@ -368,6 +368,26 @@ describe('LinkHandler', function () {
).to.equal('http://example.com');
});
+ it('should use the web address from the matching server.', async () => {
+ repository = {
+ ...repository,
+ remote: { url: 'http://example.com/foo/bar', name: 'origin' }
+ };
+
+ await setupRepository(root.path);
+
+ expect(
+ await createUrl({
+ server: {
+ http: 'http://example.com/',
+ ssh: '',
+ web: 'http://web.example.com/'
+ },
+ url: '{{ base }} | {{ repository }}'
+ })
+ ).to.equal('http://web.example.com | foo/bar');
+ });
+
it(`should use the real path for files under a directory that is a symbolic link.`, async function () {
let real: string;
let link: string;
@@ -563,6 +583,12 @@ describe('LinkHandler', function () {
});
describe('getUrlInfo', () => {
+ let server: StaticServer;
+
+ beforeEach(() => {
+ server = { http: 'http://example.com', ssh: 'ssh://example.com' };
+ });
+
it('should return undefined in strict mode when the URL does not match the server.', () => {
expect(getUrlInfo({ pattern: '.+' }, 'http://different.com/foo/bar.txt', true)).to.be
.undefined;
@@ -647,6 +673,29 @@ describe('LinkHandler', function () {
});
});
+ it('should use the web template when there is one.', () => {
+ server = {
+ http: 'http://example.com',
+ ssh: 'ssh://example.com',
+ web: 'http://web.example.com'
+ };
+
+ expect(
+ getUrlInfo(
+ {
+ pattern: 'http://(web\\.)?example\\.com/.+',
+ server: { http: '{{ http }}', ssh: '{{ ssh }}', web: '{{ web }}' }
+ },
+ 'http://web.example.com/foo/bar.txt',
+ false
+ )?.server
+ ).to.deep.equal({
+ http: 'http://example.com',
+ ssh: 'example.com',
+ web: 'http://web.example.com'
+ });
+ });
+
function getUrlInfo(
settings: Partial,
url: string,
@@ -658,7 +707,7 @@ describe('LinkHandler', function () {
function createHandler(reverse: Partial): LinkHandler {
return new LinkHandler({
name: 'Test',
- server: { http: 'http://example.com', ssh: 'ssh://example.com' },
+ server,
branchRef: 'abbreviated',
url: '',
selection: '',
diff --git a/vscode/test/remote-server.test.ts b/vscode/test/remote-server.test.ts
index a90d07a..4679340 100644
--- a/vscode/test/remote-server.test.ts
+++ b/vscode/test/remote-server.test.ts
@@ -5,6 +5,7 @@ import { StaticServer } from '../src/schema';
describe('RemoteServer', () => {
let server: RemoteServer;
+ let url: string;
describe('single static server', () => {
beforeEach(() => {
@@ -15,27 +16,43 @@ describe('RemoteServer', () => {
});
it('should return undefined when there is no match.', () => {
- expect(server.match('http://example.com:10000/foo/bar')).to.be.undefined;
+ url = 'http://example.com:10000/foo/bar';
+ match(undefined, undefined);
});
it('should return the server when matching to the HTTP address.', () => {
- expect(server.match('http://example.com:8000/foo/bar')).to.deep.equal({
- http: 'http://example.com:8000',
- ssh: 'ssh://git@example.com:9000'
- });
+ url = 'http://example.com:8000/foo/bar';
+ match({ http: 'http://example.com:8000', ssh: 'ssh://git@example.com:9000' }, 'same');
});
it('should return the server when matching to the SSH address with the SSH protocol.', () => {
- expect(server.match('ssh://git@example.com:9000/foo/bar')).to.deep.equal({
- http: 'http://example.com:8000',
- ssh: 'ssh://git@example.com:9000'
- });
+ url = 'ssh://git@example.com:9000/foo/bar';
+ match(
+ { http: 'http://example.com:8000', ssh: 'ssh://git@example.com:9000' },
+ undefined
+ );
});
it('should return the server when matching to the SSH address without the SSH protocol.', () => {
- expect(server.match('git@example.com:9000/foo/bar')).to.deep.equal({
+ url = 'git@example.com:9000/foo/bar';
+ match(
+ { http: 'http://example.com:8000', ssh: 'ssh://git@example.com:9000' },
+ undefined
+ );
+ });
+
+ it('should match the web address when there is a web address.', () => {
+ server = new RemoteServer({
http: 'http://example.com:8000',
- ssh: 'ssh://git@example.com:9000'
+ ssh: 'ssh://git@example.com:9000',
+ web: 'http://other.com:8000'
+ });
+
+ url = 'http://other.com:8000/foo/bar';
+ match(undefined, {
+ http: 'http://example.com:8000',
+ ssh: 'ssh://git@example.com:9000',
+ web: 'http://other.com:8000'
});
});
});
@@ -55,42 +72,66 @@ describe('RemoteServer', () => {
});
it('should return undefined when there is no match.', () => {
- expect(server.match('http://test.com:8000/foo/bar')).to.be.undefined;
+ url = 'http://test.com:8000/foo/bar';
+ match(undefined, undefined);
});
it('should return the matching server when matching to the HTTP address.', () => {
- expect(server.match('http://example.com:8000/foo/bar')).to.deep.equal({
- http: 'http://example.com:8000',
- ssh: 'ssh://git@example.com:9000'
- });
+ url = 'http://example.com:8000/foo/bar';
+ match({ http: 'http://example.com:8000', ssh: 'ssh://git@example.com:9000' }, 'same');
- expect(server.match('http://test.com:6000/foo/bar')).to.deep.equal({
- http: 'http://test.com:6000',
- ssh: 'ssh://git@test.com:7000'
- });
+ url = 'http://test.com:6000/foo/bar';
+ match({ http: 'http://test.com:6000', ssh: 'ssh://git@test.com:7000' }, 'same');
});
it('should return the matching server when matching to the SSH address with the SSH protocol.', () => {
- expect(server.match('ssh://git@example.com:9000/foo/bar')).to.deep.equal({
- http: 'http://example.com:8000',
- ssh: 'ssh://git@example.com:9000'
- });
-
- expect(server.match('ssh://git@test.com:7000/foo/bar')).to.deep.equal({
- http: 'http://test.com:6000',
- ssh: 'ssh://git@test.com:7000'
- });
+ url = 'ssh://git@example.com:9000/foo/bar';
+ match(
+ { http: 'http://example.com:8000', ssh: 'ssh://git@example.com:9000' },
+ undefined
+ );
+
+ url = 'ssh://git@test.com:7000/foo/bar';
+ match({ http: 'http://test.com:6000', ssh: 'ssh://git@test.com:7000' }, undefined);
});
it('should return the matching server when matching to the SSH address without the SSH protocol.', () => {
- expect(server.match('git@example.com:9000/foo/bar')).to.deep.equal({
+ url = 'git@example.com:9000/foo/bar';
+ match(
+ { http: 'http://example.com:8000', ssh: 'ssh://git@example.com:9000' },
+ undefined
+ );
+
+ url = 'git@test.com:7000/foo/bar';
+ match({ http: 'http://test.com:6000', ssh: 'ssh://git@test.com:7000' }, undefined);
+ });
+
+ it('should match the web address when there is a web address.', () => {
+ server = new RemoteServer([
+ {
+ http: 'http://example.com:8000',
+ ssh: 'ssh://git@example.com:9000',
+ web: 'http://web.example.com'
+ },
+ {
+ http: 'http://test.com:6000',
+ ssh: 'ssh://git@test.com:7000',
+ web: 'http://web.test.com'
+ }
+ ]);
+
+ url = 'http://web.example.com/foo/bar';
+ match(undefined, {
http: 'http://example.com:8000',
- ssh: 'ssh://git@example.com:9000'
+ ssh: 'ssh://git@example.com:9000',
+ web: 'http://web.example.com'
});
- expect(server.match('git@test.com:7000/foo/bar')).to.deep.equal({
+ url = 'http://web.test.com/foo/bar';
+ match(undefined, {
http: 'http://test.com:6000',
- ssh: 'ssh://git@test.com:7000'
+ ssh: 'ssh://git@test.com:7000',
+ web: 'http://web.test.com'
});
});
});
@@ -98,31 +139,54 @@ describe('RemoteServer', () => {
describe('single dynamic server', () => {
beforeEach(() => {
server = new RemoteServer({
- pattern: 'http://(.+)\\.example\\.com:8000',
+ remotePattern: 'http://(.+)\\.example\\.com:8000',
http: 'http://example.com:8000/repos/{{ match[1] }}',
ssh: 'ssh://git@example.com:9000/_{{ match[1] }}'
});
});
it('should return undefined when there is no match.', () => {
- expect(server.match('http://example.com:8000/foo/bar')).to.be.undefined;
+ url = 'http://example.com:8000/foo/bar';
+ match(undefined, undefined);
});
it('should create the details of the matching server.', () => {
- expect(server.match('http://foo.example.com:8000/bar/meep')).to.deep.equal({
- http: 'http://example.com:8000/repos/foo',
- ssh: 'ssh://git@example.com:9000/_foo'
- });
+ url = 'http://foo.example.com:8000/bar/meep';
+ match(
+ {
+ http: 'http://example.com:8000/repos/foo',
+ ssh: 'ssh://git@example.com:9000/_foo'
+ },
+ 'same'
+ );
});
it('should not crash if pattern is invalid.', () => {
server = new RemoteServer({
- pattern: 'foo[bar',
+ remotePattern: 'foo[bar',
http: 'http://example.com',
ssh: 'ssh://git@example.com'
});
- expect(server.match('foo')).to.be.undefined;
+ url = 'foo';
+ match(undefined, undefined);
+ });
+
+ it('should match the web address when there is a web address.', () => {
+ server = new RemoteServer({
+ remotePattern: 'http://(.+)\\.example\\.com:8000',
+ http: 'http://example.com:8000/repos/{{ match[1] }}',
+ ssh: 'ssh://git@example.com:9000/_{{ match[1] }}',
+ webPattern: 'http://(.+)\\.test\\.com:8000',
+ web: 'http://test.com:8000/repos/{{ match[1] }}'
+ });
+
+ url = 'http://foo.test.com:8000/bar/meep';
+ match(undefined, {
+ http: 'http://example.com:8000/repos/foo',
+ ssh: 'ssh://git@example.com:9000/_foo',
+ web: 'http://test.com:8000/repos/foo'
+ });
});
});
@@ -130,31 +194,74 @@ describe('RemoteServer', () => {
beforeEach(() => {
server = new RemoteServer([
{
- pattern: 'http://(.+)\\.example\\.com:8000',
+ remotePattern: 'http://(.+)\\.example\\.com:8000',
http: 'http://example.com:8000/repos/{{ match[1] }}',
ssh: 'ssh://git@example.com:9000/_{{ match[1] }}'
},
{
- pattern: 'ssh://git@example\\.com:9000/_([^/]+)',
+ remotePattern: 'ssh://git@example\\.com:9000/_([^/]+)',
http: 'http://example.com:8000/repos/{{ match[1] }}',
- ssh: 'ssh://git@example.com:9000/_{{ match[1] }}'
+ ssh: 'ssh://git@example.com:9000/_{{ match[1] }}',
+ webPattern: '^$' // This server should only match SSH remote URLs.
}
]);
});
it('should return undefined when there is no match.', () => {
- expect(server.match('http://example.com:8000/foo/bar')).to.be.undefined;
+ url = 'http://example.com:8000/foo/bar';
+ match(undefined, undefined);
});
it('should create the details of the matching server.', () => {
- expect(server.match('http://foo.example.com:8000/bar/meep')).to.deep.equal({
+ url = 'http://foo.example.com:8000/bar/meep';
+ match(
+ {
+ http: 'http://example.com:8000/repos/foo',
+ ssh: 'ssh://git@example.com:9000/_foo'
+ },
+ 'same'
+ );
+
+ url = 'ssh://git@example.com:9000/_foo/bar';
+ match(
+ {
+ http: 'http://example.com:8000/repos/foo',
+ ssh: 'ssh://git@example.com:9000/_foo'
+ },
+ undefined
+ );
+ });
+
+ it('should match the web address when there is a web address.', () => {
+ server = new RemoteServer([
+ {
+ remotePattern: 'http://(.+)\\.example\\.com:8000',
+ http: 'http://example.com:8000/repos/{{ match[1] }}',
+ ssh: 'ssh://git@example.com:9000/_{{ match[1] }}',
+ webPattern: 'http://(.+)\\.test\\.com:8000',
+ web: 'http://test.com:8000/repos/{{ match[1] }}'
+ },
+ {
+ remotePattern: 'ssh://git@example\\.com:9000/_([^/]+)',
+ http: 'http://example.com:8000/repos/{{ match[1] }}',
+ ssh: 'ssh://git@example.com:9000/_{{ match[1] }}',
+ webPattern: 'http://(.+)\\.other\\.com:8000',
+ web: 'http://other.com:8000/repos/{{ match[1] }}'
+ }
+ ]);
+
+ url = 'http://foo.test.com:8000/bar/meep';
+ match(undefined, {
http: 'http://example.com:8000/repos/foo',
- ssh: 'ssh://git@example.com:9000/_foo'
+ ssh: 'ssh://git@example.com:9000/_foo',
+ web: 'http://test.com:8000/repos/foo'
});
- expect(server.match('ssh://git@example.com:9000/_foo/bar')).to.deep.equal({
+ url = 'http://foo.other.com:8000/bar/meep';
+ match(undefined, {
http: 'http://example.com:8000/repos/foo',
- ssh: 'ssh://git@example.com:9000/_foo'
+ ssh: 'ssh://git@example.com:9000/_foo',
+ web: 'http://other.com:8000/repos/foo'
});
});
});
@@ -163,7 +270,7 @@ describe('RemoteServer', () => {
beforeEach(() => {
server = new RemoteServer([
{
- pattern: 'http://(.+)\\.example\\.com:8000',
+ remotePattern: 'http://(.+)\\.example\\.com:8000',
http: 'http://example.com:8000/repos/{{ match[1] }}',
ssh: 'ssh://git@example.com:9000/_{{ match[1] }}'
},
@@ -175,21 +282,24 @@ describe('RemoteServer', () => {
});
it('should return undefined when there is no match.', () => {
- expect(server.match('http://example.com:7000/foo/bar')).to.be.undefined;
+ url = 'http://example.com:7000/foo/bar';
+ match(undefined, undefined);
});
it('should return the matching server when matching to the static server.', () => {
- expect(server.match('http://example.com:10000/foo/bar')).to.deep.equal({
- http: 'http://example.com:10000',
- ssh: 'ssh://git@example.com:11000'
- });
+ url = 'http://example.com:10000/foo/bar';
+ match({ http: 'http://example.com:10000', ssh: 'ssh://git@example.com:11000' }, 'same');
});
it('should create the details of the matching server when matching to the dynamic server.', () => {
- expect(server.match('http://foo.example.com:8000/bar/meep')).to.deep.equal({
- http: 'http://example.com:8000/repos/foo',
- ssh: 'ssh://git@example.com:9000/_foo'
- });
+ url = 'http://foo.example.com:8000/bar/meep';
+ match(
+ {
+ http: 'http://example.com:8000/repos/foo',
+ ssh: 'ssh://git@example.com:9000/_foo'
+ },
+ 'same'
+ );
});
});
@@ -204,7 +314,8 @@ describe('RemoteServer', () => {
},
{
http: 'http://test.com:6000',
- ssh: 'ssh://git@test.com:7000'
+ ssh: 'ssh://git@test.com:7000',
+ web: 'http://web.test.com'
}
];
@@ -212,53 +323,67 @@ describe('RemoteServer', () => {
});
it('should return undefined when there is no match.', () => {
- expect(server.match('http://example.com:9000/foo/bar')).to.be.undefined;
+ url = 'http://example.com:9000/foo/bar';
+ match(undefined, undefined);
});
it('should return the matching server when matching to the HTTP address.', () => {
- expect(server.match('http://example.com:8000/foo/bar')).to.deep.equal({
- http: 'http://example.com:8000',
- ssh: 'ssh://git@example.com:9000'
- });
+ url = 'http://example.com:8000/foo/bar';
+ match({ http: 'http://example.com:8000', ssh: 'ssh://git@example.com:9000' }, 'same');
+
+ url = 'http://test.com:6000/foo/bar';
+ match(
+ {
+ http: 'http://test.com:6000',
+ ssh: 'ssh://git@test.com:7000',
+ web: 'http://web.test.com'
+ },
+ undefined
+ );
- expect(server.match('http://test.com:6000/foo/bar')).to.deep.equal({
+ url = 'http://web.test.com/foo/bar';
+ match(undefined, {
http: 'http://test.com:6000',
- ssh: 'ssh://git@test.com:7000'
+ ssh: 'ssh://git@test.com:7000',
+ web: 'http://web.test.com'
});
});
it('should return the matching server when matching to the SSH address.', () => {
- expect(server.match('ssh://git@example.com:9000/foo/bar')).to.deep.equal({
- http: 'http://example.com:8000',
- ssh: 'ssh://git@example.com:9000'
- });
-
- expect(server.match('ssh://git@test.com:7000/foo/bar')).to.deep.equal({
- http: 'http://test.com:6000',
- ssh: 'ssh://git@test.com:7000'
- });
+ url = 'ssh://git@example.com:9000/foo/bar';
+ match(
+ { http: 'http://example.com:8000', ssh: 'ssh://git@example.com:9000' },
+ undefined
+ );
+
+ url = 'ssh://git@test.com:7000/foo/bar';
+ match(
+ {
+ http: 'http://test.com:6000',
+ ssh: 'ssh://git@test.com:7000',
+ web: 'http://web.test.com'
+ },
+ undefined
+ );
});
it('should return the matching server when the remote URL is an HTTP address and the server has no SSH URL.', () => {
source = [{ http: 'http://example.com:8000', ssh: undefined }];
- expect(server.match('http://example.com:8000/foo/bar')).to.deep.equal({
- http: 'http://example.com:8000',
- ssh: undefined
- });
+ url = 'http://example.com:8000/foo/bar';
+ match({ http: 'http://example.com:8000', ssh: undefined }, 'same');
});
it('should not return a match when the remote URL is an SSH address and the server has no SSH URL.', () => {
source = [{ http: 'http://example.com:8000', ssh: undefined }];
- expect(server.match('ssh://git@test.com:7000/foo/bar')).to.be.undefined;
+ url = 'ssh://git@test.com:7000/foo/bar';
+ match(undefined, undefined);
});
it('should not cache the servers returned from the factory.', () => {
- expect(server.match('http://example.com:8000/foo/bar')).to.deep.equal({
- http: 'http://example.com:8000',
- ssh: 'ssh://git@example.com:9000'
- });
+ url = 'http://example.com:8000/foo/bar';
+ match({ http: 'http://example.com:8000', ssh: 'ssh://git@example.com:9000' }, 'same');
source = [
{
@@ -267,7 +392,7 @@ describe('RemoteServer', () => {
}
];
- expect(server.match('http://example.com:8000/foo/bar')).to.be.undefined;
+ match(undefined, undefined);
source = [
{
@@ -276,10 +401,17 @@ describe('RemoteServer', () => {
}
];
- expect(server.match('http://example.com:8000/foo/bar')).to.deep.equal({
- http: 'http://example.com:8000',
- ssh: 'ssh://git@example.com:9000'
- });
+ match({ http: 'http://example.com:8000', ssh: 'ssh://git@example.com:9000' }, 'same');
});
});
+
+ function match(
+ expectedRemoteMatch: StaticServer | undefined,
+ expectedWebMatch: StaticServer | 'same' | undefined
+ ): void {
+ expect(server.matchRemoteUrl(url), 'remote').to.deep.equal(expectedRemoteMatch);
+ expect(server.matchWebUrl(url), 'web').to.deep.equal(
+ expectedWebMatch === 'same' ? expectedRemoteMatch : expectedWebMatch
+ );
+ }
});