diff --git a/README.md b/README.md index e95eb56..a9d532e 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ GitLink let's users step through your code hosted on GitHub! **Help make .NET op **Important note** -*GitLink* was formerly named *GitHubLink*. By adding support to more Git hosting services the name seemed not covering the whole package. Note that the old GitHubLink packages on NuGet and Chocolatey will no long be updated / maintained. +*GitLink* was formerly named *GitHubLink*. By adding support to more Git hosting services the name seemed not covering the whole package. The old GitHubLink packages on NuGet and Chocolatey will no longer be updated or maintained. -- @@ -28,31 +28,37 @@ GitLink makes symbol servers obsolete which saves you both time with uploading s ![Stepping through external source code](doc/images/GitLink_example.gif) - -This application is based on the SourceLink project. SourceLink requires FAKE to run and not everyone likes to write code in F#. GitLink is a wrapper around SourceLink specifically written to be easily used from any build system (locally or a build server) and in any .NET language. It also provides new features such as standard integration with GitHub and BitBucket and the possibility to use remote repositories. GitLink is available as console application and can be referenced as assembly to be used in other .NET assemblies. - The advantage of GitLink is that it is fully customized for Git. It also works with GitHub or BitBucket urls so it **does not require a local git repository to work**. This makes it perfectly usable in continuous integration servers such as Continua CI. Updating all the pdb files is very fast. A solution with over 85 projects will be handled in less than 30 seconds. -When using GitLink, the user no longer has to specify symbol servers. He/she only has to enable the support for source servers in Visual Studio as shown in the image below: +When using GitLink, the user no longer has to specify symbol servers. The only requirement is to ensure the check the `Enable source server support` option in Visual Studio as shown below: ![Enabling source server support](doc/images/visualstudio_enablesourceserversupport.png) # Troubleshooting -**Note that Visual Studio 2012 needs to run elevated in order to download the source server files due to a bug in Visual Studio 2012.** +## Source Stepping isn't working -If the source stepping is not working, double check that Visual Studio has a valid symbol cache directory to store the source files being downloaded: +* Visual Studio 2012 needs to run elevated in order to download the source server files + +* Specify a value for Visual Studio -> Options -> Debugging -> Symbols -> `Cache Symbols in this directory` ![Enabling source server support](doc/images/visualstudio_symbolslocation.png) +## Source Stepping returns HTML +If your repository is private, you are likely seeing the logon HTML from your git host. + +* Log onto your git host in Internet Explorer +* Purge your local symbol cache + # Supported git providers GitLink supports the following providers out of the box (will auto-detect based on the url): * BitBucket * GitHub +* Custom Provider (custom urls) Providers currently being worked on: @@ -72,10 +78,11 @@ It is also possible to specify a custom url provider. Using GitLink via the command line is very simple: -1. Build the software (in release mode with pdb files enabled) +1. Build the solution - in release mode with pdb files enabled 2. Run the console application with the right command line parameters +3. Include the PDB in your nuget package -Below are a few examples. +See [Oren Novotony's blog post](https://oren.codes/2015/09/23/enabling-source-code-debugging-for-your-nuget-packages-with-gitlink/) for even more detail and examples on build integration. ## Most simple usage @@ -107,11 +114,37 @@ Sometimes a repository contains more than 1 solution file. By default, all solut GitLink.exe c:\source\catel -u https://github.com/catel/catel -f Catel.sln -## Ignoring projects +## Ignoring projects and explicitly including them -When specific projects should be ignored, use the *-ignore* option. This option accepts a comma separated list of projects to ignore: +When specific projects should be ignored, use the *-ignore* option. This option accepts a comma separated list of patterns to ignore. Each pattern is either a literal project name (case-insensitive) or a regex enclosed in slashes. For example: GitLink.exe c:\source\catel -u https://github.com/catel/catel -f Catel.sln -ignore Catel.Core.WP80,Catel.MVVM.WP80 + GitLink.exe c:\source\catel -u https://github.com/catel/catel -f Catel.sln -ignore /^.+\.WP80$/,Catel.Core + +In case you want to ignore most of your projects, you can explicitly *-include* only the projects you need - others will be ignored automatically. Same as *-ignore* it accepts list of patterns. For example: + + GitLink.exe c:\source\catel -u https://github.com/catel/catel -f Catel.sln -include Catel.Core + GitLink.exe c:\source\catel -u https://github.com/catel/catel -f Catel.sln -include /Catel\..*$/,SomeOtherProject + +Finally, you can set both *-ignore* and *-include* options. In this case only projects matching one of *-include* patterns will be taken, but if and only if they don't match one of *-ignore*s. For example, the following command line will include only Catel.* projects, except "Catel.Core": + + GitLink.exe c:\source\catel -u https://github.com/catel/catel -f Catel.sln -include /Catel\..*$/ -ignore Catel.Core + +## Running for an uncommon / customized URL + +When working with a repository using uncommon URL you can use placeholders to specifiy where the filename and revision hash should be, use `-u` parameter with the custom URL + + GitLink.exe c:\source\catel -u "https://host/projects/catel/repos/catel/browse/{filename}?at={revision}&raw" + +The custom url will be used to fill the placeholders with the relative file path and the revision hash. + +## Running for a custom raw content URL + +When working with a content proxy or an alternative git VCS system that supports direct HTTP access to specific file revisions use the `-u` parameter with the custom raw content root URL + + GitLink.exe c:\source\catel -u https://raw.githubusercontent.com/catel/catel + +The custom url will be used to fill in the following pattern `{customUrl}/{revision}/{relativeFilePath}` when generating the source mapping. ## Getting help @@ -135,18 +168,14 @@ The command line implementation uses the same available API. To link files to a Git project, a context must be created. The command line version does this by using the *ArgumentParser* class. It is also possible to create a context from scratch as shown in the example below: -```csharp -var context = new GitLink.Context(); -context.SolutionDirectory = @"c:\source\catel"; -context.TargetUrl = "https://github.com/catel/catel"; -context.TargetBranch = "develop"; -``` + var context = new GitLink.Context(new ProviderManager()); + context.SolutionDirectory = @"c:\source\catel"; + context.TargetUrl = "https://github.com/catel/catel"; + context.TargetBranch = "develop"; It is possible to create a context based on command line arguments: -```csharp -var context = ArgumentParser.Parse(@"c:\source\catel -u https://github.com/catel/catel -b develop"); -``` + var context = ArgumentParser.Parse(@"c:\source\catel -u https://github.com/catel/catel -b develop"); ## Linking a context @@ -192,6 +221,8 @@ Below is a list of projects already using GitLink (alphabetically ordered). - Fluent.Ribbon - GitLink - MahApps.Metro +- NBitcoin +- NBitcoin.Indexer - NEST and Elasticsearch.NET - Orc.Analytics - Orc.AutomaticSupport @@ -213,8 +244,11 @@ Below is a list of projects already using GitLink (alphabetically ordered). - Orc.SupportPackage - Orc.SystemInfo - Orc.WorkspaceManagement +- Orc.Wizard - Orchestra - OxyPlot +- QBitNinja +- ReactiveUI - Romantic Web - xUnit.net - xUnit.net Visual Studio Runner diff --git a/src/GitLink.Tests/ArgumentParserFacts.cs b/src/GitLink.Tests/ArgumentParserFacts.cs index c2ccf3f..f93bd13 100644 --- a/src/GitLink.Tests/ArgumentParserFacts.cs +++ b/src/GitLink.Tests/ArgumentParserFacts.cs @@ -119,10 +119,48 @@ public void CorrectlyParsesIgnoredProjects() Assert.AreEqual("test2", context.IgnoredProjects[1]); } + [TestCase] + public void CorrectlyParsesIncludedProjects() + { + var context = ArgumentParser.ParseArguments("solutionDirectory -u http://github.com/CatenaLogic/GitLink -debug -c someConfiguration -include test1,test2"); + + Assert.AreEqual("solutionDirectory", context.SolutionDirectory); + Assert.AreEqual("http://github.com/CatenaLogic/GitLink", context.TargetUrl); + Assert.AreEqual("someConfiguration", context.ConfigurationName); + Assert.IsTrue(context.IsDebug); + + Assert.AreEqual(2, context.IncludedProjects.Count); + Assert.AreEqual("test1", context.IncludedProjects[0]); + Assert.AreEqual("test2", context.IncludedProjects[1]); + } + + [TestCase] + public void CorrectlyParsesIncludedProjectsWithRegex() + { + var context = ArgumentParser.ParseArguments("solutionDirectory -u http://github.com/CatenaLogic/GitLink -debug -c someConfiguration -include test1,/*.test*2/"); + + Assert.AreEqual("solutionDirectory", context.SolutionDirectory); + Assert.AreEqual("http://github.com/CatenaLogic/GitLink", context.TargetUrl); + Assert.AreEqual("someConfiguration", context.ConfigurationName); + Assert.IsTrue(context.IsDebug); + + Assert.AreEqual(2, context.IncludedProjects.Count); + Assert.AreEqual("test1", context.IncludedProjects[0]); + Assert.AreEqual("/*.test*2/", context.IncludedProjects[1]); + } + [TestCase] public void ThrowsExceptionForUnknownArgument() { ExceptionTester.CallMethodAndExpectException(() => ArgumentParser.ParseArguments("solutionDirectory -x logFilePath")); } + + [TestCase] + public void PowershellDownloadSetToTrue() + { + var context = ArgumentParser.ParseArguments("solutionDirectory -u http://github.com/CatenaLogic/GitLink -powershell"); + + Assert.IsTrue(context.DownloadWithPowershell); + } } } \ No newline at end of file diff --git a/src/GitLink.Tests/GitLink.Tests.csproj b/src/GitLink.Tests/GitLink.Tests.csproj index abfd43a..337ceeb 100644 --- a/src/GitLink.Tests/GitLink.Tests.csproj +++ b/src/GitLink.Tests/GitLink.Tests.csproj @@ -89,9 +89,12 @@ + + + diff --git a/src/GitLink.Tests/ProjectExtensionsFacts.cs b/src/GitLink.Tests/ProjectExtensionsFacts.cs new file mode 100644 index 0000000..5a68e51 --- /dev/null +++ b/src/GitLink.Tests/ProjectExtensionsFacts.cs @@ -0,0 +1,52 @@ +// -------------------------------------------------------------------------------------------------------------------- +// +// Copyright (c) 2014 - 2016 CatenaLogic. All rights reserved. +// +// -------------------------------------------------------------------------------------------------------------------- +namespace GitLink.Tests +{ + using NUnit.Framework; + + [TestFixture] + public class ProjectExtensionsFacts + { + [Test] + public void NoIncludesExcludes_ProjectNotIgnored() + { + Assert.IsFalse(ProjectHelper.ShouldBeIgnored("project", new string[0], new string[0])); + } + + [TestCase("ignoredProject", "ignoredProject", true)] + [TestCase("ignoredProject", "ignoredproject", true)] + [TestCase("ignoredProject", "/ignoredProject/", true)] + [TestCase("ignoredProject", "/ignoredproject/", true)] + [TestCase("ignoredProject", "/^i\\w+t$/", true)] + [TestCase("nonIgnoredProject", "ignoredProject", false)] + public void ExcludedProject_IgnoredOnlySpecifiedOne(string projectName, string ignorePattern, bool expectedIgnore) + { + Assert.AreEqual(expectedIgnore, ProjectHelper.ShouldBeIgnored(projectName, new string[0], new[] { ignorePattern })); + } + + [TestCase("anotherProject", "includedProject", true)] + [TestCase("anotherProject", "includedproject", true)] + [TestCase("anotherProject", "/includedProject/", true)] + [TestCase("anotherProject", "/includedproject/", true)] + [TestCase("anotherProject", "/[a-z]+/", false)] + [TestCase("includedProject", "includedProject", false)] + public void ExplicitlyIncludedProject_OthersAreIgnored(string projectName, string includePattern, bool expectedIgnore) + { + Assert.AreEqual(expectedIgnore, ProjectHelper.ShouldBeIgnored(projectName, new[] { includePattern }, new string[0])); + } + + [TestCase("excludedProject", true)] + [TestCase("includedProject", false)] + [TestCase("includedAndExcludedProject", true)] + [TestCase("notIncludedNorExcludedProject", true)] + public void BothIncludedAndExcludedProjects(string projectName, bool expectedIgnore) + { + Assert.AreEqual(expectedIgnore, ProjectHelper.ShouldBeIgnored(projectName, + new[] { "includedProject", "includedAndExcludedProject" }, + new[] { "excludedProject", "includedAndExcludedProject" })); + } + } +} \ No newline at end of file diff --git a/src/GitLink.Tests/Providers/BitBucketProviderFacts.cs b/src/GitLink.Tests/Providers/BitBucketProviderFacts.cs index c4c48ec..5623278 100644 --- a/src/GitLink.Tests/Providers/BitBucketProviderFacts.cs +++ b/src/GitLink.Tests/Providers/BitBucketProviderFacts.cs @@ -72,6 +72,15 @@ public void ReturnsValidProjectUrl() Assert.AreEqual("https://bitbucket.org/CatenaLogic/GitLink", provider.ProjectUrl); } + [TestCase] + public void ReturnsValidProjectUrlWhenContainsPeriod() + { + var provider = new BitBucketProvider(); + provider.Initialize("https://bitbucket.org/CatenaLogic/dotted.Project"); + + Assert.AreEqual("https://bitbucket.org/CatenaLogic/dotted.Project", provider.ProjectUrl); + } + [TestCase] public void ReturnsValidRawGitUrl() { diff --git a/src/GitLink.Tests/Providers/CustomRawUrlProviderFacts.cs b/src/GitLink.Tests/Providers/CustomRawUrlProviderFacts.cs new file mode 100644 index 0000000..1065e26 --- /dev/null +++ b/src/GitLink.Tests/Providers/CustomRawUrlProviderFacts.cs @@ -0,0 +1,82 @@ +namespace GitLink.Tests.Providers +{ + using GitLink.Providers; + using NUnit.Framework; + + public class CustomRawUrlProviderFacts + { + [TestFixture] + public class TheInitialization + { + [TestCase("http://example.com/repo", true)] + [TestCase("https://example.com/repo", true)] + [TestCase("https://example.com/repo/", true)] + [TestCase("gopher://example.com/repo", false)] + public void CorrectlyValidatesForUrls(string url, bool expectedValue) + { + var provider = new CustomRawUrlProvider(); + var valid = provider.Initialize(url); + + Assert.AreEqual(expectedValue, valid); + } + } + + [TestFixture] + public class TheGitHubProviderProperties + { + [TestCase] + public void ReturnsNullCompany() + { + var provider = new CustomRawUrlProvider(); + provider.Initialize("http://example.com/repo"); + + Assert.IsNull(provider.CompanyName); + } + + [TestCase] + public void ReturnsNullCompanyUrl() + { + var provider = new CustomRawUrlProvider(); + provider.Initialize("http://example.com/repo"); + + Assert.IsNull(provider.CompanyUrl); + } + + [TestCase] + public void ReturnsNullProject() + { + var provider = new CustomRawUrlProvider(); + provider.Initialize("http://example.com/repo"); + + Assert.IsNull(provider.ProjectName); + } + + [TestCase] + public void ReturnsNullProjectUrl() + { + var provider = new CustomRawUrlProvider(); + provider.Initialize("http://example.com/repo"); + + Assert.IsNull(provider.ProjectUrl); + } + + [TestCase] + public void ReturnsValidRawGitUrl() + { + var provider = new CustomRawUrlProvider(); + provider.Initialize("http://example.com/repo"); + + Assert.AreEqual("http://example.com/repo", provider.RawGitUrl); + } + + [TestCase] + public void ReturnsValidRawGitUrlWithNoTrailingSlash() + { + var provider = new CustomRawUrlProvider(); + provider.Initialize("http://example.com/repo/"); + + Assert.AreEqual("http://example.com/repo", provider.RawGitUrl); + } + } + } +} diff --git a/src/GitLink.Tests/Providers/CustomUrlProviderFacts.cs b/src/GitLink.Tests/Providers/CustomUrlProviderFacts.cs new file mode 100644 index 0000000..1c26e06 --- /dev/null +++ b/src/GitLink.Tests/Providers/CustomUrlProviderFacts.cs @@ -0,0 +1,82 @@ +namespace GitLink.Tests.Providers +{ + using GitLink.Providers; + using NUnit.Framework; + using System; + using System.Collections.Generic; + using System.Linq; + using System.Text; + using System.Threading.Tasks; + + public class CustomUrlProviderFacts + { + private const string correctUrl = "https://bitbucket.intra.company.com/projects/aaa/repos/a/browse/{filename}?at={revision}&raw"; + [TestFixture] + public class TheInitialization + { + [TestCase(correctUrl, true)] + [TestCase("https://example.com/repo", false)] + [TestCase("https://bitbucket.intra.company.com/projects/aaa/repos/a/browse/{filename}?raw", true)] + [TestCase("gopher://example.com/repo", false)] + public void CorrectlyValidatesForUrls(string url, bool expectedValue) + { + var provider = new CustomUrlProvider(); + var valid = provider.Initialize(url); + + Assert.AreEqual(expectedValue, valid); + } + } + + [TestFixture] + public class TheGitHubProviderProperties + { + [TestCase] + public void ReturnsNullCompany() + { + var provider = new CustomUrlProvider(); + provider.Initialize(correctUrl); + + Assert.IsNull(provider.CompanyName); + } + + [TestCase] + public void ReturnsNullCompanyUrl() + { + var provider = new CustomUrlProvider(); + provider.Initialize(correctUrl); + + Assert.IsNull(provider.CompanyUrl); + } + + [TestCase] + public void ReturnsNullProject() + { + var provider = new CustomUrlProvider(); + provider.Initialize(correctUrl); + + Assert.IsNull(provider.ProjectName); + } + + [TestCase] + public void ReturnsNullProjectUrl() + { + var provider = new CustomUrlProvider(); + provider.Initialize(correctUrl); + + Assert.IsNull(provider.ProjectUrl); + } + + [TestCase] + public void ReturnsValidRawGitUrl() + { + var provider = new CustomUrlProvider(); + provider.Initialize(correctUrl); + + string correctReturnedUrl = correctUrl.Replace("{filename}", "%var2%"); + correctReturnedUrl = correctReturnedUrl.Replace("{revision}", "{0}"); + + Assert.AreEqual(correctReturnedUrl, provider.RawGitUrl); + } + } + } +} diff --git a/src/GitLink.Tests/Providers/ProviderManagerFacts.cs b/src/GitLink.Tests/Providers/ProviderManagerFacts.cs index 655d023..2670de8 100644 --- a/src/GitLink.Tests/Providers/ProviderManagerFacts.cs +++ b/src/GitLink.Tests/Providers/ProviderManagerFacts.cs @@ -18,6 +18,7 @@ public class TheProviderGetProviderMethod { [TestCase("https://bitbucket.org/CatenaLogic/GitLink", typeof(BitBucketProvider))] [TestCase("https://github.com/CatenaLogic/GitLink", typeof(GitHubProvider))] + [TestCase("https://example.com/repo", typeof(CustomRawUrlProvider))] [TestCase("", null)] public void ReturnsRightProvider(string url, Type expectedType) { diff --git a/src/GitLink/ArgumentParser.cs b/src/GitLink/ArgumentParser.cs index 0e6c2bd..97bcf44 100644 --- a/src/GitLink/ArgumentParser.cs +++ b/src/GitLink/ArgumentParser.cs @@ -80,6 +80,12 @@ public static Context ParseArguments(List commandLineArguments, IProvide continue; } + if (IsSwitch("powershell", name)) + { + context.DownloadWithPowershell = true; + continue; + } + // After this point, all arguments should have a value index++; var valueInfo = GetValue(namedArguments, index); @@ -134,6 +140,12 @@ public static Context ParseArguments(List commandLineArguments, IProvide continue; } + if (IsSwitch("include", name)) + { + context.IncludedProjects.AddRange(value.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).Select(x => x.Trim())); + continue; + } + if (IsSwitch("ignore", name)) { context.IgnoredProjects.AddRange(value.Split(new []{ ',' }, StringSplitOptions.RemoveEmptyEntries).Select(x => x.Trim())); diff --git a/src/GitLink/Context.cs b/src/GitLink/Context.cs index a45b72e..5d3869e 100644 --- a/src/GitLink/Context.cs +++ b/src/GitLink/Context.cs @@ -31,9 +31,12 @@ public Context(IProviderManager providerManager) Authentication = new Authentication(); ConfigurationName = "Release"; PlatformName = "AnyCPU"; + IncludedProjects = new List(); IgnoredProjects = new List(); } + public bool DownloadWithPowershell { get; set; } + public bool IsHelp { get; set; } public bool IsDebug { get; set; } @@ -92,6 +95,8 @@ public string TargetBranch public string SolutionFile { get; set; } + public List IncludedProjects { get; private set; } + public List IgnoredProjects { get; private set; } public string PdbFilesDirectory { get; set; } diff --git a/src/GitLink/Extensions/ProjectExtensions.cs b/src/GitLink/Extensions/ProjectExtensions.cs index 8f68831..af0c1e9 100644 --- a/src/GitLink/Extensions/ProjectExtensions.cs +++ b/src/GitLink/Extensions/ProjectExtensions.cs @@ -20,25 +20,6 @@ public static class ProjectExtensions { private static readonly ILog Log = LogManager.GetCurrentClassLogger(); - public static bool ShouldBeIgnored(this Project project, IEnumerable projectsToIgnore) - { - Argument.IsNotNull(() => project); - - var projectName = GetProjectName(project).ToLower(); - - foreach (var projectToIgnore in projectsToIgnore) - { - var lowerCaseProjectToIgnore = projectToIgnore.ToLower(); - - if (string.Equals(projectName, lowerCaseProjectToIgnore)) - { - return true; - } - } - - return false; - } - public static string GetProjectName(this Project project) { Argument.IsNotNull(() => project); @@ -47,7 +28,7 @@ public static string GetProjectName(this Project project) return projectName ?? Path.GetFileName(project.FullPath); } - public static void CreateSrcSrv(this Project project, string rawUrl, string revision, Dictionary paths) + public static void CreateSrcSrv(this Project project, string rawUrl, string revision, Dictionary paths, bool downloadWithPowershell) { Argument.IsNotNull(() => project); Argument.IsNotNullOrWhitespace(() => rawUrl); @@ -55,17 +36,17 @@ public static void CreateSrcSrv(this Project project, string rawUrl, string revi var srcsrvFile = GetOutputSrcSrvFile(project); - CreateSrcSrv(project, rawUrl, revision, paths, srcsrvFile); + CreateSrcSrv(project, rawUrl, revision, paths, srcsrvFile, downloadWithPowershell); } - public static void CreateSrcSrv(this Project project, string rawUrl, string revision, Dictionary paths, string srcsrvFile) + public static void CreateSrcSrv(this Project project, string rawUrl, string revision, Dictionary paths, string srcsrvFile, bool downloadWithPowershell) { Argument.IsNotNull(() => project); Argument.IsNotNullOrWhitespace(() => rawUrl); Argument.IsNotNullOrWhitespace(() => revision); Argument.IsNotNullOrWhitespace(() => srcsrvFile); - File.WriteAllBytes(srcsrvFile, SrcSrv.Create(rawUrl, revision, paths.Select(x => new Tuple(x.Key, x.Value)))); + File.WriteAllBytes(srcsrvFile, SrcSrv.Create(rawUrl, revision, paths.Select(x => new Tuple(x.Key, x.Value)), downloadWithPowershell)); } public static IEnumerable GetCompilableItems(this Project project) diff --git a/src/GitLink/GitLink.csproj b/src/GitLink/GitLink.csproj index a1838c2..f02cf08 100644 --- a/src/GitLink/GitLink.csproj +++ b/src/GitLink/GitLink.csproj @@ -111,6 +111,8 @@ + + diff --git a/src/GitLink/HelpWriter.cs b/src/GitLink/HelpWriter.cs index 603f8c9..f16a2f8 100644 --- a/src/GitLink/HelpWriter.cs +++ b/src/GitLink/HelpWriter.cs @@ -39,6 +39,8 @@ solutionPath The directory containing the solution with the pdb files. -s [shaHash] The SHA-1 hash of the commit. -d [pdbDirectory] The directory where pdb files exists, default value is the normal project output directory. + -powershell Use an indexing strategy that won't rely on SRCSRV http support, + but use a powershell command for URL download instead. -errorsaswarnings Don't fail on errors, but treat them as warnings instead. -skipverify Skip pdb verification in case it causes issues (it's a formality anyway) -debug Enables debug mode with special dumps of msbuild. diff --git a/src/GitLink/Helpers/ProjectHelper.cs b/src/GitLink/Helpers/ProjectHelper.cs index d28e20f..f4172fa 100644 --- a/src/GitLink/Helpers/ProjectHelper.cs +++ b/src/GitLink/Helpers/ProjectHelper.cs @@ -16,6 +16,7 @@ namespace GitLink using Catel.Reflection; using Microsoft.Build.Evaluation; using System.IO; + using System.Text.RegularExpressions; public static class ProjectHelper { @@ -116,5 +117,44 @@ public static Project LoadProject(string projectFile, string configurationName, return null; } } + + public static bool ShouldBeIgnored(string projectName, ICollection projectsToInclude, ICollection projectsToIgnore) + { + Argument.IsNotNull(() => projectName); + + if (projectsToIgnore.Any(projectToIgnore => ProjectNameMatchesPattern(projectName, projectToIgnore))) + { + return true; + } + + if (projectsToInclude.Count == 0) + { + return false; + } + + if (projectsToInclude.All(projectToInclude => !ProjectNameMatchesPattern(projectName, projectToInclude))) + { + return true; + } + + return false; + } + + // pattern may be either a literal string, and then we'll be comparing literally ignoring case + // or it can be a regex enclosed in slashes like /this-is-my-regex/ + private static bool ProjectNameMatchesPattern(string projectName, string pattern) + { + Argument.IsNotNull(() => pattern); + + if (pattern.Length > 2 && pattern.StartsWith("/") && pattern.EndsWith("/")) + { + var ignoreRegex = new Regex(pattern.Substring(1, pattern.Length - 2), RegexOptions.IgnoreCase); + if (ignoreRegex.IsMatch(projectName)) + { + return true; + } + } + return string.Equals(projectName, pattern, StringComparison.InvariantCultureIgnoreCase); + } } } \ No newline at end of file diff --git a/src/GitLink/Helpers/ResourceHelper.cs b/src/GitLink/Helpers/ResourceHelper.cs index 402036c..e19cbe7 100644 --- a/src/GitLink/Helpers/ResourceHelper.cs +++ b/src/GitLink/Helpers/ResourceHelper.cs @@ -14,7 +14,7 @@ public static class ResourceHelper { public static void ExtractEmbeddedResource(string resourceName, string destinationFileName) { - var assembly = AssemblyHelper.GetEntryAssembly(); + var assembly = typeof(ResourceHelper).Assembly; using (var resource = assembly.GetManifestResourceStream(resourceName)) { diff --git a/src/GitLink/Linker.cs b/src/GitLink/Linker.cs index 6366185..75ab87d 100644 --- a/src/GitLink/Linker.cs +++ b/src/GitLink/Linker.cs @@ -96,7 +96,8 @@ public static int Link(Context context) { try { - if (project.ShouldBeIgnored(context.IgnoredProjects)) + var projectName = project.GetProjectName(); + if (ProjectHelper.ShouldBeIgnored(projectName, context.IncludedProjects, context.IgnoredProjects)) { Log.Info("Ignoring '{0}'", project.GetProjectName()); Log.Info(string.Empty); @@ -199,7 +200,13 @@ private static bool LinkProject(Context context, Project project, string pdbStrF } } - var rawUrl = string.Format("{0}/{{0}}/%var2%", context.Provider.RawGitUrl); + var rawUrl = context.Provider.RawGitUrl; + + if(!rawUrl.Contains("%var2%") && !rawUrl.Contains("{0}")) + { + rawUrl= string.Format("{0}/{{0}}/%var2%", rawUrl); + } + var paths = new Dictionary(); foreach (var compilable in compilables) { @@ -212,7 +219,7 @@ private static bool LinkProject(Context context, Project project, string pdbStrF paths.Add(compilable, relativePathForUrl); } - project.CreateSrcSrv(rawUrl, shaHash, paths, projectSrcSrvFile); + project.CreateSrcSrv(rawUrl, shaHash, paths, projectSrcSrvFile, context.DownloadWithPowershell); Log.Debug("Created source server link file, updating pdb file '{0}'", context.GetRelativePath(projectPdbFile)); diff --git a/src/GitLink/Pdb/SrcSrv.cs b/src/GitLink/Pdb/SrcSrv.cs index 147103b..3bbff14 100644 --- a/src/GitLink/Pdb/SrcSrv.cs +++ b/src/GitLink/Pdb/SrcSrv.cs @@ -19,7 +19,7 @@ private static string CreateTarget(string rawUrl, string revision) return string.Format(rawUrl, revision); } - public static byte[] Create(string rawUrl, string revision, IEnumerable> paths) + public static byte[] Create(string rawUrl, string revision, IEnumerable> paths, bool downloadWithPowershell) { Argument.IsNotNullOrWhitespace(() => rawUrl); Argument.IsNotNullOrWhitespace(() => revision); @@ -29,12 +29,22 @@ public static byte[] Create(string rawUrl, string revision, IEnumerable +// Copyright (c) 2014 - 2014 CatenaLogic. All rights reserved. +// +// -------------------------------------------------------------------------------------------------------------------- + + +namespace GitLink.Providers +{ + using Catel.IoC; + using Catel.Reflection; + + public class ProviderManager : IProviderManager + { + public ProviderBase GetProvider(string url) + { + var providerTypes = TypeCache.GetTypes(x => typeof(ProviderBase).IsAssignableFromEx(x) && !x.IsAbstract && x != typeof(CustomRawUrlProvider) && x != typeof(CustomUrlProvider)); + + var typeFactory = TypeFactory.Default; + + var customUrlProvider = typeFactory.CreateInstance(); + if (customUrlProvider.Initialize(url)) + { + return customUrlProvider; + } + + foreach (var providerType in providerTypes) + { + var provider = (ProviderBase) typeFactory.CreateInstance(providerType); + if (provider.Initialize(url)) + { + return provider; + } + } + + var customProvider = typeFactory.CreateInstance(); + if (customProvider.Initialize(url)) + { + return customProvider; + } + + return null; + } + } +} \ No newline at end of file diff --git a/src/GitLink/Providers/BitBucketProvider.cs b/src/GitLink/Providers/BitBucketProvider.cs index 637737d..b124d98 100644 --- a/src/GitLink/Providers/BitBucketProvider.cs +++ b/src/GitLink/Providers/BitBucketProvider.cs @@ -13,7 +13,7 @@ namespace GitLink.Providers public class BitBucketProvider : ProviderBase { - private readonly Regex _gitHubRegex = new Regex(@"(?(?(?:https://)?bitbucket\.org/(?[^/]+))/(?[^\./]+))"); + private readonly Regex _gitHubRegex = new Regex(@"(?(?(?:https://)?bitbucket\.org/(?[^/]+))/(?[^/]+))"); public BitBucketProvider() : base(new GitPreparer()) diff --git a/src/GitLink/Providers/CustomRawUrlProvider.cs b/src/GitLink/Providers/CustomRawUrlProvider.cs new file mode 100644 index 0000000..dc7b5d6 --- /dev/null +++ b/src/GitLink/Providers/CustomRawUrlProvider.cs @@ -0,0 +1,42 @@ +namespace GitLink.Providers +{ + using System; + using System.Text.RegularExpressions; + using GitTools.Git; + + public sealed class CustomRawUrlProvider : ProviderBase + { + private readonly Regex _regex = new Regex(@"https?://.+"); + + private string _rawUrl; + + public CustomRawUrlProvider() + : base(new GitPreparer()) + { + } + + public override string RawGitUrl + { + get + { + return _rawUrl; + } + } + + public override bool Initialize(string url) + { + if (string.IsNullOrEmpty(url) || !_regex.IsMatch(url)) + { + return false; + } + + _rawUrl = url; + if (_rawUrl.EndsWith("/", StringComparison.Ordinal)) + { + _rawUrl = _rawUrl.TrimEnd('/'); + } + + return true; + } + } +} diff --git a/src/GitLink/Providers/CustomUrlProvider.cs b/src/GitLink/Providers/CustomUrlProvider.cs new file mode 100644 index 0000000..8086b42 --- /dev/null +++ b/src/GitLink/Providers/CustomUrlProvider.cs @@ -0,0 +1,48 @@ +namespace GitLink.Providers +{ + using GitTools.Git; + using System.Text.RegularExpressions; + + public sealed class CustomUrlProvider : ProviderBase + { + private const string FileNamePlaceHolder = "{filename}"; + private const string RevisionPlaceHolder = "{revision}"; + private readonly Regex _regexUrl = new Regex(@"https?://.+"); + + private string _rawUrl; + + public CustomUrlProvider() + : base(new GitPreparer()) + { + } + + public override string RawGitUrl + { + get + { + return _rawUrl; + } + } + + public override bool Initialize(string url) + { + if (string.IsNullOrEmpty(url) || !_regexUrl.IsMatch(url) || + (!url.Contains(FileNamePlaceHolder) && !url.Contains(RevisionPlaceHolder))) + { + return false; + } + + if(url.Contains(FileNamePlaceHolder)) + { + _rawUrl = url.Replace(FileNamePlaceHolder, "%var2%"); + } + + if (url.Contains(RevisionPlaceHolder)) + { + _rawUrl = _rawUrl.Replace(RevisionPlaceHolder, "{0}"); + } + + return true; + } + } +} diff --git a/src/GitLink/Providers/ProviderManager.cs b/src/GitLink/Providers/ProviderManager.cs index e7df24c..8fc4304 100644 --- a/src/GitLink/Providers/ProviderManager.cs +++ b/src/GitLink/Providers/ProviderManager.cs @@ -14,7 +14,7 @@ public class ProviderManager : IProviderManager { public ProviderBase GetProvider(string url) { - var providerTypes = TypeCache.GetTypes(x => typeof(ProviderBase).IsAssignableFromEx(x) && !x.IsAbstract); + var providerTypes = TypeCache.GetTypes(x => typeof(ProviderBase).IsAssignableFromEx(x) && !x.IsAbstract && x != typeof(CustomRawUrlProvider)); var typeFactory = TypeFactory.Default; @@ -27,6 +27,12 @@ public ProviderBase GetProvider(string url) } } + var customProvider = typeFactory.CreateInstance(); + if (customProvider.Initialize(url)) + { + return customProvider; + } + return null; } }