From ee4f464af81fd049b8de6cc6bc4ee4def88d9e4d Mon Sep 17 00:00:00 2001 From: Adam Ralph Date: Fri, 24 Aug 2018 17:26:42 +0200 Subject: [PATCH] green: RepoWithHistory, EmptyRepo --- .gitignore | 3 + MinVer.sln | 40 +++++++ MinVer/MinVer.csproj | 13 +++ MinVer/Version.cs | 121 ++++++++++++++++++++ MinVer/Versioner.cs | 45 ++++++++ MinVerTests/Infra/AssertFile.cs | 23 ++++ MinVerTests/MinVerTests.csproj | 20 ++++ MinVerTests/Versioning.cs | 196 ++++++++++++++++++++++++++++++++ MinVerTests/general.txt | 52 +++++++++ README.md | 51 +++++++++ appveyor.yml | 17 +++ assets/min-ver.png | Bin 0 -> 3474 bytes build.cmd | 4 + build.sh | 4 + targets/Program.cs | 20 ++++ targets/Targets.csproj | 14 +++ 16 files changed, 623 insertions(+) create mode 100644 .gitignore create mode 100644 MinVer.sln create mode 100644 MinVer/MinVer.csproj create mode 100644 MinVer/Version.cs create mode 100644 MinVer/Versioner.cs create mode 100644 MinVerTests/Infra/AssertFile.cs create mode 100644 MinVerTests/MinVerTests.csproj create mode 100644 MinVerTests/Versioning.cs create mode 100644 MinVerTests/general.txt create mode 100644 README.md create mode 100644 appveyor.yml create mode 100644 assets/min-ver.png create mode 100644 build.cmd create mode 100755 build.sh create mode 100644 targets/Program.cs create mode 100644 targets/Targets.csproj diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..07b26ed1 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +.vs/ +bin/ +obj/ diff --git a/MinVer.sln b/MinVer.sln new file mode 100644 index 00000000..6a8ed7ae --- /dev/null +++ b/MinVer.sln @@ -0,0 +1,40 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.28010.2003 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MinVerTests", "MinVerTests\MinVerTests.csproj", "{57307B80-9750-4D4F-91DC-EE28EC89EDDD}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MinVer", "MinVer\MinVer.csproj", "{EE44EA11-CEBE-49C7-9F23-EE8AE6FDFCFB}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "targets", "targets\Targets.csproj", "{0F266E8B-E818-41A8-B93C-9E80E9446AC6}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {57307B80-9750-4D4F-91DC-EE28EC89EDDD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {57307B80-9750-4D4F-91DC-EE28EC89EDDD}.Debug|Any CPU.Build.0 = Debug|Any CPU + {57307B80-9750-4D4F-91DC-EE28EC89EDDD}.Release|Any CPU.ActiveCfg = Release|Any CPU + {57307B80-9750-4D4F-91DC-EE28EC89EDDD}.Release|Any CPU.Build.0 = Release|Any CPU + {EE44EA11-CEBE-49C7-9F23-EE8AE6FDFCFB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {EE44EA11-CEBE-49C7-9F23-EE8AE6FDFCFB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {EE44EA11-CEBE-49C7-9F23-EE8AE6FDFCFB}.Release|Any CPU.ActiveCfg = Release|Any CPU + {EE44EA11-CEBE-49C7-9F23-EE8AE6FDFCFB}.Release|Any CPU.Build.0 = Release|Any CPU + {0F266E8B-E818-41A8-B93C-9E80E9446AC6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0F266E8B-E818-41A8-B93C-9E80E9446AC6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0F266E8B-E818-41A8-B93C-9E80E9446AC6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0F266E8B-E818-41A8-B93C-9E80E9446AC6}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {9A50D1D0-1F93-4845-829D-C33C590C82F4} + EndGlobalSection + GlobalSection(Performance) = preSolution + HasPerformanceSessions = true + EndGlobalSection +EndGlobal diff --git a/MinVer/MinVer.csproj b/MinVer/MinVer.csproj new file mode 100644 index 00000000..ef5f32d9 --- /dev/null +++ b/MinVer/MinVer.csproj @@ -0,0 +1,13 @@ + + + + latest + netstandard2.0 + + + + + + + + diff --git a/MinVer/Version.cs b/MinVer/Version.cs new file mode 100644 index 00000000..6ae17ca9 --- /dev/null +++ b/MinVer/Version.cs @@ -0,0 +1,121 @@ +namespace MinVer +{ + using System; + using System.Collections.Generic; + using System.Globalization; + using System.Linq; + using static System.Math; + +#pragma warning disable CA1036 // Override methods on comparable types + public class Version : IComparable +#pragma warning restore CA1036 // Override methods on comparable types + { + private readonly int major; + private readonly int minor; + private readonly int patch; + private readonly List preReleaseIdentifiers; + + public Version() => this.preReleaseIdentifiers = new List { "alpha", "0" }; + + public Version(int major, int minor, int patch, IEnumerable preReleaseIdentifiers) + { + this.major = major; + this.minor = minor; + this.patch = patch; + this.preReleaseIdentifiers = preReleaseIdentifiers?.ToList() ?? new List(); + } + + public override string ToString() => + $"{this.major}.{this.minor}.{this.patch}{(this.preReleaseIdentifiers.Count == 0 ? "" : $"-{string.Join(".", this.preReleaseIdentifiers)}")}"; + + public int CompareTo(Version other) + { + var major = this.major.CompareTo(other.major); + if (major != 0) + { + return major; + } + + var minor = this.minor.CompareTo(other.minor); + if (minor != 0) + { + return minor; + } + + var patch = this.patch.CompareTo(other.patch); + if (patch != 0) + { + return patch; + } + + if (this.preReleaseIdentifiers.Count > 0 && other.preReleaseIdentifiers.Count == 0) + { + return -1; + } + + if (this.preReleaseIdentifiers.Count == 0 && other.preReleaseIdentifiers.Count > 0) + { + return 1; + } + + var maxCount = Max(this.preReleaseIdentifiers.Count, other.preReleaseIdentifiers.Count); + for (var index = 0; index < maxCount; ++index) + { + if (this.preReleaseIdentifiers.Count == index && other.preReleaseIdentifiers.Count > index) + { + return -1; + } + + if (this.preReleaseIdentifiers.Count > index && other.preReleaseIdentifiers.Count == index) + { + return 1; + } + + if (int.TryParse(this.preReleaseIdentifiers[index], out var thisNumber) && int.TryParse(other.preReleaseIdentifiers[index], out var otherNumber)) + { + var number = thisNumber.CompareTo(otherNumber); + if (number != 0) + { + return number; + } + } + else + { + var text = string.CompareOrdinal(this.preReleaseIdentifiers[index], other.preReleaseIdentifiers[index]); + if (text != 0) + { + return text; + } + } + } + + return 0; + } + + public Version AddHeight(int height) => + height == 0 + ? new Version(this.major, this.minor, this.patch, this.preReleaseIdentifiers) + : this.preReleaseIdentifiers.Count == 0 + ? new Version(this.major, this.minor + 1, 0, new[] { "alpha", "0", height.ToString(CultureInfo.InvariantCulture) }) + : new Version(this.major, this.minor, this.patch, this.preReleaseIdentifiers.Concat(new[] { height.ToString(CultureInfo.InvariantCulture) })); + + public static Version ParseOrDefault(string text) + { + if (text == default) + { + return null; + } + + var numbersAndPreRelease = text.Split(new[] { '-' }, 2); + var numbers = numbersAndPreRelease[0].Split('.'); + + return + numbers.Length == 3 && + int.TryParse(numbers[0], out var major) && + int.TryParse(numbers[1], out var minor) && + int.TryParse(numbers[2], out var patch) + ? new Version(major, minor, patch, numbersAndPreRelease.Length == 2 ? numbersAndPreRelease[1].Split('.') : null) + : null; + } + } +} diff --git a/MinVer/Versioner.cs b/MinVer/Versioner.cs new file mode 100644 index 00000000..841fc618 --- /dev/null +++ b/MinVer/Versioner.cs @@ -0,0 +1,45 @@ +namespace MinVer +{ + using System.Linq; + using LibGit2Sharp; + + public static class Versioner + { + public static Version GetVersion(string path) + { + using (var repo = new Repository(path)) + { + return GetVersion(repo.Commits.FirstOrDefault(), 0, repo.Tags); + } + } + + private static Version GetVersion(Commit commit, int height, TagCollection tags) + { + if (commit == default) + { + return new Version(); + } + + var version = GetVersionOrDefault(tags, commit); + + if (version != default) + { + return version.AddHeight(height); + } + + return commit.Parents + .Select(parent => GetVersion(parent, height + 1, tags)) + .Where(_ => _ != default) + .OrderByDescending(_ => _) + .FirstOrDefault() ?? + new Version().AddHeight(height); + } + + private static Version GetVersionOrDefault(TagCollection tags, Commit commit) => tags + .Where(tag => tag.Target.Sha == commit.Sha) + .Select(tag => Version.ParseOrDefault(tag.FriendlyName)) + .Where(_ => _ != default) + .OrderByDescending(_ => _) + .FirstOrDefault(); + } +} diff --git a/MinVerTests/Infra/AssertFile.cs b/MinVerTests/Infra/AssertFile.cs new file mode 100644 index 00000000..bc0ba229 --- /dev/null +++ b/MinVerTests/Infra/AssertFile.cs @@ -0,0 +1,23 @@ +namespace MinVerTests.Infra +{ + using System; + using System.IO; + using System.Threading.Tasks; + + public static class AssertFile + { + public static async Task Contains(string expectedPath, string actual) + { + if (actual != await File.ReadAllTextAsync(expectedPath)) + { + var actualPath = Path.Combine( + Path.GetDirectoryName(expectedPath), + Path.GetFileNameWithoutExtension(expectedPath) + "-actual" + Path.GetExtension(expectedPath)); + + await File.WriteAllTextAsync(actualPath, actual); + + throw new Exception($"{actualPath} does not contain the contents of {expectedPath}."); + } + } + } +} diff --git a/MinVerTests/MinVerTests.csproj b/MinVerTests/MinVerTests.csproj new file mode 100644 index 00000000..ee820011 --- /dev/null +++ b/MinVerTests/MinVerTests.csproj @@ -0,0 +1,20 @@ + + + + latest + netcoreapp2.1 + + + + + + + + + + + + + + + diff --git a/MinVerTests/Versioning.cs b/MinVerTests/Versioning.cs new file mode 100644 index 00000000..725929aa --- /dev/null +++ b/MinVerTests/Versioning.cs @@ -0,0 +1,196 @@ +namespace MinVerTests +{ + using System; + using System.Collections.Generic; + using System.IO; + using System.Linq; + using System.Threading.Tasks; + using LibGit2Sharp; + using MinVer; + using MinVerTests.Infra; + using Xbehave; + using Xunit; + using static SimpleExec.Command; + + public static class Versioning + { + [Scenario] + [Example( + "general", + @" +git commit --allow-empty -m ""."" +git commit --allow-empty -m ""."" +git commit --allow-empty -m ""."" +git tag 0.0.0-alpha.1 +git commit --allow-empty -m ""."" +git commit --allow-empty -m ""."" +git commit --allow-empty -m ""."" +git tag 0.0.0 +git commit --allow-empty -m ""."" +git commit --allow-empty -m ""."" +git commit --allow-empty -m ""."" +git tag 0.1.0-beta.1 +git commit --allow-empty -m ""."" +git commit --allow-empty -m ""."" +git commit --allow-empty -m ""."" +git tag 0.1.0 +git commit --allow-empty -m ""."" +git commit --allow-empty -m ""."" +git commit --allow-empty -m ""."" +git tag 1.0.0-alpha.1 +git commit --allow-empty -m ""."" +git commit --allow-empty -m ""."" +git commit --allow-empty -m ""."" +git tag 1.0.0-alpha.2 +git commit --allow-empty -m ""."" +git commit --allow-empty -m ""."" +git commit --allow-empty -m ""."" +git tag 1.0.0-beta.1 +git commit --allow-empty -m ""."" +git commit --allow-empty -m ""."" +git commit --allow-empty -m ""."" +git tag 1.0.0-beta.2 +git commit --allow-empty -m ""."" +git commit --allow-empty -m ""."" +git commit --allow-empty -m ""."" +git tag 1.0.0-rc.1 +git commit --allow-empty -m ""."" +git commit --allow-empty -m ""."" +git commit --allow-empty -m ""."" +git tag 1.0.0-rc.2 +git tag 1.0.0 +git checkout -b foo +git commit --allow-empty -m ""."" +git commit --allow-empty -m ""."" +git commit --allow-empty -m ""."" +git tag 1.1.0-alpha.1 +git checkout master +git commit --allow-empty -m ""."" +git commit --allow-empty -m ""."" +git commit --allow-empty -m ""."" +git tag 1.1.0-alpha.2 +git commit --allow-empty -m ""."" +git commit --allow-empty -m ""."" +git commit --allow-empty -m ""."" +git merge foo --no-edit +git commit --allow-empty -m ""."" +git commit --allow-empty -m ""."" +git commit --allow-empty -m ""."" +git tag 1.1.0-beta.1 +git commit --allow-empty -m ""."" +git commit --allow-empty -m ""."" +git commit --allow-empty -m ""."" +git tag 1.1.0-beta.2 +git tag 1.1.0-beta.10 +git commit --allow-empty -m ""."" +git commit --allow-empty -m ""."" +git commit --allow-empty -m ""."" +git tag 1.1.0-rc.1 +git tag 1.1.0 +")] + public static void RepoWithHistory(string name, string historicalCommands, string path) + { + $"Given a git repository in `{path = Path.Combine(Path.GetTempPath(), name)}` with a history of branches and/or tags" + .x(async () => + { + if (!Directory.Exists(path)) + { + Directory.CreateDirectory(path); + } + + if (Directory.Exists(Path.Combine(path, ".git"))) + { + await RunAsync("git", "checkout master", path); + await RunAsync("git", "reset root --hard", path); + using (var repo = new Repository(path)) + { + foreach (var branch in repo.Branches.Where(b => b.FriendlyName != "master")) + { + repo.Branches.Remove(branch); + } + + foreach (var tag in repo.Tags.Where(b => b.FriendlyName != "root")) + { + repo.Tags.Remove(tag); + } + } + } + else + { + await RunAsync("git", "init", path); + await RunAsync("git", @"config user.email ""johndoe @tempuri.org""", path); + await RunAsync("git", @"config user.name ""John Doe""", path); + await RunAsync("git", @"commit --allow-empty -m "".""", path); + await RunAsync("git", "tag root", path); + } + + foreach (var command in historicalCommands.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries)) + { + var nameAndArgs = command.Split(" ", 2); + await RunAsync(nameAndArgs[0], nameAndArgs[1], path); + await Task.Delay(100); + } + }); + + "When the version is determined for every commit" + .x(() => + { + var versionCounts = new Dictionary(); + + using (var repo = new Repository(path)) + { + foreach (var commit in repo.Commits) + { + Commands.Checkout(repo, commit); + + var version = Versioner.GetVersion(path); + var versionString = version.ToString(); + var tagName = $"v/{versionString}"; + + if (!repo.Tags.Any(tag => tag.Target.Sha == commit.Sha && tag.FriendlyName == tagName)) + { + versionCounts.TryGetValue(versionString, out var oldVersionCount); + var versionCount = oldVersionCount + 1; + versionCounts[versionString] = versionCount; + + tagName = versionCount > 1 + ? $"v({versionCount})/{versionString}" + : tagName; + + repo.Tags.Add(tagName, commit); + } + } + + Commands.Checkout(repo, repo.Branches["master"]); + } + }); + + "Then the versions are as expected" + .x(async () => await AssertFile.Contains($"../../../{name}.txt", await ReadAsync("git", "log --graph --pretty=format:'%d'", path))); + } + + [Scenario] + public static void EmptyRepo(string name, string path, MinVer.Version version) + { + $"Given an empty repo git repository in `{path = Path.Combine(Path.GetTempPath(), name = "empty-repo")}`" + .x(async () => + { + if (!Directory.Exists(path)) + { + Directory.CreateDirectory(path); + } + + if (!Directory.Exists(Path.Combine(path, ".git"))) + { + await RunAsync("git", "init", path); + } + }); + + "When the version is determined" + .x(() => version = Versioner.GetVersion(path)); + + "Then the version is 0.0.0-alpha.0" + .x(() => Assert.Equal("0.0.0-alpha.0", version.ToString())); + } + } +} diff --git a/MinVerTests/general.txt b/MinVerTests/general.txt new file mode 100644 index 00000000..44d85591 --- /dev/null +++ b/MinVerTests/general.txt @@ -0,0 +1,52 @@ +* ' (HEAD -> master, tag: v/1.1.0, tag: 1.1.0-rc.1, tag: 1.1.0)' +* ' (tag: v/1.1.0-beta.10.2)' +* ' (tag: v/1.1.0-beta.10.1)' +* ' (tag: v/1.1.0-beta.10, tag: 1.1.0-beta.2, tag: 1.1.0-beta.10)' +* ' (tag: v/1.1.0-beta.1.2)' +* ' (tag: v/1.1.0-beta.1.1)' +* ' (tag: v/1.1.0-beta.1, tag: 1.1.0-beta.1)' +* ' (tag: v/1.1.0-alpha.2.6)' +* ' (tag: v/1.1.0-alpha.2.5)' +* ' (tag: v/1.1.0-alpha.2.4)' +|\ +| * ' (tag: v/1.1.0-alpha.1, tag: 1.1.0-alpha.1, foo)' +| * ' (tag: v(2)/1.1.0-alpha.0.2)' +| * ' (tag: v(2)/1.1.0-alpha.0.1)' +* | ' (tag: v/1.1.0-alpha.2.3)' +* | ' (tag: v/1.1.0-alpha.2.2)' +* | ' (tag: v/1.1.0-alpha.2.1)' +* | ' (tag: v/1.1.0-alpha.2, tag: 1.1.0-alpha.2)' +* | ' (tag: v/1.1.0-alpha.0.2)' +* | ' (tag: v/1.1.0-alpha.0.1)' +|/ +* ' (tag: v/1.0.0, tag: 1.0.0-rc.2, tag: 1.0.0)' +* ' (tag: v/1.0.0-rc.1.2)' +* ' (tag: v/1.0.0-rc.1.1)' +* ' (tag: v/1.0.0-rc.1, tag: 1.0.0-rc.1)' +* ' (tag: v/1.0.0-beta.2.2)' +* ' (tag: v/1.0.0-beta.2.1)' +* ' (tag: v/1.0.0-beta.2, tag: 1.0.0-beta.2)' +* ' (tag: v/1.0.0-beta.1.2)' +* ' (tag: v/1.0.0-beta.1.1)' +* ' (tag: v/1.0.0-beta.1, tag: 1.0.0-beta.1)' +* ' (tag: v/1.0.0-alpha.2.2)' +* ' (tag: v/1.0.0-alpha.2.1)' +* ' (tag: v/1.0.0-alpha.2, tag: 1.0.0-alpha.2)' +* ' (tag: v/1.0.0-alpha.1.2)' +* ' (tag: v/1.0.0-alpha.1.1)' +* ' (tag: v/1.0.0-alpha.1, tag: 1.0.0-alpha.1)' +* ' (tag: v/0.2.0-alpha.0.2)' +* ' (tag: v/0.2.0-alpha.0.1)' +* ' (tag: v/0.1.0, tag: 0.1.0)' +* ' (tag: v/0.1.0-beta.1.2)' +* ' (tag: v/0.1.0-beta.1.1)' +* ' (tag: v/0.1.0-beta.1, tag: 0.1.0-beta.1)' +* ' (tag: v/0.1.0-alpha.0.2)' +* ' (tag: v/0.1.0-alpha.0.1)' +* ' (tag: v/0.0.0, tag: 0.0.0)' +* ' (tag: v/0.0.0-alpha.1.2)' +* ' (tag: v/0.0.0-alpha.1.1)' +* ' (tag: v/0.0.0-alpha.1, tag: 0.0.0-alpha.1)' +* ' (tag: v/0.0.0-alpha.0.2)' +* ' (tag: v/0.0.0-alpha.0.1)' +* ' (tag: v/0.0.0-alpha.0, tag: root)' \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 00000000..b8e98307 --- /dev/null +++ b/README.md @@ -0,0 +1,51 @@ + + +# MinVer + +_[![nuget](https://img.shields.io/nuget/v/MinVer.svg?style=flat)](https://www.nuget.org/packages/MinVer)_ +_[![Build status](https://ci.appveyor.com/api/projects/status/0ai8j3x4tg6w3ima/branch/master?svg=true)](https://ci.appveyor.com/project/adamralph/min-ver/branch/master)_ + +A minimalistic [.NET package](https://www.nuget.org/packages/MinVer) for versioning .NET projects using Git tags. + +Platform support: [.NET Standard 1.3 and upwards](https://docs.microsoft.com/en-us/dotnet/standard/net-standard). + +## Quick start + +1. Remove any `Version` or `VersionPrefix` elements from your project. (You can keep `VersionSuffix`, but it should be restricted to [build metadata](https://semver.org/#spec-item-10) only.) +1. `dotnet add package MinVer` +2. `dotnet build` +3. Your project will be versioned according to the latest tag found in the commit history. + +## How it works + +### Inputs + +- The last tag on HEAD or it's ancestors which represents a [SemVer](https://semver.org) version number.* +- By how many commits HEAD is ahead of the tag (known as "height"). + +\* _Each time the history diverges to two parents, both branches are followed, and the last tag is found on each. The tag with the latest version number is used._ + +### Algorithm + +- If the height is zero (i.e. the tag is on HEAD), then the HEAD version matches the tag. +- If the height is non-zero (i.e. the tag is on an older commit), then: + - If the last tag is an RTM version, `MAJOR.MINOR.PATCH`, then the HEAD version is `MAJOR.MINOR+1.PATCH-alpha.0.{height}`. + - If the last tag is a pre-release version, `MAJOR.MINOR.PATCH-{pre-release identifiers}`, then the HEAD version is `MAJOR.MINOR.PATCH-{pre-release identifiers}.{height}`. + +## FAQ + +### Does MinVer work with my chosen branching strategy? + +Yes! MinVer doesn't care about branches. It's all about the tags! + +That means MinVer is compatible with [Git Flow](https://nvie.com/posts/a-successful-git-branching-model/), [GitHub Flow](https://guides.github.com/introduction/flow/), [Release Flow](https://docs.microsoft.com/en-us/azure/devops/learn/devops-at-microsoft/release-flow), and any other exotic flow. + +### What if it all goes wrong? + +If your tags get into a mess and you can't find a way out, you can temporarily switch off MinVer and switch back to the old way of doing things by simply adding a `Version` or `VersionPrefix` element to your project. When you've figure out the problem, remove the element and MinVer will resume active duty. + +The same applies if you find a bug in MinVer (consider that a challenge!) and you're waiting for a fix, but you need to ship your software in the meantime. + +--- + +[Tag](https://thenounproject.com/term/tag/938952) by [Ananth](https://thenounproject.com/ananthshas/) from [the Noun Project](https://thenounproject.com/). diff --git a/appveyor.yml b/appveyor.yml new file mode 100644 index 00000000..79cf356a --- /dev/null +++ b/appveyor.yml @@ -0,0 +1,17 @@ +version: '{build}' +image: +- Visual Studio 2017 +- Ubuntu +pull_requests: + do_not_increment_build_number: true +branches: + only: + - master + - /release-.*/ +build_script: +- cmd: ./build.cmd +- sh: ./build.sh +test: off +artifacts: +- path: ./**/*.nupkg +deploy: off diff --git a/assets/min-ver.png b/assets/min-ver.png new file mode 100644 index 0000000000000000000000000000000000000000..a416e4debce55c5164ed8532133521f69db2ef71 GIT binary patch literal 3474 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 RunAsync("dotnet", "build MinVer.sln --configuration Release")); + + Target( + "test", + DependsOn("build"), + () => RunAsync("dotnet", $"test ./MinVerTests/MinVerTests.csproj --configuration Release --no-build --verbosity=normal")); + + return RunTargetsAsync(args); + } +} diff --git a/targets/Targets.csproj b/targets/Targets.csproj new file mode 100644 index 00000000..3e425f84 --- /dev/null +++ b/targets/Targets.csproj @@ -0,0 +1,14 @@ + + + + latest + Exe + netcoreapp2.1 + + + + + + + +