Spring From the Trenches: Returning Git Commit Information as JSON

There are situations when we must know the exact version of our web application that is deployed to a remote server. For example, a customer might want to know if we have already deployed a bug fix to the server X.

We can, of course, try to find an answer to that question by using the "traditional" method. The problem is that:

  • No one cannot remember who updated the server X or when it was updated.
  • The person who updated it cannot remember which was the last commit that was included in the build.

In other words, we are screwed. We can try to test if the bug is still present at the server X, but this doesn’t really help us because our bug fix might not work.

This blog post describes how we can solve this problem. Let’s start by extracting the build-time state of our Git repository.

If you use Spring Boot, you should use the Spring Boot Actuator. It helps you to publish information about the state of your Git repository.

If you have not read the following blog posts, you should read them before you continue reading this blog post:

Extracting the Build-Time State of Our Git Repository

We can extract the build-time state of our Git repository by using the Maven Git Commit Id plugin. Let's find out how we can configure the Maven Git Commit Id plugin and add the extracted information to a properties file.

First, we need to configure the location of our resource directory and ensure that the property placeholders found from our properties files are replaced with the actual property values. We can do this by adding the following XML to the build section of our pom.xml file:

<resources>
	<resource>
		<directory>src/main/resources</directory>
		<filtering>true</filtering>
		<includes>
			<include>**/*.properties</include>
		</includes>
	</resource>
</resources>
Second, we need to configure the Maven Git Commit Id plugin. We can do this by following these steps:

  1. Add the Maven Git Commit Id plugin to our build.
  2. Ensure that the revision goal of the Maven Git Commit Id plugin is invoked at the initialize phase of the default lifecycle.
  3. Configure the location of the .git directory.

We need to add the following XML to the plugins section of the pom.xml file:

<plugin>
	<groupId>pl.project13.maven</groupId>
	<artifactId>git-commit-id-plugin</artifactId>
	<version>2.1.13</version>
	<!--
		 Ensure that the revision goal is invoked during the initialize
		 phase.
	-->
	<executions>
		<execution>
			<goals>
				<goal>revision</goal>
			</goals>
		</execution>
	</executions>
	<configuration>
		<!--
			Configure the location of the .git directory.
		-->
		<dotGitDirectory>${project.basedir}/../.git</dotGitDirectory>
	</configuration>
</plugin>
If you are not happy with the default configuration of the Maven Git Commit Id plugin, you should take a closer look at its README:

  • Using the plugin provides a commented XML configuration file that describes the configuration of the Maven Git Commit Id plugin.
  • Configuration options in depth describes every configuration option of the Maven Git Commit Id plugin.

Third, we need to create the properties file that contains the information which is extracted from our Git repository. The application.properties file looks as follows:

git.tags=${git.tags}
git.branch=${git.branch}
git.dirty=${git.dirty}
git.remote.origin.url=${git.remote.origin.url}

git.commit.id=${git.commit.id}
git.commit.id.abbrev=${git.commit.id.abbrev}
git.commit.id.describe=${git.commit.id.describe}
git.commit.id.describe-short=${git.commit.id.describe-short}
git.commit.user.name=${git.commit.user.name}
git.commit.user.email=${git.commit.user.email}
git.commit.message.full=${git.commit.message.full}
git.commit.message.short=${git.commit.message.short}
git.commit.time=${git.commit.time}

git.build.user.name=${git.build.user.name}
git.build.user.email=${git.build.user.email}
git.build.time=${git.build.time}

We have now configured the Maven Git Commit Id plugin. When we compile our project, the property placeholders found from the application.properties file are replaced with the actual property values that are extracted from our Git repository.

The application.properties file found from the target/classes directory looks as follows:

git.tags=
git.branch=master
git.dirty=true
git.remote.origin.url=git@github.com:pkainulainen/spring-from-the-trenches.git

git.commit.id=1bdfe9cf22b550a3ebe170f60df165e5c26448f9
git.commit.id.abbrev=1bdfe9c
git.commit.id.describe=1bdfe9c-dirty
git.commit.id.describe-short=1bdfe9c-dirty
git.commit.user.name=Petri Kainulainen
git.commit.user.email=petri.kainulainen@gmail.com
git.commit.message.full=Declare PropertySourcesPlaceholderConfigurer in a static @Bean method
git.commit.message.short=Declare PropertySourcesPlaceholderConfigurer in a static @Bean method
git.commit.time=16.04.2015 @ 23:35:23 EEST

git.build.user.name=Petri Kainulainen
git.build.user.email=petri.kainulainen@gmail.com
git.build.time=18.04.2015 @ 17:07:55 EEST
If you don’t want to create the properties file, the Maven Git Commit Id plugin can generate one for you.

Let's move on and find out how we can inject the Git commit information into properties beans.

Injecting the Git Commit Information Into Properties Beans

We need to create three properties bean classes that are described in the following:

  • The BuildProperties class contains information about the person who started the build.
  • The CommitProperties class contains information about the latest commit that is included in the build.
  • The GitProperties class contains a few "common" properties such as branch, tags, and remoteOriginUrl. It also contains a references to BuildProperties and CommitProperties objects.
Properties beans are similar than the configuration beans that were described in my earlier blog post. The reason why I used a different suffix for these classes is that they aren't part of the configuration of our web application. They simply contain information that is written to a log file and returned as JSON.

Also, if you don’t know why you should inject property values into special bean classes and how you can do it, you should read my blog post that answers to both questions.

First, we need to create the BuildProperties class. This class has the final time, userEmail, and userName fields. The actual field values are injected into these fields by using constructor injection. The source code of the BuildProperties class looks as follows:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

@Component
public class BuildProperties {

    private final String time;
    private final String userEmail;
    private final String userName;

    @Autowired
    public BuildProperties(@Value("${git.build.time}") String time,
                           @Value("${git.build.user.email}") String userEmail,
                           @Value("${git.build.user.name}") String userName) {
        this.time = time;
        this.userEmail = userEmail;
        this.userName = userName;
    }
	
    //Getters are omitted for the sake of clarity
}

Second, we need to create the CommitProperties class. This class has the final describe, describeShort, fullMessage, id, idAbbrev, shortMessage, time, userEmail, and userName fields. The actual property values are injected into these fields by using constructor injection. The source code of the CommitProperties class looks as follows:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

@Component
public class CommitProperties {

    private final String describe;
    private final String describeShort;
    private final String fullMessage;
    private final String id;
    private final String idAbbrev;
    private final String shortMessage;
    private final String time;
    private final String userEmail;
    private final String userName;

    @Autowired
    public CommitProperties(@Value("${git.commit.id.describe}") String describe,
                            @Value("${git.commit.id.describe-short}") String describeShort,
                            @Value("${git.commit.message.full}") String fullMessage,
                            @Value("${git.commit.id}") String id,
                            @Value("${git.commit.id.abbrev}") String idAbbrev,
                            @Value("${git.commit.message.short}") String shortMessage,
                            @Value("${git.commit.time}") String time,
                            @Value("${git.commit.user.email}") String userEmail,
                            @Value("${git.commit.user.name}") String userName) {
        this.describe = describe;
        this.describeShort = describeShort;
        this.fullMessage = fullMessage;
        this.id = id;
        this.idAbbrev = idAbbrev;
        this.shortMessage = shortMessage;
        this.time = time;
        this.userEmail = userEmail;
        this.userName = userName;
    }

    //Getters are omitted for the sake of clarity
}

Third, we need to create the GitProperties class. This class has the final branch, build, commit, dirty, remoteOriginUrl, and tags fields. The actual field values (or objects) are injected into these fields by using constructor injection. The source code of the GitProperties class looks as follows:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

@Component
public class GitProperties {

    private String branch;
    private final BuildProperties build;
    private final CommitProperties commit;
    private final boolean dirty;
    private final String remoteOriginUrl;
    private final String tags;

    @Autowired
    public GitProperties(@Value("${git.branch}") String branch,
                         BuildProperties build,
                         CommitProperties commit,
                         @Value("${git.dirty}") boolean dirty,
                         @Value("${git.remote.origin.url}") String remoteOriginUrl,
                         @Value("${git.tags}") String tags) {
        this.branch = branch;
        this.build = build;
        this.commit = commit;
        this.dirty = dirty;
        this.remoteOriginUrl = remoteOriginUrl;
        this.tags = tags;
    }

    //Getters are omitted for the sake of clarity
}

Let’s move on and write the Git commit information to a log file.

Writing the Git Commit Information to a Log File

Our next step is to write the Git commit information information to a log file. Let's find out how we can do that.

First, we have to add toString() methods to BuildProperties, CommitProperties, and GitProperties classes and implement these methods by using the ToStringBuilder class.

The source code of the BuildProperties class looks as follows:

import org.apache.commons.lang3.builder.ToStringBuilder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

@Component
public class BuildProperties {

    private final String time;
    private final String userEmail;
    private final String userName;

    @Autowired
    public BuildProperties(@Value("${git.build.time}") String time,
                           @Value("${git.build.user.email}") String userEmail,
                           @Value("${git.build.user.name}") String userName) {
        this.time = time;
        this.userEmail = userEmail;
        this.userName = userName;
    }

    //Getters are omitted for the sake of clarity

    @Override
    public String toString() {
        return new ToStringBuilder(this)
                .append("time", this.time)
                .append("userEmail", this.userEmail)
                .append("userName", this.userName)
                .toString();
    }
}

The source code of the CommitProperties class looks as follows:

import org.apache.commons.lang3.builder.ToStringBuilder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

@Component
public class CommitProperties {

    private final String describe;
    private final String describeShort;
    private final String fullMessage;
    private final String id;
    private final String idAbbrev;
    private final String shortMessage;
    private final String time;
    private final String userEmail;
    private final String userName;

    @Autowired
    public CommitProperties(@Value("${git.commit.id.describe}") String describe,
                            @Value("${git.commit.id.describe-short}") String describeShort,
                            @Value("${git.commit.message.full}") String fullMessage,
                            @Value("${git.commit.id}") String id,
                            @Value("${git.commit.id.abbrev}") String idAbbrev,
                            @Value("${git.commit.message.short}") String shortMessage,
                            @Value("${git.commit.time}") String time,
                            @Value("${git.commit.user.email}") String userEmail,
                            @Value("${git.commit.user.name}") String userName) {
        this.describe = describe;
        this.describeShort = describeShort;
        this.fullMessage = fullMessage;
        this.id = id;
        this.idAbbrev = idAbbrev;
        this.shortMessage = shortMessage;
        this.time = time;
        this.userEmail = userEmail;
        this.userName = userName;
    }

    //Getters are omitted for the sake of clarity

    @Override
    public String toString() {
        return new ToStringBuilder(this)
                .append("describe", this.describe)
                .append("describeShort", this.describeShort)
                .append("fullMessage", this.fullMessage)
                .append("id", this.id)
                .append("idAbbrev", this.idAbbrev)
                .append("shortMessage", this.shortMessage)
                .append("time", this.time)
                .append("userEmail", this.userEmail)
                .append("userName", this.userName)
                .toString();
    }
}

The source code of the GitProperties class looks as follows:

import org.apache.commons.lang3.builder.ToStringBuilder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

@Component
public class GitProperties {

    private String branch;
    private final BuildProperties build;
    private final CommitProperties commit;
    private final boolean dirty;
    private final String remoteOriginUrl;
    private final String tags;

    @Autowired
    public GitProperties(@Value("${git.branch}") String branch,
                         BuildProperties build,
                         CommitProperties commit,
                         @Value("${git.dirty}") boolean dirty,
                         @Value("${git.remote.origin.url}") String remoteOriginUrl,
                         @Value("${git.tags}") String tags) {
        this.branch = branch;
        this.build = build;
        this.commit = commit;
        this.dirty = dirty;
        this.remoteOriginUrl = remoteOriginUrl;
        this.tags = tags;
    }

    //Getters are omitted for the sake of clarity

    @Override
    public String toString() {
        return new ToStringBuilder(this)
                .append("branch", this.branch)
                .append("build", this.build)
                .append("commit", this.commit)
                .append("dirty", this.dirty)
                .append("remoteOriginUrl", this.remoteOriginUrl)
                .append("tags", this.tags)
                .toString();
    }
}

Second, we have to write the Git commit information to a log file when our application is started. We can do this by following these steps:

  1. Add a static final Logger field to the GitProperties class and create a new Logger object by using the LoggerFactory class.
  2. Add a writeGitCommitInformationToLog() method to the GitProperties class and annotate it with the @PostConstruct annotation. This ensures that the Spring container invokes this method after it has injected the dependencies of the created bean object into it.
  3. Implement the writeGitCommitInformationToLog() method by writing the Git commit information to a log file.

After we have made these changes, the source code of the GitProperties class looks as follows:

import org.apache.commons.lang3.builder.ToStringBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;

@Component
public class GitProperties {

    private static final Logger LOGGER = LoggerFactory.getLogger(GitProperties.class);

    private String branch;

    private final BuildProperties build;

    private final CommitProperties commit;

    private final boolean dirty;

    private final String remoteOriginUrl;

    private final String tags;

    @Autowired
    public GitProperties(@Value("${git.branch}") String branch,
                         BuildProperties build,
                         CommitProperties commit,
                         @Value("${git.dirty}") boolean dirty,
                         @Value("${git.remote.origin.url}") String remoteOriginUrl,
                         @Value("${git.tags}") String tags) {
        this.branch = branch;
        this.build = build;
        this.commit = commit;
        this.dirty = dirty;
        this.remoteOriginUrl = remoteOriginUrl;
        this.tags = tags;
    }

    //Getters are omitted for the sake of clarity

    @Override
    public String toString() {
        return new ToStringBuilder(this)
                .append("branch", this.branch)
                .append("build", this.build)
                .append("commit", this.commit)
                .append("dirty", this.dirty)
                .append("remoteOriginUrl", this.remoteOriginUrl)
                .append("tags", this.tags)
                .toString();
    }

    @PostConstruct
    public void writeGitCommitInformationToLog() {
        LOGGER.info("Application was built by using the Git commit: {}", this);
    }
}

When we start our web application, we should find the following information from its log file:

INFO  - GitProperties - Application was built by using the Git commit: net.petrikainulainen.spring.trenches.config.GitProperties@47044f7d[
	branch=master,
	build=net.petrikainulainen.spring.trenches.config.BuildProperties@7b14c61[
		time=19.04.2015 @ 00:47:37 EEST,
		userEmail=petri.kainulainen@gmail.com,
		userName=Petri Kainulainen
	],
	commit=net.petrikainulainen.spring.trenches.config.CommitProperties@8fcc534[
		describe=1bdfe9c-dirty,
		describeShort=1bdfe9c-dirty,
		fullMessage=Declare PropertySourcesPlaceholderConfigurer in a static @Bean method,
		id=1bdfe9cf22b550a3ebe170f60df165e5c26448f9,
		idAbbrev=1bdfe9c,
		shortMessage=Declare PropertySourcesPlaceholderConfigurer in a static @Bean method,
		time=16.04.2015 @ 23:35:23 EEST,
		userEmail=petri.kainulainen@gmail.com,
		userName=Petri Kainulainen
	],
	dirty=true,
	remoteOriginUrl=git@github.com:pkainulainen/spring-from-the-trenches.git,
	tags=
]
That information is written into a single line, but I formatted it a bit because I wanted to make it easier to read.

Let’s find out how we can return the Git commit information as JSON.

Returning the Git Commit Information as JSON

Earlier we created a controller class that returns the runtime configuration of a web application as JSON. Let's modify this class to return the Git commit information as JSON. We can do this by following these steps:

  1. Add a final GitProperties field to the PropertiesController class.
  2. Inject the GitProperties bean into the created controller bean by using constructor injection.
  3. Create a controller method that processes GET requests send to the url '/version' and implement it by returning the GitProperties object.

The source code of the PropertiesController looks as follows:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

@RestController
final class PropertiesController {

    private final ApplicationProperties applicationProperties;
    private final GitProperties gitProperties;

    @Autowired
    PropertiesController(ApplicationProperties applicationProperties, 
						 GitProperties gitProperties) {
        this.applicationProperties = applicationProperties;
        this.gitProperties = gitProperties;
    }

    @RequestMapping(value = "/config", method = RequestMethod.GET)
    ApplicationProperties getAppConfiguration() {
        return applicationProperties;
    }

    @RequestMapping(value = "/version", method = RequestMethod.GET)
    GitProperties getVersion() {
        return gitProperties;
    }
}

When we send a GET request to the url '/version', our controller method returns the following JSON:

{
	"branch":"master",
	"build":{
		"time":"19.04.2015 @ 00:47:37 EEST",
		"userEmail":"petri.kainulainen@gmail.com",
		"userName":"Petri Kainulainen"
	},
	"commit":{
		"describe":"1bdfe9c-dirty",
		"describeShort":"1bdfe9c-dirty",
		"fullMessage":"Declare PropertySourcesPlaceholderConfigurer in a static @Bean method",
		"id":"1bdfe9cf22b550a3ebe170f60df165e5c26448f9",
		"idAbbrev":"1bdfe9c",
		"shortMessage":"Declare PropertySourcesPlaceholderConfigurer in a static @Bean method",
		"time":"16.04.2015 @ 23:35:23 EEST",
		"userEmail":"petri.kainulainen@gmail.com",
		"userName":"Petri Kainulainen"
	},
	"dirty":true,
	"remoteOriginUrl":"git@github.com:pkainulainen/spring-from-the-trenches.git",
	"tags":""
}
We shouldn’t allow everyone to access the Git commit information of our application. If this would be a real life application, we should ensure that only administrators can access this information.

Let's move on and summarize what we learned from this blog post.

Summary

This blog post has taught us three things:

  • We can extract the build-time state from a Git repository by using the Maven Git Commit Id plugin.
  • We can write the Git commit information to a log file by overriding the toString() methods of the properties bean classes and writing the property values of these beans to a log file after the property values have been injected into them.
  • We can return the Git commit information as JSON by creating a controller method that returns the “root” properties bean object (GitProperties).

P.S. You can get the example application of this blog post from Github.

18 comments… add one
  • Abderrazak Apr 22, 2015 @ 17:40

    Hi Petri

    Thank's a lot for the very useful tip, howerver, I choosed to use

    true
    src/main/resources/git.properties

    but values aren't take in consideration, any clues about that !

    thanks

    • Anonymous Apr 22, 2015 @ 17:42

      the form escaped my XML tags,
      I choosed to use
      generateGitPropertiesFile and generateGitPropertiesFilename properties for the maven plugin to generate into another file (git.properties) how to use values into that file instead of application.properties ?

      Thanks

      • Petri Apr 22, 2015 @ 17:59

        You can configure the used properties file(s) by using the @PropertySource annotation.

        The location of the example application's properties file is configured in the WebAppContext class.

        If you want to use another properties file (git.properties) that is found from the classpath, you have to remove the "old" @PropertySource annotation and replace it with this one:

        @PropertySource("classpath:git.properties")
        


        On the other hand, if you want to use multiple properties files that are found from the classpath, you have to use this annotation:

        @PropertySource({"classpath:file1.properties", "classpath:file2.properties"})
        

        • Abderrazak Apr 22, 2015 @ 18:16

          This is exactly what I did
          I put the @PropertySource("classpath:git.properties") on my SpringBootApplication.java, I also checked that the git.properties is generated and has the correct values, when I call my /version controller, values seem to be not injected into my GitProperties bean, here's the code on github git@github.com:jpuzzler/spring-boot-lab.git

          thanks

          • Abderrazak Apr 22, 2015 @ 18:43

            Resolved, sorry for the disturb, it happend that I wrote @Value("git.branch") instead of @Value("${git.branch}") "Not enough focus"

            Cheers

          • Petri Apr 22, 2015 @ 18:48

            Hi,

            It is good to hear that you were able to solve your problem! By the way, that mistake is pretty devious, and I have to confess that I made the same mistake earlier this week... I guess it happens to all of us.

  • Ramuk Apr 23, 2015 @ 15:49

    A naive question, how do I execute the following goal in a Gradle project? Or to put in other way, how do I get this working in a Gradle Project?

    revision

    • Petri Apr 23, 2015 @ 16:02

      You have to use a Gradle plugin that can extract the built-time state from your Git repository and add the extracted information to a properties file. It seems that the Gradle Snapshot Plugin can do this.

      • Felipe Carvalho Apr 22, 2016 @ 11:18

        If you want to use Gradle, take a look at https://plugins.gradle.org/plugin/com.gorylenko.gradle-git-properties

        It's really easy to use. Just declare it in your build.gradle as described on the plugin page and, if you use Spring Boot, Git info will be automatically available as JSON on /info/ endpoint, like this:

        {"git":{"branch":"HEAD","commit":{"id":"713fda0","time":"1461250928"}}}

        In my case, declaring the plugin in the "old way" did the job (ie, by using "apply plugin" syntax). The new plugin syntax didn't really work for me.

  • Patrick Sep 21, 2015 @ 23:28

    Hi Petri,
    nice post, we do the same in our projects. Instead of writing the git values into a property file, we hide it even more. We write it into the Manifest! So nobody can easily fiddle around in the property file when you build the artifact for the nexus repo.
    Best,
    Patrick

    • Petri Sep 30, 2015 @ 21:07

      Hi Patrick,

      That is a really interesting use case. Maybe you should write a blog post about it?

  • spandana Jul 1, 2017 @ 13:39

    Hi,
    I have Generated git properties using Git-Commit-Id-Plugin,
    file location - \target\classes\git.properties
    Am trying use that git properties in pom.xml but the variables are not being exposed to the pom?

    • Petri Jul 5, 2017 @ 10:42

      Hi,

      Can you add your pom.xml file to pastebin and share the link with me?

  • John Aug 29, 2017 @ 21:51

    I am using Spring Boot 1.5.6 and Git-commit-id-plugin 2.2.2.

    I created the application.properties in the resource folder as you mentioned.

    But when running my Spring Boot appliction, the placeholders in application.properties file located in the target/classes directory won't be replaced with the actual Git property values.

    I ran also tried with the addition of initialize prior to revision.

    But no luck

    • Petri Aug 29, 2017 @ 22:05

      Hi,

      I had a similar problem when I was using Maven properties filtering with Spring Boot. I was able to solve it by replacing the "traditional" placeholder (${foo}) with a placeholder that uses the following syntax: @foo@. Let me know if this did the trick.

      • John Aug 31, 2017 @ 19:09

        Thank you very much for your fast reply ! Your trick worked !

        • Petri Aug 31, 2017 @ 20:10

          You are welcome!

Leave a Reply