コード例 #1
0
        public void ShouldAppendToExistingChangelog()
        {
            var parser = new ConventionalCommitParser();

            var plainLinkBuilder = new PlainLinkBuilder();
            var changelog        = Changelog.Discover(_testDirectory);

            changelog.Write(new Version(1, 0, 0), new DateTimeOffset(), plainLinkBuilder, new List <ConventionalCommit>
            {
                parser.Parse(new TestCommit("a360d6a307909c6e571b29d4a329fd786c5d4543", "fix: a fix in version 1.0.0")),
            });

            changelog.Write(new Version(1, 1, 0), new DateTimeOffset(), plainLinkBuilder, new List <ConventionalCommit>
            {
                parser.Parse(new TestCommit("b360d6a307909c6e571b29d4a329fd786c5d4543", "fix: a fix in version 1.1.0")),
            });

            var changelogContents = File.ReadAllText(changelog.FilePath);

            changelogContents.ShouldContain("<a name=\"1.0.0\"></a>");
            changelogContents.ShouldContain("a fix in version 1.0.0");

            changelogContents.ShouldContain("<a name=\"1.1.0\"></a>");
            changelogContents.ShouldContain("a fix in version 1.1.0");
        }
コード例 #2
0
    public void ShouldHideFixSectionWhenHideIsTrue()
    {
        var plainLinkBuilder = new PlainLinkBuilder();
        var changelog        = ChangelogBuilder.CreateForPath(_testDirectory);

        changelog.Write(
            new Version(1, 1, 0),
            new DateTimeOffset(),
            plainLinkBuilder,
            new List <ConventionalCommit>
        {
            ConventionalCommitParser.Parse(new TestCommit("a360d6a307909c6e571b29d4a329fd786c5d4543", "fix: a fix")),
            ConventionalCommitParser.Parse(new TestCommit("b360d6a307909c6e571b29d4a329fd786c5d4543", "feat: a feature")),
        },
            ChangelogOptions.Default with
        {
            Sections = new[]
            {
                new ChangelogSection {
                    Type = "feat", Hidden = false, Section = "Features"
                },
                new ChangelogSection {
                    Type = "fix", Hidden = true, Section = "Bug Fixes"
                },
            }
        });
コード例 #3
0
        public void ConventionalCommitParser_Parse_SuccessfullyParsesMultipleLineCommit(string message, bool hasScope, bool hasBreakingToken)
        {
            // given commit message does not meet conventional commit standards
            var commit = new Mock <Commit>();

            commit.Setup(x => x.Message).Returns(message);

            // when we parse commit message
            var parser = new ConventionalCommitParser();
            var conventionalCommits = parser.Parse(new[] { commit.Object });
            ConventionalCommit conventionalCommit = conventionalCommits.FirstOrDefault();

            var trailer = conventionalCommit.Trailers.FirstOrDefault();

            // then we return a null value
            Assert.NotNull(conventionalCommit);
            Assert.NotNull(conventionalCommit.Type);
            Assert.NotNull(conventionalCommit.Subject);

            if (hasScope)
            {
                Assert.NotNull(conventionalCommit.Scope);
            }

            if (hasBreakingToken)
            {
                Assert.True(conventionalCommit.HasBreakingToken);
            }

            Assert.NotEmpty(conventionalCommit.Trailers);
            Assert.NotNull(trailer);
            Assert.NotNull(trailer.Type);
            Assert.NotNull(trailer.Subject);
        }
コード例 #4
0
    public void ShouldGenerateAChangelogForFixFeatAndBreakingCommits()
    {
        var plainLinkBuilder = new PlainLinkBuilder();
        var changelog        = ChangelogBuilder.CreateForPath(_testDirectory);

        changelog.Write(
            new Version(1, 1, 0),
            new DateTimeOffset(),
            plainLinkBuilder,
            new List <ConventionalCommit>
        {
            ConventionalCommitParser.Parse(new TestCommit("a360d6a307909c6e571b29d4a329fd786c5d4543", "fix: a fix")),
            ConventionalCommitParser.Parse(new TestCommit("b360d6a307909c6e571b29d4a329fd786c5d4543", "feat: a feature")),
            ConventionalCommitParser.Parse(
                new TestCommit("c360d6a307909c6e571b29d4a329fd786c5d4543", "feat: a breaking change feature\nBREAKING CHANGE: this will break everything")),
        },
            ChangelogOptions.Default);

        var changelogContents = File.ReadAllText(changelog.FilePath);

        var sb = new ChangelogStringBuilder();

        sb.Append(ChangelogOptions.Preamble);
        sb.Append("<a name=\"1.1.0\"></a>");
        sb.Append("## 1.1.0 (1-1-1)", 2);
        sb.Append("### Features", 2);
        sb.Append("* a breaking change feature");
        sb.Append("* a feature", 2);
        sb.Append("### Bug Fixes", 2);
        sb.Append("* a fix", 2);
        sb.Append("### Breaking Changes", 2);
        sb.Append("* a breaking change feature", 2);
        Assert.Equal(sb.Build(), changelogContents);
    }
コード例 #5
0
    public void ShouldUseFullHeaderAsSubjectIfNoTypeWasGivenButSubjectUsesColon()
    {
        var testCommit         = new TestCommit("c360d6a307909c6e571b29d4a329fd786c5d4543", "broadcast $destroy event: on scope destruction");
        var conventionalCommit = ConventionalCommitParser.Parse(testCommit);

        Assert.Equal(testCommit.Message, conventionalCommit.Subject);
    }
コード例 #6
0
        public void ShouldIncludeAllCommitsInChangelogWhenGiven()
        {
            var parser = new ConventionalCommitParser();

            var changelog = Changelog.Discover(_testDirectory);

            changelog.Write(new Version(1, 1, 0), new DateTimeOffset(), new List <ConventionalCommit>
            {
                parser.Parse(new TestCommit("chore: nothing important")),
                parser.Parse(new TestCommit("chore: some foo bar")),
            }, true);

            var changelogContents = File.ReadAllText(changelog.FilePath);

            changelogContents.ShouldContain("nothing important");
            changelogContents.ShouldContain("some foo bar");
        }
コード例 #7
0
    public void ShouldSupportExclamationMarkToSignifyingBreakingChanges(string commitMessage)
    {
        var testCommit         = new TestCommit("c360d6a307909c6e571b29d4a329fd786c5d4543", commitMessage);
        var conventionalCommit = ConventionalCommitParser.Parse(testCommit);

        conventionalCommit.Notes.ShouldHaveSingleItem();
        conventionalCommit.Notes[0].Title.ShouldBe("BREAKING CHANGE");
        conventionalCommit.Notes[0].Text.ShouldBe(string.Empty);
    }
コード例 #8
0
    public void ShouldParseTypeScopeAndSubjectFromSingleLineCommitMessage()
    {
        var testCommit         = new TestCommit("c360d6a307909c6e571b29d4a329fd786c5d4543", "feat(scope): broadcast $destroy event on scope destruction");
        var conventionalCommit = ConventionalCommitParser.Parse(testCommit);

        Assert.Equal("feat", conventionalCommit.Type);
        Assert.Equal("scope", conventionalCommit.Scope);
        Assert.Equal("broadcast $destroy event on scope destruction", conventionalCommit.Subject);
    }
コード例 #9
0
        public void ShouldUseFullHeaderAsSubjectIfNoTypeWasGivenButSubjectUsesColon()
        {
            var parser = new ConventionalCommitParser();

            var testCommit         = new TestCommit("broadcast $destroy event: on scope destruction");
            var conventionalCommit = parser.Parse(testCommit);

            Assert.Equal(testCommit.Message, conventionalCommit.Subject);
        }
コード例 #10
0
        public void ShouldGenerateAChangelogForFixFeatAndBreakingCommits()
        {
            ConventionalCommitParser parser = new ConventionalCommitParser();

            var changelog = Changelog.Discover(_testDirectory);

            changelog.Write(new Version(1, 1, 0), new DateTimeOffset(), new List <ConventionalCommit> {
                parser.Parse(new TestCommit("fix: a fix")),
                parser.Parse(new TestCommit("feat: a feature")),
                parser.Parse(new TestCommit("feat: a breaking change feature\nBREAKING CHANGE: this will break everything")),
            });

            var wasChangelogWritten = File.Exists(Path.Join(_testDirectory, "CHANGELOG.md"));

            Assert.True(wasChangelogWritten);

            // TODO: Assert changelog entries
        }
コード例 #11
0
        public void ShouldAppendToExistingChangelog()
        {
            File.WriteAllText(Path.Combine(_testDirectory, "CHANGELOG.md"), "# Some preamble\n\n##SomeVersion");

            ConventionalCommitParser parser = new ConventionalCommitParser();

            var changelog = Changelog.Discover(_testDirectory);

            changelog.Write(new Version(1, 1, 0), new DateTimeOffset(), new List <ConventionalCommit> {
                parser.Parse(new TestCommit("fix: a fix")),
                parser.Parse(new TestCommit("feat: a feature")),
                parser.Parse(new TestCommit("feat: a breaking change feature\nBREAKING CHANGE: this will break everything")),
            });

            var wasChangelogWritten = File.Exists(Path.Join(_testDirectory, "CHANGELOG.md"));

            Assert.True(wasChangelogWritten);

            // TODO: Assert changelog entries
        }
コード例 #12
0
        public void ShouldParseTypeScopeAndSubjectFromSingleLineCommitMessageIfSubjectUsesColon()
        {
            var parser = new ConventionalCommitParser();

            var testCommit         = new TestCommit("feat(scope): broadcast $destroy: event on scope destruction");
            var conventionalCommit = parser.Parse(testCommit);

            Assert.Equal("feat", conventionalCommit.Type);
            Assert.Equal("scope", conventionalCommit.Scope);
            Assert.Equal("broadcast $destroy: event on scope destruction", conventionalCommit.Subject);
        }
コード例 #13
0
    public void ShouldExtractCommitNotes()
    {
        var testCommit         = new TestCommit("c360d6a307909c6e571b29d4a329fd786c5d4543", "feat(scope): broadcast $destroy: event on scope destruction\nBREAKING CHANGE: this will break rc1 compatibility");
        var conventionalCommit = ConventionalCommitParser.Parse(testCommit);

        Assert.Single(conventionalCommit.Notes);

        var breakingChangeNote = conventionalCommit.Notes.Single();

        Assert.Equal("BREAKING CHANGE", breakingChangeNote.Title);
        Assert.Equal("this will break rc1 compatibility", breakingChangeNote.Text);
    }
コード例 #14
0
        public void ShouldBuildGithubSSHCommitLinks()
        {
            var linkBuilder = new GithubLinkBuilder("[email protected]:organization/repository.git");
            var changelog   = Changelog.Discover(_testDirectory);

            changelog.Write(new Version(1, 0, 0), new DateTimeOffset(), linkBuilder, new List <ConventionalCommit>
            {
                ConventionalCommitParser.Parse(new TestCommit("a360d6a307909c6e571b29d4a329fd786c5d4543", "fix: a fix in version 1.0.0")),
            });

            var changelogContents = File.ReadAllText(changelog.FilePath);

            changelogContents.ShouldContain("* a fix in version 1.0.0 ([a360d6a](https://www.github.com/organization/repository/commit/a360d6a307909c6e571b29d4a329fd786c5d4543))");
        }
コード例 #15
0
        public void ShouldAppendToExistingChangelog()
        {
            var parser = new ConventionalCommitParser();

            var changelog = Changelog.Discover(_testDirectory);

            changelog.Write(new Version(1, 0, 0), new DateTimeOffset(), new List <ConventionalCommit>
            {
                parser.Parse(new TestCommit("fix: a fix in version 1.0.0")),
            });

            changelog.Write(new Version(1, 1, 0), new DateTimeOffset(), new List <ConventionalCommit>
            {
                parser.Parse(new TestCommit("fix: a fix in version 1.1.0")),
            });

            var changelogContents = File.ReadAllText(changelog.FilePath);

            changelogContents.ShouldContain("<a name=\"1.0.0\"></a>");
            changelogContents.ShouldContain("a fix in version 1.0.0");

            changelogContents.ShouldContain("<a name=\"1.1.0\"></a>");
            changelogContents.ShouldContain("a fix in version 1.1.0");
        }
コード例 #16
0
        public void ConventionalCommitParser_Parse_FailsToParseSingleLineCommit()
        {
            // given commit message does not meet conventional commit standards
            var commit = new Mock <Commit>();

            commit.Setup(x => x.Message).Returns("This is a failure test.");

            // when we parse commit message
            var parser = new ConventionalCommitParser();
            var conventionalCommits = parser.Parse(new[] { commit.Object });
            ConventionalCommit conventionalCommit = conventionalCommits.FirstOrDefault();

            // then we return a null value
            Assert.Null(conventionalCommit);
        }
コード例 #17
0
        public void ShouldIncludeAllCommitsInChangelogWhenGiven()
        {
            var plainLinkBuilder = new PlainLinkBuilder();
            var changelog        = Changelog.Discover(_testDirectory);

            changelog.Write(new Version(1, 1, 0), new DateTimeOffset(), plainLinkBuilder, new List <ConventionalCommit>
            {
                ConventionalCommitParser.Parse(new TestCommit("a360d6a307909c6e571b29d4a329fd786c5d4543", "chore: nothing important")),
                ConventionalCommitParser.Parse(new TestCommit("b360d6a307909c6e571b29d4a329fd786c5d4543", "chore: some foo bar")),
            }, true);

            var changelogContents = File.ReadAllText(changelog.FilePath);

            changelogContents.ShouldContain("nothing important");
            changelogContents.ShouldContain("some foo bar");
        }
コード例 #18
0
        public void ShouldAppendAtEndIfChangelogContainsExtraInformation()
        {
            File.WriteAllText(Path.Combine(_testDirectory, "CHANGELOG.md"), "# Should be kept by versionize\n\nSome information about the changelog");

            var plainLinkBuilder = new PlainLinkBuilder();
            var changelog        = Changelog.Discover(_testDirectory);

            changelog.Write(new Version(1, 0, 0), new DateTimeOffset(), plainLinkBuilder, new List <ConventionalCommit>
            {
                ConventionalCommitParser.Parse(new TestCommit("a360d6a307909c6e571b29d4a329fd786c5d4543", "fix: a fix in version 1.0.0")),
            });

            var changelogContents = File.ReadAllText(changelog.FilePath);

            changelogContents.ShouldBe("# Should be kept by versionize\n\nSome information about the changelog\n\n<a name=\"1.0.0\"></a>\n## 1.0.0 (1-1-1)\n\n### Bug Fixes\n\n* a fix in version 1.0.0\n\n");
        }
コード例 #19
0
    public void ShouldGenerateWithoutLiteralLineBreakCharacters()
    {
        var plainLinkBuilder = new PlainLinkBuilder();
        var changelog        = ChangelogBuilder.CreateForPath(_testDirectory);

        changelog.Write(
            new Version(1, 1, 0),
            new DateTimeOffset(),
            plainLinkBuilder,
            new List <ConventionalCommit>
        {
            ConventionalCommitParser.Parse(new TestCommit("a360d6a307909c6e571b29d4a329fd786c5d4543", "fix: a fix")),
        },
            ChangelogOptions.Default);

        var contents = File.ReadAllText(Path.Join(_testDirectory, "CHANGELOG.md"));

        contents.ShouldNotContain("\\n");
    }
コード例 #20
0
        public void ShouldGenerateAChangelogForFixFeatAndBreakingCommits()
        {
            var plainLinkBuilder = new PlainLinkBuilder();
            var changelog        = Changelog.Discover(_testDirectory);

            changelog.Write(new Version(1, 1, 0), new DateTimeOffset(), plainLinkBuilder, new List <ConventionalCommit>
            {
                ConventionalCommitParser.Parse(new TestCommit("a360d6a307909c6e571b29d4a329fd786c5d4543", "fix: a fix")),
                ConventionalCommitParser.Parse(new TestCommit("b360d6a307909c6e571b29d4a329fd786c5d4543", "feat: a feature")),
                ConventionalCommitParser.Parse(
                    new TestCommit("c360d6a307909c6e571b29d4a329fd786c5d4543", "feat: a breaking change feature\nBREAKING CHANGE: this will break everything")),
            });

            var wasChangelogWritten = File.Exists(Path.Join(_testDirectory, "CHANGELOG.md"));

            Assert.True(wasChangelogWritten);

            // TODO: Assert changelog entries
        }
コード例 #21
0
    public SemanticVersion Versionize(VersionizeOptions options)
    {
        var workingDirectory = _directory.FullName;

        using var repo = new Repository(workingDirectory);

        var isDirty = repo.RetrieveStatus(new StatusOptions()).IsDirty;

        if (!options.SkipDirty && isDirty)
        {
            Exit($"Repository {workingDirectory} is dirty. Please commit your changes.", 1);
        }

        var projects = Projects.Discover(workingDirectory);

        if (projects.IsEmpty())
        {
            Exit($"Could not find any projects files in {workingDirectory} that have a <Version> defined in their csproj file.", 1);
        }

        if (projects.HasInconsistentVersioning())
        {
            Exit($"Some projects in {workingDirectory} have an inconsistent <Version> defined in their csproj file. Please update all versions to be consistent or remove the <Version> elements from projects that should not be versioned", 1);
        }

        Information($"Discovered {projects.GetProjectFiles().Count()} versionable projects");
        foreach (var project in projects.GetProjectFiles())
        {
            Information($"  * {project}");
        }

        var versionTag       = repo.SelectVersionTag(projects.Version);
        var isInitialRelease = versionTag == null;
        var commitsInVersion = repo.GetCommitsSinceLastVersion(versionTag);

        var conventionalCommits = ConventionalCommitParser.Parse(commitsInVersion);

        var versionIncrement = new VersionIncrementStrategy(conventionalCommits);

        var nextVersion = isInitialRelease ? projects.Version : versionIncrement.NextVersion(projects.Version, options.Prerelease);

        // For non initial releases: for insignificant commits such as chore increment the patch version if IgnoreInsignificantCommits is not set
        if (!isInitialRelease && nextVersion == projects.Version)
        {
            if (options.IgnoreInsignificantCommits || options.ExitInsignificantCommits)
            {
                var exitCode = options.ExitInsignificantCommits ? 1 : 0;
                Exit($"Version was not affected by commits since last release ({projects.Version})", exitCode);
            }
            else
            {
                nextVersion = nextVersion.IncrementPatchVersion();
            }
        }

        if (!string.IsNullOrWhiteSpace(options.ReleaseAs))
        {
            try
            {
                nextVersion = SemanticVersion.Parse(options.ReleaseAs);
            }
            catch (Exception)
            {
                Exit($"Could not parse the specified release version {options.ReleaseAs} as valid version", 1);
            }
        }

        if (nextVersion < projects.Version)
        {
            Exit($"Semantic versioning conflict: the next version {nextVersion} would be lower than the current version {projects.Version}. This can be caused by using a wrong pre-release label or release as version", 1);
        }

        if (!options.DryRun && !options.SkipCommit && repo.VersionTagsExists(nextVersion))
        {
            Exit($"Version {nextVersion} already exists. Please use a different version.", 1);
        }

        var versionTime = DateTimeOffset.Now;

        // Commit changelog and version source
        if (!options.DryRun && (nextVersion != projects.Version))
        {
            projects.WriteVersion(nextVersion);

            foreach (var projectFile in projects.GetProjectFiles())
            {
                Commands.Stage(repo, projectFile);
            }
        }

        Step($"bumping version from {projects.Version} to {nextVersion} in projects");

        var changelog            = ChangelogBuilder.CreateForPath(workingDirectory);
        var changelogLinkBuilder = LinkBuilderFactory.CreateFor(repo);

        if (options.DryRun)
        {
            string markdown = ChangelogBuilder.GenerateMarkdown(nextVersion, versionTime, changelogLinkBuilder, conventionalCommits, options.Changelog);
            DryRun(markdown.TrimEnd('\n'));
        }
        else
        {
            changelog.Write(nextVersion, versionTime, changelogLinkBuilder, conventionalCommits, options.Changelog);
        }

        Step("updated CHANGELOG.md");

        if (!options.DryRun && !options.SkipCommit)
        {
            if (!repo.IsConfiguredForCommits())
            {
                Exit(@"Warning: Git configuration is missing. Please configure git before running versionize:
git config --global user.name ""John Doe""
$ git config --global user.email [email protected]", 1);
            }

            Commands.Stage(repo, changelog.FilePath);

            foreach (var projectFile in projects.GetProjectFiles())
            {
                Commands.Stage(repo, projectFile);
            }

            var author    = repo.Config.BuildSignature(versionTime);
            var committer = author;

            var releaseCommitMessage = $"chore(release): {nextVersion} {options.CommitSuffix}".TrimEnd();
            var versionCommit        = repo.Commit(releaseCommitMessage, author, committer);
            Step("committed changes in projects and CHANGELOG.md");

            repo.Tags.Add($"v{nextVersion}", versionCommit, author, $"{nextVersion}");
            Step($"tagged release as {nextVersion}");

            Information("");
            Information("i Run `git push --follow-tags origin master` to push all changes including tags");
        }
        else if (options.SkipCommit)
        {
            Information("");
            Information($"i Commit and tagging of release was skipped. Tag this release as `v{nextVersion}` to make versionize detect the release");
        }

        return(nextVersion);
    }
        public async Task StartAsync(CancellationToken cancellationToken)
        {
            var repositoryPath = Directory.GetCurrentDirectory();
            var repository     = new Repository(repositoryPath);
            var gitRepository  = new GitRepository(repository);

            IEnumerable <Commit> commits = new List <Commit>();

            if (string.IsNullOrEmpty(_options.EndTag))
            {
                commits = await gitRepository.GetLatestCommitsFromTagAsync(
                    _options.StartTag,
                    cancellationToken);
            }
            else
            {
                commits = await gitRepository.GetLatestCommitsBetweenTagsAsync(
                    _options.StartTag,
                    _options.EndTag,
                    cancellationToken);
            }

            var parser = new ConventionalCommitParser();
            var conventionalCommits = parser.Parse(commits);

            IDictionary <string, IDictionary <string, ICollection <string> > > changelogMessages = new Dictionary <string, IDictionary <string, ICollection <string> > >();

            foreach (var conventionalCommit in conventionalCommits)
            {
                if (conventionalCommit is null)
                {
                    continue;
                }

                AppendChangelogMessage(conventionalCommit, changelogMessages);

                foreach (var conventionalCommitTrailer in conventionalCommit.Trailers)
                {
                    AppendChangelogMessage(conventionalCommitTrailer, changelogMessages);
                }
            }

            StringBuilder builder = new StringBuilder();

            _ = builder.AppendLine($"# {_options.Version} - {DateTimeOffset.UtcNow:yyyy/MM/dd}");
            foreach (var keyPair in changelogMessages.OrderBy(x => x.Key))
            {
                _ = builder.AppendLine($"## **{keyPair.Key.Humanize().Transform(To.SentenceCase).Pluralize()}**");
                foreach (var pair in keyPair.Value.OrderBy(x => x.Key))
                {
                    _ = builder.AppendLine($"* **{pair.Key.Replace("_", "").Humanize().Transform(To.SentenceCase)}**");
                    foreach (var message in pair.Value)
                    {
                        _ = builder.AppendLine($"  * {message.Humanize().Transform(To.SentenceCase)}");
                    }
                    _ = builder.AppendLine();
                }

                _ = builder.AppendLine();
            }

            using var readFileStream = File.Open(_options.OutputPath, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.ReadWrite);
            using var streamReader   = new StreamReader(readFileStream);

            _ = builder.AppendLine(streamReader.ReadToEnd());

            streamReader.Close();

            using var writeFileStream = File.Open(_options.OutputPath, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.ReadWrite);
            using var streamWriter    = new StreamWriter(writeFileStream);

            _ = writeFileStream.Seek(0, SeekOrigin.Begin);

            await streamWriter.WriteAsync(builder, cancellationToken);

            streamWriter.Close();

            _applicationLifetime.StopApplication();
        }