Skip to content

elab/jgit-buildnumber

 
 

Repository files navigation

JGit Build Number for Maven, Ant, and Gradle

Extracts Git metadata and a freely composable build number in pure Java without Git command-line tool.

Homepage: https://github.com/elab/jgit-buildnumber

Maven Central: search.maven.org - mvnrepository.com - repo1.maven.org

Current version Published Compatibility
2.7.0 2022-06-12 Java 8 .. 17+, Maven 3+, Ant 1+, Gradle 3 .. 7+

Based on the work of Alex Kasko with merged changes from other forks. Thank you guys! Additionally contains many new features, bug fixes, and performance improvements.

🚀 The project enjoys growing popularity. The number of monthly downloads from Maven Central exceeded 10.000 (180 unique IPs) at the end of 2021 according to statistics of Sonatype (which is the hoster of open source projects for Maven Central). Thank you for your trust! ❤️

We believe that JGit Build Number is the best plugin for its purpose, but you can also look at alternatives:

Say what you think, feedback is always welcome :)

Contact: Eugen Labun labun@gmx.net


Contents:


Philosophy

Build number should identify the code state of the project from which it has been created. Particularly, it should not depend on:

  • where the build takes place (locally, build server);
  • how many times the same project state has been build (i.e. no simple increment).

In our case, the default BuildNumber looks like v19.3351.ddda02b and consists of:

  • human readable id: tag name or branch name v19

    git describe --exact-match --tags HEAD # tag name
    git symbolic-ref -q HEAD # branch name
    
  • build incremental id: commits count (closely resembles SVN revision number) 3351

    git rev-list HEAD | wc -l
    
  • globally unique id: commit SHA-1 ddda02b

    git rev-parse HEAD # revision
    git rev-parse --short HEAD # short revision
    
  • dirty flag (optional): inserted if there are differences between working-tree, index, and HEAD; the BuildNumber would look like v19.3351.ddda02b-dirty; you cannot trust the build in this case :)

    git status
    

Instead of the Git CLI commands above, the pure Java JGit API is used. Note: The JGit output (intentionally) doesn't coincide exactly with the output of Git CLI. E.g. branch and tag names are returned without the refs/... prefix.

Extracted properties

Git metadata, build number, and build date (added for convenience) are published as following project properties:

property description
git.revision HEAD SHA-1
git.shortRevision HEAD SHA-1 (abbreviated, see shortRevisionLength)
git.dirty contains dirtyValue if differences exist between working-tree, index, and HEAD; empty string otherwise;
in verbose mode, detailed info will be printed to log about the changes which caused the dirty status (very helpful if the problem occurs on a remote build server)
git.branch branch name; empty string for detached HEAD
(see also
GitLab tips)
git.tag HEAD tag name; empty string if no tags defined; multiple tags separated with ;
git.nearestTag nearest tag name; empty string if no tags found; multiple tags (belonging to the same commit) are separated with ;
Only the "counted" commits are looked for tags, see
countCommitsSince... parameter; see also commitsCountSinceNearestTag property
git.parent SHA-1 of the parent commit (HEAD^); multiple parents separated with ;
git.shortParent SHA-1 of the parent commit (HEAD^) (abbreviated, see shortRevisionLength); multiple parents separated with ;
git.commitsCount commits count; -1 for a Git shallow clone; see countCommitsSince...
git.commitsCountSinceNearestTag commits count since nearestTag; empty string if the nearest tag is not found; the counting is exclusive (i.e. commit with this tag is not counted, to match the logic of describe)
git.authorDate authored date of HEAD commit; see gitDateFormat, dateFormatTimeZone
git.commitDate committed date of HEAD commit; see gitDateFormat, dateFormatTimeZone
git.describe result of JGit describe command (long format, all tags will be considered: annotated and lightweight (not annotated)); abbreviated commit hash if no tags found (see setAlways(true))
git.buildDateMillis start time of plugin execution in milliseconds, as returned by System.currentTimeMillis()
git.buildDate start time of plugin execution, created from buildDateMillis and formatted according to buildDateFormat, dateFormatTimeZone
git.buildNumber composed from other properties according to buildNumberFormat parameter

The default BuildNumber can be easily redefined using extracted properties (see buildNumberFormat).

All properties, including BuildNumber, are available in Maven, Ant, or Gradle build for the entire application.

Note that you can also redefine the default namespace git using namespace parameter.

You can see the extracted properties, the execution time, and other info in the build log. Set the verbose parameter to true to achieve that.

Extracted properties can be accessed in the same way in all build tools: as git.buildNumber or ${git.buildNumber}. See examples in sections for Maven, Ant, Gradle.

Configuration

We follow a zero configuration approach. Therefore all parameters are optional. But just in the case you would like to tweak something, there is a lot of possibilities to do that:

parameter description
namespace Properties are published with this namespace prefix. You may want to redefine the default value:
  • to avoid name clashes with other plugins;
  • to extract properties for multiple Git repos (use multiple plugin/task executions with different namespaces for that).
The value must be a valid
Java name without a dot at the end. Default: "git".
dirtyValue Value for dirty property. Default: "dirty".
shortRevisionLength Length of abbreviated SHA-1 for shortRevision and shortParent properties, min. 0, max. 40. Default: 7.
gitDateFormat Format for Git authorDate and Git commitDate properties (see SimpleDateFormat). The default locale will be used. TimeZone can be specified with dateFormatTimeZone.
Default: "yyyy-MM-dd".
buildDateFormat Format for buildDate property (see SimpleDateFormat). The default locale will be used. TimeZone can be specified with dateFormatTimeZone.
Default: "yyyy-MM-dd HH:mm:ss".
dateFormatTimeZone TimeZone for gitDateFormat and buildDateFormat parameters (see TimeZone#getTimeZone(String)).
Default: current default TimeZone, as returned by TimeZone#getDefault(). (Note that Maven's built-in maven.build.timestamp property cannot use the default time zone and always returns time in UTC.)
countCommitsInPath Relative path to a folder or a file in Git Repo. Only commits which affect the specified path will be counted. The path starts without the leading /; path to a folder may contain an optional trailing /.

The parameter is useful if you want to count commits only for a part of a Git repo. E.g. if your Git Repo contains application code under app/ path and documentation under docs/, you can count commits separately (and have different buildNumbers) for each of those parts. See concrete example in
Ant section.
Default: not set (all commits get counted).

Note: The commit specified with one of countCommitsSince parameters has to be among the commits remaining after applying the countCommitsInPath parameter.
countCommitsSinceInclusive
countCommitsSinceExclusive
Specifies since which ancestor commit (inclusive or exclusive) to count commits. Can be specified as a tag (annotated or lightweight) or SHA-1 (complete or abbreviated).
If such commit is not found, error message is printed and build will fail (since otherwise you would get an unexpected wrong build number). If both, inclusive and exclusive parameters are specified, the "inclusive" version wins.

The parameter is useful if you only want to count commits since start of the current development iteration.
Default: not set (all commits get counted).

Note: Technically, commits are counted backwards from HEAD to parents, through all branches which participated in HEAD state, from child to parent commit, in reverse chronological order of commits in parallel branches according to "committed date" of commits, until the specified ancestor commit is reached (or till root of Git repo). The traverse order should be exactly the same as displayed in "History" view of Eclipse.
buildNumberFormat JavaScript expression to format/compose the buildNumber property. Uses Nashorn JS engine from JDK (if running on Java 8 .. 10) or standalone Nashorn (if running on Java 11+).

All extracted properties are exposed to JavaScript as global String variables (names without "git" namespace). Environment variables are available in the readonly env map (see example of usage in GitLab tips). Note: All variable names are case-sensitive, even on Windows.

JavaScript engine is only initialized if buildNumberFormat is provided.

Example: branch + "." + commitsCount + "/" + commitDate + "/" + shortRevision + (dirty.length > 0 ? "-" + dirty : "");

Default: <tag or branch>.<commitsCount>.<shortRevision>-<dirty>
or, more precisely, equivalent of the following JavaScript (evaluation result of the last line gets returned; real implementation is in Java for performance):
name = (tag.length > 0) ? tag : (branch.length > 0) ? branch : "UNNAMED";
name + "." + commitsCount + "." + shortRevision + (dirty.length > 0 ? "-" + dirty : "");
repositoryDirectory Directory to start searching Git root from, should contain .git directory or be a subdirectory of such directory. Default: project directory (Maven: ${project.basedir}, Ant: ${basedir}, Gradle: projectDir).
runOnlyAtExecutionRoot Setting this parameter to false allows to re-read metadata from Git repo in every submodule of a Maven multi-module project, not only in the root one. Has no effect for Ant or Gradle. Default: true.
skip Setting this parameter to true will skip extraction of Git metadata and creation of buildNumber. Default: false.
verbose Print more information during build (parameters, all extracted properties, changes that caused dirty status, number of tags, execution times, etc). Default: false.

Working with parameters is very similar in all build tools. See examples in sections for Maven, Ant, Gradle.

Performance

Execution time primarily depends on the complexity of Git repo (especially on the number of tags, followed by the number of commits) and whether you use a custom JS buildNumberFormat or not. Without custom buildNumberFormat, you should expect execution time of 0.5 - 1.5 s. With custom buildNumberFormat add ca. 0.5 s.

Usage in Maven

The plugin binds to the validate phase (the first Maven life cycle phase) per default, so that the extracted properties are available in all other phases.

Typical usage (no specific configuration)

Typical usage with writing extracted properties to the MANIFEST.MF file:

<build>
    <plugins>

        <plugin>
            <!-- https://github.com/elab/jgit-buildnumber -->
            <groupId>com.labun.buildnumber</groupId>
            <artifactId>jgit-buildnumber-maven-plugin</artifactId>
            <version>2.7.0</version>
            <executions>
                <execution>
                    <id>jgit-buildnumber</id>
                    <goals>
                        <goal>extract-buildnumber</goal>
                    </goals>
                </execution>
            </executions>
        </plugin>

        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-jar-plugin</artifactId>
            <version>3.2.0</version>
            <configuration>
                <archive>
                    <manifestEntries>
                        <Version>${git.buildNumber}</Version>
                        <Build-Time>${git.buildDate}</Build-Time>
                    </manifestEntries>
                </archive>
            </configuration>
        </plugin>

    </plugins>
</build>

Maven resource filtering / Spring Boot

You can write the extracted properties into arbitrary files (.properties, .java, ...) using Maven resource filtering.

For example, to create git.properties file:

  1. Create file /src/main/resources/git.properties with the required properties:
buildNumber = ${git.buildNumber}
buildDate = ${git.buildDate}
...

Note: In Spring Boot projects, use this notation to access Maven properties: @git.buildNumber@.

  1. Add resource filtering configuration to the <build> section of your pom.xml:
<resources>
    <resource>
        <directory>src/main/resources</directory>
        <filtering>true</filtering>
        <includes>
            <include>git.properties</include>
        </includes>
    </resource>
</resources>

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-resources-plugin</artifactId>
    <configuration>
        <outputDirectory>${project.build.directory}</outputDirectory>
    </configuration>
    <executions>
        <execution>
            <goals>
                <goal>resources</goal>
            </goals>
        </execution>
    </executions>
</plugin>

Example with configuration

<plugin>
    <!-- https://github.com/elab/jgit-buildnumber -->
    <groupId>com.labun.buildnumber</groupId>
    <artifactId>jgit-buildnumber-maven-plugin</artifactId>
    <version>2.7.0</version>
    <executions>
        <execution>
            <id>git-buildnumber</id>
            <goals>
                <goal>extract-buildnumber</goal>
            </goals>
            <configuration>
                <countCommitsSinceInclusive>v18-start</countCommitsSinceInclusive>
                <dirtyValue>DEV</dirtyValue>
                <buildNumberFormat>
                    branch + "." + commitsCount + "/" + commitDate + "/" + shortRevision + (dirty.length > 0 ? "-" + dirty : "");
                </buildNumberFormat>
                <verbose>true</verbose>
            </configuration>
        </execution>
    </executions>
</plugin>

Multi-module projects

If the plugin is defined in parent module of a multi-module project, it will access the Git repo only once. (If you want to change that, see runOnlyAtExecutionRoot.) The properties extracted in parent module are propagated to all child modules. This only applies to normal Maven builds though, not for Eclipse m2e incremental builds, since Eclipse / OSGI has a flat workspace and doesn't support nested Maven modules.

Eclipse m2e

The plugin contains lifecycle-mapping-metadata for Eclipse m2e, and will be executed in m2e incremental builds (yet not on configuration). This is particularly important for local deployments to a JEE server from within Eclipse, if you want to see the proper build number in your web application. (Local deployment somehow depends on m2e incremental build).

If you observe performance problems, "Run on incremental" can be disabled by adding the following to Eclipse m2e workspace lifecycle-mapping-metadata.xml (Eclipse > Window > Preferences > Maven > Lifecycle Mappings):

<?xml version="1.0" encoding="UTF-8"?>
<lifecycleMappingMetadata>
    <pluginExecutions>
        <pluginExecution>
            <pluginExecutionFilter>
                <!-- https://github.com/elab/jgit-buildnumber -->
                <groupId>com.labun.buildnumber</groupId>
                <artifactId>jgit-buildnumber-maven-plugin</artifactId>
                <versionRange>[0.0,)</versionRange>
                <goals>
                    <goal>extract-buildnumber</goal>
                </goals>
            </pluginExecutionFilter>
            <action>
                <!-- disables plugin for m2e incremental builds -->
                <ignore/>
            </action>
        </pluginExecution>
    </pluginExecutions>
</lifecycleMappingMetadata>

Restart Eclipse thereafter ("apply" in Preferences is not enough).

Changing JGit version

If for some reason you'd like to change the JGit version used (e.g. a bug was encountered in a specific version), it is possible.

Since the dependency on JGit is transitive (via jgit-buildnumber-common artifact), the JGit version can be replaced via Maven exclusion mechanisms. (If it was a direct dependency, that would likely not be possible.)

Example: To change the JGit version to 5.11.1.202105131744-r (just an example, there is no particular reason for it), add the following <dependencies> section to the jgit-buildnumber-maven-plugin:

<plugin>
    <!-- https://github.com/elab/jgit-buildnumber -->
    <groupId>com.labun.buildnumber</groupId>
    <artifactId>jgit-buildnumber-maven-plugin</artifactId>
    <version>2.7.0</version>

    <dependencies>
        <dependency>
            <groupId>com.labun.buildnumber</groupId>
            <artifactId>jgit-buildnumber-common</artifactId>
            <version>2.7.0</version>
            <exclusions>
                <exclusion>
                    <groupId>org.eclipse.jgit</groupId>
                    <artifactId>org.eclipse.jgit</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.eclipse.jgit</groupId>
            <artifactId>org.eclipse.jgit</artifactId>
            <version>5.11.1.202105131744-r</version>
        </dependency>
    </dependencies>

    <executions>
        ...
    </executions>
</plugin>

As you see, the existing dependency was excluded from the jgit-buildnumber-common artifact, and the new dependency was defined on the jgit-buildnumber-maven-plugin.

Taking advantage of the Maven dependency resolution logic, you can do the same even more concise. Just add the other JGit version as a direct dependency to the jgit-buildnumber-maven-plugin. That other version, as the "nearest definition", will take precedence over the version defined in the jgit-buildnumber-common. The whole <dependencies> section of the jgit-buildnumber-maven-plugin would look as follows:

    ...
    <dependencies>
        <dependency>
            <groupId>org.eclipse.jgit</groupId>
            <artifactId>org.eclipse.jgit</artifactId>
            <version>5.11.1.202105131744-r</version>
        </dependency>
    </dependencies>
    ...

You can control the versions used by starting Maven build with the --debug option, and then looking in the log for "org.eclipse.jgit" version.

Usage in Ant

Usage is very similar to Maven. As all parameters are optional you don't have to specify any. Excerpt from build.xml:

<target name="jgit-buildnumber">
    <taskdef name="extract-buildnumber" classname="com.labun.buildnumber.JGitBuildNumberAntTask" classpathref="dependencies" />
    <extract-buildnumber />
</target>

See complete build.xml example with task parameters.

Another example shows how to extract two different buildNumbers for two repositories (e.g. "application" and "documentation") in one build file.

Usage in Gradle

Usage is very similar to Maven and Ant. Essentially, you only need to specify the dependency on jgit-buildnumber-gradle-plugin.

Complete working example of build.gradle:

buildscript {
    repositories { mavenLocal(); mavenCentral() }
    dependencies { classpath 'com.labun.buildnumber:jgit-buildnumber-gradle-plugin:2.7.0' }
}

import com.labun.buildnumber.JGitBuildNumberGradleTask

task 'extract-buildnumber' (type: JGitBuildNumberGradleTask)

See extended build.gradle example with task parameters.

The only difference in setting task parameters with Gradle (as compared to Maven and Ant) is that Gradle doesn't implicitly convert strings to other types. Therefore you have to do it explicitly. "JGit Build Number" has only one such parameter: repositoryDirectory of type java.io.File. If you need to specify this parameter, simply call an appropriate constructor:

task 'extract-buildnumber' (type: JGitBuildNumberGradleTask) {
    repositoryDirectory = new File('<absolute path>') // or: repositoryDirectory = file('<relative path>')
    ...
}

GitLab tips

Normally, the Git repo is checked out using a branch name. However, it also can be checked out using the SHA-1 of a specific commit, which is how the standard GitLab runner behaves, even on the branch builds. In this case, the HEAD is "detached" and the branch name is not available (this plugin returns an empty string for the branch property). Moreover, GitLab performs some specific art of repo cloning, where no local branch references are present in the Git metadata (refs/heads). So that "checking out" the CI_COMMIT_BRANCH is creating a new branch instead of checking out an existing one.

What can we do in such a case if the branch name is still required for the Build Number (assuming the build is triggered by a branch, otherwise the value CI_COMMIT_BRANCH will not be available):

  • Disable the default cloning and SHA-1 based checkout by setting the GIT_STRATEGY variable to none and perform a normal repo cloning and branch checkout using the CI_COMMIT_BRANCH value.

OR

  • Use the CI_COMMIT_BRANCH value directly in the buildNumberFormat JavaScript, utilizing the feature of the "JGit Build Number", which grants readonly access to environment variables from the JS via env map.

Example:

<buildNumberFormat>
   function isEmpty(v) { return v == null || v === ''; }

   if (isEmpty(branch) && !isEmpty(env.CI_COMMIT_BRANCH)) {
       print('\"branch\" value for BuildNumber will be taken from env.CI_COMMIT_BRANCH: ' + env.CI_COMMIT_BRANCH);
       branch = env.CI_COMMIT_BRANCH;
   }

   branch + '.' + commitsCount + '-' + commitDate + '-' + shortRevision + (isEmpty(dirty) ? '' : '-' + dirty);
</buildNumberFormat>

By the way, you can see in the example how to print diagnostic messages directly from the JavaScript.

Development notes

This section is intended for developers of "JGit Build Number". It can be ignored if you only use the plugins in your projects.

The project uses Lombok, a great tool which helps to free the source code from ugly boilerplate like getters and setters. Lombok is supported by all major IDEs and build tools. For Eclipse, simply add this to eclipse.ini:

-vmargs
-javaagent:<path-to-lombok-jar>

For other IDEs and tools, see projectlombok.org/setup.

License information

This project is released under the Apache License 2.0.

Changelog

2.7.0 (2022-06-12)

  • Gradle 7+ compatibility (thanks to Tony Galuhn)

  • dependency updates:

    • nashorn-core 15.3 -> 15.4,
    • maven-core 3.8.4 -> 3.8.5,
    • maven-plugin-api 3.8.4 -> 3.8.5,
    • maven-plugin-annotations 3.6.2 -> 3.6.4,
    • lombok 1.18.22 -> 1.18.24,
    • logback 1.2.7 -> 1.2.11,
    • maven-jar-plugin 3.2.0 -> 3.2.2 (in Maven example),
    • slf4j-api 1.7.32 -> 1.7.36 (in Ant example)
  • add readonly 'env' map (environment variables) to JS context

  • README: add GitLab tips section (working around detached HEAD and absence of local branch references by utilizing the 'env' map in JS)

2.6.0 (2022-01-05)

  • dependency updates:

    • nashorn-core 15.2 -> 15.3 (this update adds compatibility with Java 17, see https://bugs.openjdk.java.net/browse/JDK-8269602 and openjdk/nashorn#16 for details),
    • jgit 5.12.0.202106070339-r -> 5.13.0.202109080827-r (note: jgit v6 requires Java 11, so we will stick with v5 for now, to ensure compatibility with Java 8),
    • lombok 1.18.20 -> 1.18.22,
    • junit 5.7.2 -> 5.8.2,
    • logback 1.2.3 -> 1.2.7,
    • maven-plugin-plugin 3.6.0 -> 3.6.2,
    • maven-site-plugin 3.7.1 -> 3.9.1,
    • maven-project-info-reports-plugin 3.0.0 -> 3.1.2,
    • maven-source-plugin 3.1.0 -> 3.2.1,
    • maven-javadoc-plugin 3.1.0 -> 3.3.1,
    • maven-scm-api 1.11.2 -> 1.12.0,
    • maven-scm-provider-jgit 1.11.2 -> 1.12.0,
    • maven-plugin-api 3.8.1 -> 3.8.4,
    • maven-core 3.8.1 -> 3.8.4,
    • maven-plugin-annotations 3.6.1 -> 3.6.2,
    • ant 1.10.10 -> 1.10.12,
    • maven-jar-plugin 3.1.1 -> 3.2.0 (in Maven example),
    • slf4j-api 1.7.2 -> 1.7.32 (in Ant example)
  • Maven pom.xml files: align developers/contributors sections with Maven guidelines (i.e. leave under "developers" sections only those developers who are immediately responsible for the code and should be contacted about the project, as suggested in https://maven.apache.org/pom.html#Developers; move all others to "contributors")

  • README: add some Maven Central download statistics

  • README: redesign "Current version" table

2.5.0 (2021-06-20)

  • dependency updates: jgit 5.12.0.202106070339-r, lombok 1.18.20, maven-plugin-api 3.8.1, maven-core 3.8.1, maven-plugin-annotations 3.6.1, ant 1.10.10, groovy 2.5.14
  • if git status is dirty, log which changes caused that (verbose mode only)
  • use standalone version of Nashorn JavaScript engine if running on Java 11+
    (prevents deprecation warning on Java 11-14; enables working with Java 15+ where Nashorn is not a part of JDK anymore)
  • initialize JavaScript engine in parallel with reading Git repo (reduces overall execution time by ca. 0.5 s)
  • log number of tags (verbose mode only)
  • print some error resolution hints if countCommitsSince... in conjunction with countCommitsInPath was used and no such commit (from the countCommitsSince parameter) is found
  • homepage URL added to poms: https://github.com/elab/jgit-buildnumber
  • new property: git.buildDateMillis
  • new property: git.commitsCountSinceNearestTag
  • README revised (slightly restructured, notes about usage with Spring Boot added, section "Changing JGit version" added

2.4.0 (2020-01-01)

  • new property: git.nearestTag

2.3.1 (2019-10-01)

  • git.describe: assuring not null result to prevent plugin fail (thanks to RobertPaasche)
  • git.describe: also consider lightweight (not annotated) tags

About

Extracts Git metadata and a freely composable build number in pure Java without Git command-line tool.

Resources

Stars

Watchers

Forks

Packages

No packages published

Languages

  • Java 100.0%