/spotless-changelog

The changelog is cast, let the versions fall where they may.

Primary LanguageJavaApache License 2.0Apache-2.0

Spotless Changelog

The changelog is cast, let the versions fall where they may.

Gradle plugin Maven central Apache 2.0

Changelog Javadoc Live chat CircleCI

Spotless Changelog checks that your changelog complies with the format of keepachangelog, and uses the information from only the changelog file to compute the version of the next release. If you want, you can use the computed version to update the changelog file, then automatically commit, tag, and push when a release is published.

There are many plugins that compute your next version using a variety of configurable rules based on git tags, commit message standards, class file comparison, and other methods. They tend to require a lot of documentation.

If your changelog doesn't already have information about breaking changes and new features, then you should fix that first, whether you end up adopting this plugin or not! But once your changelog has that information, why not make things simple and use it as the source-of-truth? If you want, there are also plugins which can generate your changelog automatically from your git commits or github issues (although we tend to think it's better to be a bit more human about it).

Currently Spotless Changelog only has a gradle plugin, but the logic lives in a separate library, and we welcome contributions for other build systems, just as happened with the Spotless code formatter.

Keep your changelog clean

You have to start out with a changelog in the keepachangelog format. Here is a concise example:

## [Unreleased]
### Added
- A great feature

## [1.0.0] - 2019-12-02
Initial release.

In your build.gradle, you do this:

plugins {
  id 'com.diffplug.spotless-changelog' version '3.1.2'
}

spotlessChangelog { // only necessary if you need to change the defaults below
  changelogFile 'CHANGELOG.md'
  enforceCheck true
}

The changelogCheck task will now run every time gradlew check runs, and it will verify that the changelog versions and dates conform to the format.

It does not enforce the entire keepachangelog format, only the ## [x.y.z] yyyy-mm-dd lines. We're happy to take a PR to support a stricter format, but it will be optional.

Legacy changelogs

If you have a big long legacy changelog that's not in the keepachangelog format, good job! Changelogs are good!

You don't have to convert the whole thing. Just stick <!-- END CHANGELOG --> on its own line into your changelog, and it will stop parsing as soon as this line is encountered. The goal isn't perfection, just better.

Compute the next version

By default, Spotless Changelog uses the names breaking.added.fixed for x.y.z. When computing the next version, Spotless Changelog always starts with the most recent version from your changelog. From there, the only decision we have to make is which position to bump: breaking, added, or fixed. By default, Spotless Changelog will bump fixed. All you need to do is set the rules for escalating to an added or breaking bump:

spotlessChangelog {  // defaults
  // breaking.added.fixed
  ifFoundBumpBreaking '**BREAKING**'
  ifFoundBumpAdded    '### Added'
}

If you're curious what the next version will be:

cmd> gradlew changelogPrint
myproj 1.0.4 -> 1.1.0

You can use these versions in your buildscript as spotlessChangelog.versionNext and spotlessChangelog.versionLast:

project.version = project.spotlessChangelog.versionNext
dependencies {
  implementation "group:artifact:${project(':dep-project-with-own-changelog').spotlessChangelog.versionLast}"
}

We also support other version schemas, like 2.0 instead of 2.0.0, or brand.major.minor.patch, or Ryyyy.SRx, or any custom function you want. See ALTERNATE_VERSION_SCHEMAS.md for details.

Pre-1.0, 0.x, and other forms of perfomative insecurity

The terrible thing about 0.x is that the more unstable a codebase is, the more valuable (concise compatibility guarantee).(new feature advertisement).(lowest downside risk to upgrade) would be!

But habits are what they are, and you're going to keep publishing things with 0.x. I will judge you for that, but Spotless Changelog won't. It will just increment the added version (0.1.0, 0.2.0, 0.3.0, etc) whether your changelog has **BREAKING** or just ### Added. See how the information is getting lost? If you really want to stick with 0.x, you can convey a lot more information with 0.breaking.added.fixed.

Alphas, betas, release-candidates, etc.

If you want, you can set forceNextVersion '3.0.0.BETA7-RC1-FINAL'. It will still check that your changelog is formatted, but it will short-circuit the next version calculation. This is also how to go from 0.x to 1.0.

-SNAPSHOT

Integration testing is important, and -SNAPSHOT is an easy way to do it. JitPack is an even better way to do it, because using the git SHA as your version number solves the ambiguity and repeatability problems of -SNAPSHOT.

That said,-SNAPSHOT is handy and widely used. One way to get it is forceNextVersion '1.0.0-SNAPSHOT'. Another way is to set appendDashSnapshotUnless_dashPrelease=true. This mode acts like a gun safety where all versions are nerfed to -SNAPSHOT, until you allow a release by adding -Prelease=true to the gradle command line.

Update the changelog, commit, push

gradlew changelogBump will turn [Unreleased] into [1.2.3] 2011-11-11 (or whatever) and add a new [Unreleased] section in your working copy file. gradlew changelogPush will commit, tag, and push that change.

spotlessChangelog {  // defaults
  tagPrefix 'release/'
  commitMessage 'Published release/{{version}}' // {{version}} will be replaced
  remote 'origin'
  branch 'main'
}

We recommend that you use changelogPush as your deploy task, and wire your tasks so that it will only happen if the publish was successful. Here is a battle-tested wiring plan which can even update your README.md with the latest version number. A simpler scheme is presented below:

Multiple changelogs per project

That's fine too! If you apply com.diffplug.spotless-changelog to multiple projects, just be sure to set the tagPrefix and commitMessage properties so that you have unique tags and commit messages for each project. You also might want to use the snippet below in your root project to ensure that you only release one of the changelogs at a time.

gradle.taskGraph.whenReady { taskGraph ->
  def changelogPushTasks = taskGraph.allTasks.stream()
    .filter { t -> t.name == 'changelogPush' }
    .map { t -> t.path }
    .toList()
  if (changelogPushTasks.size() > 1) {
    throw new IllegalArgumentException("Run changelogPush one at a time:\n" + changelogPushTasks.join('\n'))
  }
}

Reference

Plugin DSL (javadoc)

spotlessChangelog { // all defaults
  // keep changelog formatted
  changelogFile 'CHANGELOG.md'
  enforceCheck true
  // calculate next version (breaking.added.fixed)
  ifFoundBumpBreaking ['**BREAKING**']
  ifFoundBumpAdded    ['### Added']
  forceNextVersion null
  // rare to change this, see ALTERNATE_VERSION_SCHEMAS.md
  versionSchema Semver.class
  // default value is false, but if you set it to true, then it will
  // append -SNAPSHOT to nextVersion unless you add -Prelease=true to the gradle command line
  appendDashSnapshotUnless_dashPrelease=false
  // tag and push
  tagPrefix 'release/'
  commitMessage 'Published release/{{version}}' // {{version}} will be replaced
  tagMessage null // default is null (creates lightweight tag); {{changes}} and {{version}} will be replaced
  runAfterPush null // runs a CLI command after the push; {{changes}} and {{version}} will be replaced
  remote 'origin'
  branch 'main'
  // default value is `yes`, but if you set it to `no`, then it will
  // disable ssh host key checking (.ssh/known_hosts).
  sshStrictHostKeyChecking "yes" // can override with `-PsshStrictHostKeyChecking=no`
}

// last version parsed from changelog
String versionLast = spotlessChangelog.versionLast
// calculated next version
String versionNext = spotlessChangelog.versionNext
// NOTE: Once you call either of these, you can't modify the spotlessChangelog configuration.
//       Don't worry about doing this accidentally, you'll get a loud error if you do.

Tasks (code)

  • changelogPrint - prints the last published version and calculated next version
    • myproj 1.0.4 -> 1.1.0
  • changelogCheck - throws an error if the changelog is not formatted according to your rules
    • if enforceCheck true (default) then check.dependsOn changelogCheck
  • changelogBump - updates the changelog on disk with the next version and the current UTC date
    • applying changelogBump multiple times in a row is fine, an empty section under [Unreleased] is enough to know that it has already been applied.
  • changelogPush - commits the changelog, tags, and pushes
    • changelogPush depends on changelogBump depends on changelogCheck
    • If changelogPush is in the task graph, then changelogCheck will do an extra check to make sure that the git push will succeed. The changelogBump section above shows how you wire changelogCheck into your jar task so that your build will fail early if you haven't correctly setup the git credentials.

Requirements

Java 8+ and Gradle 6.2+. (Spotless Changelog 2.2.0 can support Gradle 5.2+)

Acknowledgments