Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add path filtering support to version.json #399

Merged
merged 26 commits into from Jan 20, 2020
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
100 changes: 100 additions & 0 deletions src/NerdBank.GitVersioning.Tests/FilterPathTests.cs
@@ -0,0 +1,100 @@
using System;
using System.IO;
using Nerdbank.GitVersioning;
using Xunit;

public class FilterPathTests
{
[Theory]
[InlineData("relativepath.txt", "foo", "foo/relativepath.txt")]
[InlineData("./relativepath.txt", "foo", "foo/relativepath.txt")]
[InlineData("./dir\\hi/relativepath.txt", "foo", "foo/dir/hi/relativepath.txt")]
[InlineData(".\\relativepath.txt", "foo", "foo/relativepath.txt")]
[InlineData(":^relativepath.txt", "foo", "foo/relativepath.txt")]
[InlineData(":!relativepath.txt", "foo", "foo/relativepath.txt")]
[InlineData(":!/absolutepath.txt", "foo", "absolutepath.txt")]
[InlineData(":!\\absolutepath.txt", "foo", "absolutepath.txt")]
[InlineData("../bar/relativepath.txt", "foo", "bar/relativepath.txt")]
[InlineData(":/", "foo", "")]
[InlineData(":/absolutepath.txt", "foo", "absolutepath.txt")]
[InlineData(":/bar/absolutepath.txt", "foo", "bar/absolutepath.txt")]
[InlineData(":\\bar\\absolutepath.txt", "foo", "bar/absolutepath.txt")]
public void CanBeParsedToRepoRelativePath(string pathSpec, string relativeTo, string expected)
{
Assert.Equal(expected, new FilterPath(pathSpec, relativeTo).RepoRelativePath);
}

[Theory]
[InlineData(":!.", "foo", "foo")]
[InlineData(":!.", "foo", "foo/")]
[InlineData(":!.", "foo", "foo/relativepath.txt")]
[InlineData(":!relativepath.txt", "foo", "foo/relativepath.txt")]
[InlineData(":^relativepath.txt", "foo", "foo/relativepath.txt")]
[InlineData(":^./relativepath.txt", "foo", "foo/relativepath.txt")]
[InlineData(":^../bar", "foo", "bar")]
[InlineData(":^../bar", "foo", "bar/")]
[InlineData(":^../bar", "foo", "bar/somefile.txt")]
[InlineData(":^/absolute.txt", "foo", "absolute.txt")]
public void PathsCanBeExcluded(string pathSpec, string relativeTo, string repoRelativePath)
{
Assert.True(new FilterPath(pathSpec, relativeTo, true).Excludes(repoRelativePath));
Assert.True(new FilterPath(pathSpec, relativeTo, false).Excludes(repoRelativePath));
}

[Theory]
[InlineData(":!.", "foo", "foo.txt")]
[InlineData(":^relativepath.txt", "foo", "foo2/relativepath.txt")]
[InlineData(":^/absolute.txt", "foo", "absolute.txt.bak")]
[InlineData(":^/absolute.txt", "foo", "absolute")]

// Not exclude paths
[InlineData(":/absolute.txt", "foo", "absolute.txt")]
[InlineData("/absolute.txt", "foo", "absolute.txt")]
[InlineData("../root.txt", "foo", "root.txt")]
[InlineData("relativepath.txt", "foo", "foo/relativepath.txt")]
public void NonMatchingPathsAreNotExcluded(string pathSpec, string relativeTo, string repoRelativePath)
{
Assert.False(new FilterPath(pathSpec, relativeTo, true).Excludes(repoRelativePath));
Assert.False(new FilterPath(pathSpec, relativeTo, false).Excludes(repoRelativePath));
}

[Theory]
[InlineData(":!.", "foo", "Foo")]
[InlineData(":!.", "foo", "Foo/")]
[InlineData(":!.", "foo", "Foo/relativepath.txt")]
[InlineData(":!RelativePath.txt", "foo", "foo/relativepath.txt")]
[InlineData(":^relativepath.txt", "foo", "Foo/RelativePath.txt")]
[InlineData(":^./relativepath.txt", "Foo", "foo/RelativePath.txt")]
[InlineData(":^../bar", "foo", "Bar")]
[InlineData(":^../bar", "foo", "Bar/")]
[InlineData(":^../bar", "foo", "Bar/SomeFile.txt")]
[InlineData(":^/absOLUte.txt", "foo", "Absolute.TXT")]
public void PathsCanBeExcludedCaseInsensitive(string pathSpec, string relativeTo, string repoRelativePath)
{
Assert.True(new FilterPath(pathSpec, relativeTo, true).Excludes(repoRelativePath));
}

[Theory]
[InlineData(":!.", "foo", "Foo")]
[InlineData(":!.", "foo", "Foo/")]
[InlineData(":!.", "foo", "Foo/relativepath.txt")]
[InlineData(":!RelativePath.txt", "foo", "foo/relativepath.txt")]
[InlineData(":^relativepath.txt", "foo", "Foo/RelativePath.txt")]
[InlineData(":^./relativepath.txt", "Foo", "foo/RelativePath.txt")]
[InlineData(":^../bar", "foo", "Bar")]
[InlineData(":^../bar", "foo", "Bar/")]
[InlineData(":^../bar", "foo", "Bar/SomeFile.txt")]
[InlineData(":^/absOLUte.txt", "foo", "Absolute.TXT")]
public void NonMatchingPathsAreNotExcludedCaseSensitive(string pathSpec, string relativeTo, string repoRelativePath)
{
Assert.False(new FilterPath(pathSpec, relativeTo, false).Excludes(repoRelativePath));
}

[Fact]
public void InvalidPathspecsThrow()
{
Assert.Throws<ArgumentNullException>(() => new FilterPath(null, ""));
Assert.Throws<ArgumentException>(() => new FilterPath("", ""));
Assert.Throws<FormatException>(() => new FilterPath(":?", ""));
}
}
88 changes: 79 additions & 9 deletions src/NerdBank.GitVersioning.Tests/GitExtensionsTests.cs
Expand Up @@ -13,6 +13,8 @@

public class GitExtensionsTests : RepoTestBase
{
private readonly IReadOnlyList<FilterPath> emptyFilterPath = null;

public GitExtensionsTests(ITestOutputHelper Logger)
: base(Logger)
{
Expand All @@ -23,8 +25,8 @@ public GitExtensionsTests(ITestOutputHelper Logger)
public void GetHeight_EmptyRepo()
{
Branch head = this.Repo.Head;
Assert.Throws<InvalidOperationException>(() => head.GetHeight());
Assert.Throws<InvalidOperationException>(() => head.GetHeight(c => true));
Assert.Throws<InvalidOperationException>(() => head.GetHeight(this.emptyFilterPath));
Assert.Throws<InvalidOperationException>(() => head.GetHeight(this.emptyFilterPath, c => true));
}

[Fact]
Expand All @@ -33,11 +35,11 @@ public void GetHeight_SinglePath()
var first = this.Repo.Commit("First", this.Signer, this.Signer, new CommitOptions { AllowEmptyCommit = true });
var second = this.Repo.Commit("Second", this.Signer, this.Signer, new CommitOptions { AllowEmptyCommit = true });
var third = this.Repo.Commit("Third", this.Signer, this.Signer, new CommitOptions { AllowEmptyCommit = true });
Assert.Equal(3, this.Repo.Head.GetHeight());
Assert.Equal(3, this.Repo.Head.GetHeight(c => true));
Assert.Equal(3, this.Repo.Head.GetHeight(this.emptyFilterPath));
Assert.Equal(3, this.Repo.Head.GetHeight(this.emptyFilterPath, c => true));

Assert.Equal(2, this.Repo.Head.GetHeight(c => c != first));
Assert.Equal(1, this.Repo.Head.GetHeight(c => c != second));
Assert.Equal(2, this.Repo.Head.GetHeight(this.emptyFilterPath, c => c != first));
Assert.Equal(1, this.Repo.Head.GetHeight(this.emptyFilterPath, c => c != second));
}

[Fact]
Expand All @@ -56,13 +58,13 @@ public void GetHeight_Merge()
this.Repo.Merge(secondCommit, new Signature("t", "t@t.com", DateTimeOffset.Now), new MergeOptions { FastForwardStrategy = FastForwardStrategy.NoFastForward });

// While we've created 8 commits, the tallest height is only 7.
Assert.Equal(7, this.Repo.Head.GetHeight());
Assert.Equal(7, this.Repo.Head.GetHeight(this.emptyFilterPath));

// Now stop enumerating early on just one branch of the ancestry -- the number should remain high.
Assert.Equal(7, this.Repo.Head.GetHeight(c => c != secondCommit));
Assert.Equal(7, this.Repo.Head.GetHeight(this.emptyFilterPath, c => c != secondCommit));

// This time stop in both branches of history, and verify that we count the taller one.
Assert.Equal(3, this.Repo.Head.GetHeight(c => c != secondCommit && c != branchCommits[2]));
Assert.Equal(3, this.Repo.Head.GetHeight(this.emptyFilterPath, c => c != secondCommit && c != branchCommits[2]));
}

[Fact]
Expand Down Expand Up @@ -121,6 +123,74 @@ public void GetVersionHeight_VersionJsonHasParsingErrorsInHistory()
Assert.Equal(0, this.Repo.GetVersionHeight());
}

[Fact]
AArnott marked this conversation as resolved.
Show resolved Hide resolved
public void GetVersionHeight_IntroducingFiltersIncrementsHeight()
{
this.WriteVersionFile(relativeDirectory: "some-sub-dir");
Assert.Equal(1, this.Repo.GetVersionHeight("some-sub-dir"));

var versionData = VersionOptions.FromVersion(new Version("1.2"));
versionData.PathFilters = new[] { "./" };
this.WriteVersionFile(versionData, "some-sub-dir");
Assert.Equal(2, this.Repo.GetVersionHeight("some-sub-dir"));
}

[Fact]
public void GetVersionHeight_IncludeFilter()
{
var versionData = VersionOptions.FromVersion(new Version("1.2"));
versionData.PathFilters = new[] { "./" };
this.WriteVersionFile(versionData, "some-sub-dir");
Assert.Equal(1, this.Repo.GetVersionHeight("some-sub-dir"));

// Expect commit outside of project tree to not affect version height
var otherFilePath = Path.Combine(this.RepoPath, "my-file.txt");
File.WriteAllText(otherFilePath, "hello");
Commands.Stage(this.Repo, otherFilePath);
this.Repo.Commit("Add other file outside of project root", this.Signer, this.Signer);
Assert.Equal(1, this.Repo.GetVersionHeight("some-sub-dir"));

// Expect commit inside project tree to affect version height
var containedFilePath = Path.Combine(this.RepoPath, "some-sub-dir", "another-file.txt");
File.WriteAllText(containedFilePath, "hello");
Commands.Stage(this.Repo, containedFilePath);
this.Repo.Commit("Add file within project root", this.Signer, this.Signer);
Assert.Equal(2, this.Repo.GetVersionHeight("some-sub-dir"));
}

[Fact]
public void GetVersionHeight_IncludeExcludeFilter()
{
var versionData = VersionOptions.FromVersion(new Version("1.2"));
versionData.PathFilters = new[] { "./", ":^/some-sub-dir/ignore.txt", ":^excluded-dir" };
this.WriteVersionFile(versionData, "some-sub-dir");
Assert.Equal(1, this.Repo.GetVersionHeight("some-sub-dir"));

// Commit touching excluded path does not affect version height
var ignoredFilePath = Path.Combine(this.RepoPath, "some-sub-dir", "ignore.txt");
File.WriteAllText(ignoredFilePath, "hello");
Commands.Stage(this.Repo, ignoredFilePath);
this.Repo.Commit("Add excluded file", this.Signer, this.Signer);
Assert.Equal(1, this.Repo.GetVersionHeight("some-sub-dir"));

// Commit touching both excluded and included path does affect height
var includedFilePath = Path.Combine(this.RepoPath, "some-sub-dir", "another-file.txt");
File.WriteAllText(includedFilePath, "hello");
File.WriteAllText(ignoredFilePath, "changed");
Commands.Stage(this.Repo, includedFilePath);
Commands.Stage(this.Repo, ignoredFilePath);
this.Repo.Commit("Change both excluded and included file", this.Signer, this.Signer);
Assert.Equal(2, this.Repo.GetVersionHeight("some-sub-dir"));

// Commit touching excluded directory does not affect version height
var fileInExcludedDirPath = Path.Combine(this.RepoPath, "some-sub-dir", "excluded-dir", "ignore.txt");
Directory.CreateDirectory(Path.GetDirectoryName(fileInExcludedDirPath));
File.WriteAllText(fileInExcludedDirPath, "hello");
Commands.Stage(this.Repo, fileInExcludedDirPath);
this.Repo.Commit("Add file to excluded dir", this.Signer, this.Signer);
Assert.Equal(2, this.Repo.GetVersionHeight("some-sub-dir"));
}

[Theory]
[InlineData("2.2-alpha", "2.2-rc", false)]
[InlineData("2.2-rc", "2.2", false)]
Expand Down
13 changes: 13 additions & 0 deletions src/NerdBank.GitVersioning.Tests/VersionFileTests.cs
Expand Up @@ -321,6 +321,19 @@ public void GetVersion_ReadReleaseSettings_BranchName()
Assert.Equal("someValue{version}", versionOptions.Release.BranchName);
}

[Fact]
public void GetVersion_ReadPathFilters()
{
var json = @"{ ""version"" : ""1.2"", ""pathFilters"" : [ "":/root.txt"", ""./hello"" ] }";
var path = Path.Combine(this.RepoPath, "version.json");
File.WriteAllText(path, json);

var versionOptions = VersionFile.GetVersion(this.RepoPath);

Assert.NotNull(versionOptions.PathFilters);
Assert.Equal(new[] {":/root.txt", "./hello"}, versionOptions.PathFilters);
}

[Fact]
public void GetVersion_String_MissingFile()
{
Expand Down