Software Development

Creating beautiful release notes with git, gradle and markdown

During the last days I asked myself how to generated releases notes from information that are available in commit  / tag messages from git.

The decisions

My first approach was to create the list of changes directly from the commit messages, but this approach has multiple drawbacks.
 
 
 
 

  • The commit messages must be written very disciplined
  • An marker is required to collect messages that belongs to a release (A usual tag would be sufficient for that)
  • The content of the commit messages must follow conventions, so that featuresbugfixes and changes could be collected and displayed in one block

So I go for annotated tags, which must provide a message with a tag. Creating them is fairly easy with git

git tag -a v.1.0.0 -m "This is an annotated tag"

Sidenote: This kind of tags has a nice addition, it allows to check which commits belongs a specific tag.

The next decision was about the markup language that should be used for the messages. Because I’m a fan of markdown, I decided to go for that. This will lead to human readable messages and a well known and working parsing.

Important hint

Git interprets # usually as the start of a comment. The annotated tags must be created with the option –cleanup=verbatim to suppress this feature if you would like to use more than the first and second header.

With this decisions I started to implement a small example in gradle, which creates the release notes during the build process. I the next section I show you the required code

Implementation

Prerequisites

Because I want to have also a beautiful version of the release notes I decided to convert the markdown directly to a html page. For doing this I use PegDown as Markdown processor and twitter bootstrap with bootswatch themes for styling the output.

Initial build script

buildscript {
    repositories {
        mavenCentral()
        mavenLocal()
    }
    dependencies {
        classpath 'org.pegdown:pegdown:1.4.1'
    }
}
 
import org.pegdown.PegDownProcessor
import groovy.text.SimpleTemplateEngine

 Implementing the logic

We start with the task the controls the process of generating the release notes

 task releaseNotes() {
    def releaseNotes = new File('releaseNotes.md')
    releaseNotes.delete()
    def versions = ""
    def tags = readTags()
    tags.each {tag ->
        versions += "- [$tag](#$tag)\n"
    }
 
    tags.each {tag ->
        releaseNotes << "# ${tag}\n"
        def message = readTagMessage(tag)
        message.each{releaseNotes << "$it\n"}
        releaseNotes << "\n"
    }
 
    def writer = new StringWriter()
    def pdp = new PegDownProcessor()
    def engine = new SimpleTemplateEngine()
    def template = engine.createTemplate(new File("releaseNotes.tpl"))
    def daten = [releaseNotes:pdp.markdownToHtml(new File("releaseNotes.md").text), application: project.name, versions:pdp.markdownToHtml(versions)]
 
    def ergebnis = template.make(daten)
    new File('releaseNotes.html').withWriter { w ->
        w.write(ergebnis)
    }
}

The process is really simple.

  • First we clean up old artifacts of a markdown file (line 2 and 3). After this we created the section Versions and load the available tags (lines 5 till 10) and put everything into a separated list.
  • Then we create information about the Release Notes and add a header for each tag followed by the message belonging to this tag (lines 12 till 18).
  • As a last step we parse the markdown file and create a html page by passing by the parsed values to a template.

As you see three more components are required

  1. the readTags method
  2. the readTagMessage method
  3. a template for the html page

readTags

This method calls:

 git tag -l

and returns a reversed ordered list of tags

def readTags() {
    def tags = []
    def proc = "git tag -l".execute()
    proc.in.eachLine { line -> tags += line}
    tags.sort {}
    Collections.reverse( tags )
    tags
}

 readTagMessage

This method uses

 def readTagMessage(String tag) {
    def message = []
    def proc = "git cat-file tag $tag".execute()
    def startCollection = false
    proc.in.eachLine { line ->
        if (line.isEmpty()) {
            startCollection = true
        }
        if (startCollection) {
            message += line
        }
    }
    proc.err.eachLine { line -> println line }
    message
}

 releaseNotes.tpl

This template contains three properties application, releaseNotes and versions. Application is substituted with your applications name and used in the title. releaseNotes contains the converted release notes. And finally versions represents:

<?xml version="1.0" encoding="iso-8859-1" ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
        "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xml:lang='de' xmlns='http://www.w3.org/1999/xhtml' lang='de'>
    <head>
        <title>Release Notes of $application</title>
        <link rel="stylesheet" href="http://bootswatch.com/yeti/bootstrap.min.css">
        <link rel="stylesheet" href="http://netdna.bootstrapcdn.com/bootstrap/3.1.1/css/bootstrap-theme.min.css">
        <style type="text/css">
        .releasenotes h1 {
          font-size: 1.5em;
        }
        .releasenotes h2 {
          font-size: 1.2em;
        }
      </style>
    </head>
    <body>
      <div class="row">
      <div class="navbar navbar-default">
        <div class="col-md-4"></div>
        <div class="col-md-4">
          <h1>Releasenotes</h1>
        </div>
        <div class="col-md-4"></div>
      </div>
      </div>
        <div class="row releasenotes">
            <div class="col-md-4"></div>
            <div class="col-md-4">$releaseNotes</div>
            <div class="col-md-4"><h1>Versions</h1>$versions</div>
        </div>
        <script src="http://netdna.bootstrapcdn.com/bootstrap/3.1.1/js/bootstrap.min.js"></script>
    </body>
</html>

As already stated in the prerequisites twitter bootstrap and a theme from bootswatch is applied to beautify the code.

Final

After putting everything together we can create a annoated tag and call simply

 gradle releaseNotes

and gradle will read all the messages and tags and create two files

  • releaseNotes.md and
  • releaseNotes.html (Example – Thanks to a colleague of mine for his thoughts on layout and design for this version)

Conclusion

With this simple build script and a little bit conventions we can create release notes from our repository information. Additionally we don’t have to maintain multiple places of release notes, because everything is directly stored in the repository.

By using Markdown we have a simple markup and human readable markup language that allows us to create beautiful release notes for our web applications. It’s possible to parse the markdown information directly to a html page, as I did in this example, but you can also provide the simple markdown file and parse it on the client side or at delivery time on your server.

 

Peter Daum

Peter is senior Java developer in the telecommunication industry. He works in the area of business and operations support systems and is always happy to share his knowledge and experience. He is interested in everything related to Java and software craftsmanship.
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

1 Comment
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
bvn13
7 years ago

PegDown is depricated (look at description: https://github.com/sirthias/pegdown), FlexMark is the choose now.

Back to top button