pl.project13.maven.git.GitCommitIdMojo Maven / Gradle / Ivy
/*
* This file is part of git-commit-id-maven-plugin by Konrad 'ktoso' Malawski
*
* git-commit-id-maven-plugin is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* git-commit-id-maven-plugin is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with git-commit-id-maven-plugin. If not, see .
*/
package pl.project13.maven.git;
import java.io.File;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.function.Supplier;
import org.apache.maven.execution.MavenSession;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecution;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.PluginParameterExpressionEvaluator;
import org.apache.maven.plugins.annotations.Component;
import org.apache.maven.plugins.annotations.LifecyclePhase;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.project.MavenProject;
import org.apache.maven.settings.Settings;
import org.sonatype.plexus.build.incremental.BuildContext;
import pl.project13.core.*;
import pl.project13.core.git.GitDescribeConfig;
import pl.project13.core.log.LogInterface;
import pl.project13.core.util.BuildFileChangeListener;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
/**
* Puts git build-time information into property files or maven's properties.
*
* @since 1.0
*/
@Mojo(name = "revision", defaultPhase = LifecyclePhase.INITIALIZE, threadSafe = true)
public class GitCommitIdMojo extends AbstractMojo {
private static final String CONTEXT_KEY = GitCommitIdMojo.class.getName() + ".properties";
// ============================================================================================================
// Parameter injected by maven itself can't be configured in the pom.xml!
/**
* This parameter can't be configured in the {@code pom.xml}
* it represents the Maven Project that will be injected by maven itself.
*/
@Parameter(defaultValue = "${project}", readonly = true, required = true)
MavenProject project;
/**
* This parameter can't be configured in the {@code pom.xml}
* it represents the list of projects in the reactor that will be injected by maven itself.
*/
@Parameter(defaultValue = "${reactorProjects}", readonly = true, required = true)
List reactorProjects;
/**
* This parameter can't be configured in the {@code pom.xml}
* it represents the Mojo Execution that will be injected by maven itself.
*/
@Parameter(defaultValue = "${mojoExecution}", readonly = true, required = true)
MojoExecution mojoExecution;
/**
* This parameter can't be configured in the {@code pom.xml}
* it represents the Maven Session Object that will be injected by maven itself.
*/
@Parameter(defaultValue = "${session}", readonly = true, required = true)
MavenSession session;
/**
* This parameter can't be configured in the {@code pom.xml}
* it represents the Maven settings that will be injected by maven itself.
*/
@Parameter(defaultValue = "${settings}", readonly = true, required = true)
Settings settings;
// ============================================================================================================
// Parameters that can be configured in the pom.xml
/**
* Configuration to tell the git-commit-id-maven-plugin if the plugin should
* inject the git properties into all reactor projects not just the current one.
*
* The property is set to {@code false} by default to prevent the overriding of
* properties that may be unrelated to the project. If you need to expose your git properties
* to another maven module (e.g. maven-antrun-plugin) you need to set it to {@code true}.
*
* Inject git properties into all reactor projects, not just the current one
* may slow down the build and you don't always need this feature.
*
* For details about why you might want to skip this, read this issue:
* pull #65
*
* Example:
*
* {@code
* false
* }
*
*
*
* @since 2.1.4
*/
@Parameter(defaultValue = "false")
boolean injectAllReactorProjects;
/**
* Configuration to tell the git-commit-id-maven-plugin to print
* some more verbose information during the build
* (e.g. a summary of all collected properties when it's done).
*
* By default this option is disabled (set to {@code false})
*
* Note, If enabled (set to {@code true}) the plugin may
* print information you deem sensible, so be extra cautious when you share those.
*
* Example:
*
* {@code
* false
* }
*
*
*/
@Parameter(defaultValue = "false")
boolean verbose;
/**
* Configuration to tell the git-commit-id-maven-plugin to not run in
* a pom packaged project (e.g. {@code pom }).
*
* By default 'pom' packaged projects will be skipped (to {@code true})
*
* You may want to set this to {@code false}, if the plugin
* should also run inside a pom packaged project.
* Most projects won't need to override this property.
* For an use-case for this kind of behaviour see:
*
* Issue 21
*
*
* Note: The plugin might not execute at all, if you also set {@code true }
*
* Example:
*
* {@code
* true
* }
*
*
*/
@Parameter(defaultValue = "true")
boolean skipPoms;
/**
* Configuration to tell the git-commit-id-maven-plugin to
* generate a {@code 'git.properties'} file.
* By default the plugin will not not generate such a file (set to {@code false}),
* and only adds properties to maven project properties.
*
* Set this to {@code 'true'} if you want an easy way to expose your git information
* into your final artifact (jar, war, ...), which will generate a properties file (with filled out values)
* that can be configured to end up in the final artifact.
* Refer to the configuration of {@link #generateGitPropertiesFilename}`
* that helps you setup that final path.
*
* Such generated property file, can normally be read using
*
* new Properties().load(...)
*
* during runtime.
*
* Note:
* When writing the {@code git.properties} file the value *git.build.time* will only be updated
* when things in the commit information have changed. If you only change a bit of your code
* and rebuild/rerun you will see an older timestamp that you may have expected. Essentially
* the functional meaning becomes **The latest build time when the git information was written
* to the git.properties file**.
* The reason why this was done can be found in
* [issue 151](https://github.com/git-commit-id/git-commit-id-maven-plugin/issues/151).
*
* Example:
*
* {@code
* true
* }
*
*
*/
@Parameter(defaultValue = "false")
boolean generateGitPropertiesFile;
/**
* Configuration to tell the git-commit-id-maven-plugin about the
* location where you want to generate a {@code 'git.properties'} file.
*
* By default the file would be generated under
* {@code ${project.build.outputDirectory}/git.properties}, but you would need to
* set {@link #generateGitPropertiesFile} to {@code true} first to "activate" the generation of this file.
* You can also choose the format of the generated properties by specifying it under {@link #format}.
*
* The path can be relative to {@code ${project.basedir}} (e.g. {@code target/classes/git.properties}) or
* can be a full path (e.g. {@code ${project.build.outputDirectory}/git.properties}).
*
* Note: If you plan to set the generateGitPropertiesFilename-Path to a location where usually
* the source-files comes from (e.g. {@code src/main/resources}) and experience that your IDE
* (e.g. eclipse) invokes "Maven Project Builder" once every second, the chances that you
* are using an IDE where the src-folder is a watched folder for files that are only
* edited by humans is pretty high.
*
* For further information refer to the manual for your
* specific IDE and check the workflow of "incremental project builders".
*
* In order to fix this problem we recommend to set the generateGitPropertiesFilename-Path
* to a target folder (e.g. {@code ${project.build.outputDirectory}}) since this is
* the place where all derived/generated resources should go.
*
* With plugin version 3.0.0 we introduced a smarter way to counter that issue, but that might not
* be supported by your IDE.
* See: pull 385
* for further information
*
* Example:
*
* {@code
* ${project.build.outputDirectory}/git.properties
* }
*
*
*
*/
@Parameter(defaultValue = "${project.build.outputDirectory}/git.properties")
String generateGitPropertiesFilename;
/**
* Controls whether special characters in the properties
* within the {@link #generateGitPropertiesFilename} should be unicode escaped.
* By default properties are escaped (e.g. \\u6E2C\\u8A66\\u4E2D\\u6587).
* If you write commit messages in chinese and want to extract the message
* without any additional conversion from the generated properties
* you may want to set this to {@code false}.
*
* See issue 590
* for further details.
*
* Example:
*
* {@code
* true
* }
*
*
*
* @since 6.0.0
*/
@Parameter(defaultValue = "true")
boolean generateGitPropertiesFileWithEscapedUnicode;
/**
* Configuration to tell the git-commit-id-maven-plugin about the
* root directory of the git repository we want to check.
* By default uses {@code ${project.basedir}/.git} will most probably be
* ok for single module projects, in other cases please use `../` to get higher up
* in the dir tree (e.g. {@code ${project.basedir}/../.git}).
*
* Example:
*
* {@code
* ${project.basedir}/.git
* }
*
*
*/
@Parameter(defaultValue = "${project.basedir}/.git")
File dotGitDirectory;
/**
* Configuration for the {@code 'git-describe'} command.
* You can modify the dirty marker, abbrev length and other options here.
* The following `gitDescribe` configuration below is optional and can be leveraged as a
* really powerful versioning helper. If you are not familiar with
* git-describe
* it is highly recommended to go through this part of the documentation.
*
* More advanced users can most likely skip the explanations in this section, as it just explains the
* same options that git provides.
* As a side note this plugin tries to be 1-to-1 compatible with git's plain output, even
* though the describe functionality has been reimplemented manually using JGit (you don't
* have to have a git executable to use the plugin).
*
* For further information refer to this.
*
* Example:
*
* {@code
*
*
* false
*
*
* true
*
*
* 7
*
*
* -dirty
*
*
* *
*
*
* false
*
*
* false
*
* }
*
*
*
* @since 2.1.0
*/
@Parameter
GitDescribeConfig gitDescribe;
/**
* Minimum length of {@code 'git.commit.id.abbrev'} property.
* Value must be from 2 to 40 (inclusive), other values will result in an exception.
*
* Defaults to `7`
*
* An abbreviated commit is a shorter version of commit id. However, it is guaranteed to be unique.
* To keep this contract, the plugin may decide to print an abbreviated version
* that is longer than the value specified here.
*
* Example: You have a very big repository, yet you set this value to 2. It's very probable that you'll end up
* getting a 4 or 7 char long abbrev version of the commit id. If your repository, on the other hand,
* has just 4 commits, you'll probably get a 2 char long abbreviation.
*
* Example:
*
* {@code
* 7
* }
*
*
*
* @since 2.0.4
*/
@Parameter(defaultValue = "7")
int abbrevLength;
/**
* Denotes the format to save properties of the properties file that can be configured with
* {@link #generateGitPropertiesFilename}.
*
* Valid options are encoded in {@link CommitIdPropertiesOutputFormat}
* and currently would allow "properties" (default) and "json".
* Future option like yml, toml, ... might be supported at some point.
*
* Note:
* If you set this to "json", you might also should checkout the documentation about
* {@link #commitIdGenerationMode} and may want to set
* {@code full }.
*
*
* Example:
*
* {@code
* properties
* }
*
*
*/
@Parameter(defaultValue = "properties")
String format;
/**
* Not settable by any configuration in the {@code pom.xml}.
* For internal use only (represents the {@link #format} the user has set as enum.
*/
private CommitIdPropertiesOutputFormat commitIdPropertiesOutputFormat;
/**
* Configuration to tell the git-commit-id-maven-plugin about the
* property that will be used as the "namespace" prefix for all exposed/generated properties.
* An example the plugin may generate the property `${configured-prefix}.commit.id`.
* Such behaviour can be used to generate properties for multiple git repositories (see
* issue 173
* for a full example).
*
* By default is set to {@code 'git'} that for example would allow you to access {@code ${git.branch}}
*
* Example:
*
* {@code
* git
* }
*
*
*/
@Parameter(defaultValue = "git")
String prefix;
/**
* This date format will be used to format the time of any exposed/generated property
* that represents dates or times exported by this plugin (e.g. {@code git.commit.time}, {@code git.build.time}).
* It should be a valid {@link SimpleDateFormat} string.
*
*
* The current dateFormat is set to match maven's default {@code yyyy-MM-dd'T'HH:mm:ssZ}.
* Please note that in previous versions (2.2.0 - 2.2.2) the default dateFormat was set to:
* {@code dd.MM.yyyy '@' HH:mm:ss z}. However the {@code RFC 822 time zone} seems to give a more
* reliable option in parsing the date and it's being used in maven as default.
*
* Example:
*
* {@code
* yyyy-MM-dd'T'HH:mm:ssZ
* }
*
*
*
* @since 2.2.0
*/
@Parameter(defaultValue = "yyyy-MM-dd'T'HH:mm:ssZ")
String dateFormat;
/**
* The timezone used in the {@link #dateFormat} of dates exported by this plugin (e.g. {@code git.commit.time}, {@code git.build.time}).
* It should be a valid Timezone string such as {@code 'America/Los_Angeles'}, {@code 'GMT+10'} or {@code 'PST'}.
*
* As a general warning try to avoid three-letter time zone IDs because the same abbreviation are often used for multiple time zones.
* Please review https://docs.oracle.com/javase/7/docs/api/java/util/TimeZone.html
* for more information on this issue.
*
* The default value we'll use the timezone use the timezone that's shipped with java
* ({@code java.util.TimeZone.getDefault().getID()}).
* Note: If you plan to set the java's timezone by using
* {@code MAVEN_OPTS=-Duser.timezone=UTC mvn clean package},
* {@code mvn clean package -Duser.timezone=UTC},
* or any other configuration keep in mind that this option will override those settings and
* will not take other configurations into account!
*
* Example:
*
* {@code
* ${user.timezone}
* }
*
*
*
* @since 2.2.0
*/
@Parameter
String dateFormatTimeZone;
/**
* Specify whether the plugin should fail when a {@code '.git'} directory cannot be found.
* When set to {@code false} and no {@code .git} directory is found the plugin will skip execution.
*
* Defaults to {@code true}, so a missing {@code '.git'} directory is treated as error
* and should cause a failure in your build.
*
* Example:
*
* {@code
* true
* }
*
*
*
* @since 2.0.4
*/
@Parameter(defaultValue = "true")
boolean failOnNoGitDirectory;
/**
* Set this to {@code false} to continue the build even if unable to get enough data for a complete run.
* This may be useful during CI builds if the CI server does weird things to the repository.
*
* Setting this value to {@code false} causes the plugin to gracefully tell you "I did my best"
* and abort its execution if unable to obtain git meta data - yet the build will continue to run without failing.
*
* By default the plugin will fail the build (set to {@code true}) if unable to obtain enough data for a complete
* run.
*
* See issue #63
* for a rationale behind this flag.
*
* Example:
*
* {@code
* true
* }
*
*
*
* @since 2.1.5
*/
@Parameter(defaultValue = "true")
boolean failOnUnableToExtractRepoInfo;
/**
* This plugin ships with custom {@code jgit} implementation that is being used to obtain all relevant information.
* If set to {@code true} the plugin will use native git executable instead of the custom {@code jgit} implementation
* to fetch information about the repository.
* Of course if set to {@code true} will require a git executable to be installed in system.
*
* Although setting this to {@code true} (use the native git executable)
* should usually give your build some performance boost, it may randomly
* break if you upgrade your git version and it decides to print information in a different
* format suddenly.
*
* By default the plugin will use {@code jgit} implementation as a source of information about the repository.
* As rule of thumb, keep using the default {@code jgit} implementation (set to {@code false})
* until you notice performance problems within your build (usually when you have *hundreds* of maven modules).
*
* With plugin version *3.0.2* you can also control it using the commandline option
* {@code -Dmaven.gitcommitid.nativegit=true}. See {@link #useNativeGitViaCommandLine}
*
* Example:
*
* {@code
* true
* }
*
*
*
* @since 2.1.9
*/
@Parameter(defaultValue = "false")
boolean useNativeGit;
/**
* Option to be used in command-line to override the value of {@link #useNativeGit} specified in
* the pom.xml, or its default value if it's not set explicitly.
*
* NOTE / WARNING:
* Do *NOT* set this property inside the configuration of your plugin.
* Please read issue 315
* to find out why.
*
* Example:
*
* {@code
* mvn clean package -Dmaven.gitcommitid.nativegit=true
* }
*
*
*
* @since 3.0.2
*/
@Parameter(property = "maven.gitcommitid.nativegit", defaultValue = "false")
boolean useNativeGitViaCommandLine;
/**
* When set to {@code true} the plugin execution will completely skip.
* This is useful for e.g. profile activated plugin invocations or to use properties to
* enable / disable pom features.
*
* By default the execution is not skipped (set to {@code false})
*
* With version *2.2.3* you can also skip the plugin by using the commandline option
* {@code -Dmaven.gitcommitid.skip=true}. See {@link #skipViaCommandLine}
*
* Example:
*
* {@code
* false
* }
*
*
*
* @since 2.1.8
*/
@Parameter(defaultValue = "false")
boolean skip;
/**
* Option to be used in command-line to override the value of {@link #skip} specified in
* the pom.xml, or its default value if it's not set explicitly.
* Set this to {@code true} to skip plugin execution via commandline.
*
* NOTE / WARNING:
* Do *NOT* set this property inside the configuration of your plugin.
* Please read issue 315
* to find out why.
*
* Example:
*
* {@code
* mvn clean package -Dmaven.gitcommitid.skip=true
* }
*
*
*
* @since 2.2.4
*/
@Parameter(property = "maven.gitcommitid.skip", defaultValue = "false")
private boolean skipViaCommandLine;
/**
* Use with caution!
*
* Set this to {@code true} to only run once in a multi-module build.
* This means that the plugins effects will only execute once for the first project in the execution graph.
* If {@code skipPoms} is set to {@code true} (default) the plugin will run for the first
* non pom project in the execution graph (as listed in the reactor build order).
* This probably won't "do the right thing" if your project has more than one git repository.
*
* Defaults to {@code false}, so the plugin may get executed multiple times in a reactor build!
*
* Important: If you're using {@link #generateGitPropertiesFile}, setting {@code runOnlyOnce} will make
* the plugin only generate the file in the project build directory which is the first one
* based on the execution graph (!).
*
* Important: Please note that the git-commit-id-maven-plugin also has an option to skip pom
* project ({@code pom }). If you plan to use the {@code runOnlyOnce} option
* alongside with an aggregator pom you may want to set {@code false }.
* Refer to {@link #skipPoms} for more information
*
* For multi-module build you might also want to set {@link #injectAllReactorProjects} to make
* the {@code git.*} maven properties available in all modules.
*
* Note:
* Prior to version 4.0.0 the plugin was simply using the execute once applied for the parent
* project (which might have skipped execution if the parent project was a pom project).
*
* Example:
*
* {@code
* true
* }
*
*
*
* @since 2.1.12
*/
@Parameter(defaultValue = "false")
boolean runOnlyOnce;
/**
* Can be used to exclude certain properties from being emitted (e.g. filter out properties
* that you *don't* want to expose). May be useful when you want to hide
* {@code git.build.user.email} (maybe because you don't want to expose your eMail?),
* or the email of the committer?
*
* Each value may be globbing, that is, you can write {@code git.commit.user.*} to
* exclude both the {@code name}, as well as {@code email} properties from being emitted.
*
*
Please note that the strings here are Java regexes ({@code .*} is globbing,
* not plain {@code *}).
* If you have a very long list of exclusions you may want to
* use {@link #includeOnlyProperties}.
*
*
This feature was implemented in response to issue 91,
* so if you're curious about the use-case, check that issue.
*
* Prior to version 3.0.0 the plugin used the 'naive' approach to ask for all properties
* and then apply filtering. However, with the growing numbers of properties each property
* eat more and more of execution time that will be filtered out afterwards.
* With 3.0.0 this behaviour was readjusted to a 'selective running' approach whereby the
* plugin will not even try to get the property when excluded. Such behaviour can result in
* an overall reduced execution time of the plugin
* (see issue 408 for details).
*
* Defaults to the empty list (= no properties are excluded).
*
*
Example:
*
* {@code
*
* git.user.*
*
* }
*
*
*
* @since 2.1.9
*/
@Parameter
List excludeProperties;
/**
* Can be used to include only certain properties into the emission (e.g. include only
* properties that you want to expose). This feature was implemented to avoid big exclude
* properties tag when we only want very few specific properties.
*
* The inclusion rules, will be overruled by the {@link #excludeProperties} rules
* (e.g. you can write an inclusion rule that applies for multiple
* properties and then exclude a subset of them).
* You can therefor can be a bit broader in the inclusion rules and
* exclude more sensitive ones in the {@link #excludeProperties} rules.
*
*
Each value may be globbing, that is, you can write {@code git.commit.user.*} to
* exclude both the {@code name}, as well as {@code email} properties from being emitted.
*
*
Please note that the strings here are Java regexes ({@code .*} is globbing,
* not plain {@code *}).
* If you have a short list of exclusions you may want to
* use {@link #excludeProperties}.
*
*
Prior to version 3.0.0 the plugin used the 'naive' approach to ask for all properties
* and then apply filtering. However, with the growing numbers of properties each property
* eat more and more of execution time that will be filtered out afterwards.
* With 3.0.0 this behaviour was readjusted to a 'selective running' approach whereby the
* plugin will not even try to get the property when excluded. Such behaviour can result in
* an overall reduced execution time of the plugin
* (see issue 408 for details).
*
* Defaults to the empty list (= no properties are excluded).
*
* Example:
*
* {@code
*
* ^git.commit.id.full$
*
* }
*
*
*
* @since 2.1.14
*/
@Parameter
List includeOnlyProperties;
/**
* The option can be used to tell the plugin how it should generate the {@code 'git.commit.id'} property.
* Due to some naming issues when exporting the properties as an json-object
* (issue 122) we needed to
* make it possible to export all properties as a valid json-object.
*
* Due to the fact that this is one of the major properties the plugin is exporting we
* just don't want to change the exporting mechanism and somehow throw the backwards
* compatibility away.
* We rather provide a convenient switch where you can choose if you
* would like the properties as they always had been, or if you rather need to support
* full json-object compatibility.
*
* In the case you need to fully support json-object we unfortunately need to change the
* {@code 'git.commit.id'} property from {@code 'git.commit.id'} to {@code 'git.commit.id.full'} in the exporting
* mechanism to allow the generation of a fully valid json object.
*
* Currently, the switch allows two different options:
*
* -
* By default this property is set to {@code 'flat'} and will generate the formerly known
* property {@code 'git.commit.id'} as it was in the previous versions of the plugin.
* Keeping it to {@code 'flat'} by default preserve backwards compatibility and does not require further
* adjustments by the end user.
*
* -
* If you set this switch to {@code 'full'} the plugin will export the formerly known property
* {@code 'git.commit.id'} as {@code 'git.commit.id.full'} and therefore will generate a fully valid
* json object in the exporting mechanism.
*
*
*
* Note: If you set the value to something that's not equal to {@code 'flat'} or {@code 'full'} (ignoring the case)
* the plugin will output a warning and will fallback to the default {@code 'flat'} mode.
*
*
* Example:
*
* {@code
* flat
* }
*
*
*
* @since 2.2.0
*/
@Parameter(defaultValue = "flat")
String commitIdGenerationMode;
/**
* Not settable by any configuration in the {@code pom.xml}.
* For internal use only (represents the {@link #commitIdGenerationMode} the user has set as enum.
*/
private CommitIdGenerationMode commitIdGenerationModeEnum;
/**
* Can be used to replace certain characters or strings using regular expressions within the
* exposed properties.
* Replacements can be performed using regular expressions and on a configuration level
* it can be defined whether the replacement should affect all properties or just a single one.
*
* Please note that the replacement will only be applied to properties that are being generated by the plugin.
* If you want to replace properties that are being generated by other plugins you may want to use the
* maven-replacer-plugin or any other alternative.
*
* Replacements can be configured with a {@code replacementProperty}.
* A {@code replacementProperty} can have a {@code property}` and a {@code regex}-tag.
* If the {@code replacementProperty} configuration has a {@code property}-tag the
* replacement will only be performed on that specific property
* (e.g. {@code git.branch } will only be performed on {@code git.branch}).
*
* In case this specific element is not defined or left empty the replacement will be
* performed on all generated properties.
*
* The optional {@code regex}-tag can either be {@code true} to perform a replacement with regular
* expressions or {@code false} to perform a replacement with java's string.replace-function.
*
* By default the replacement will be performed with regular expressions ({@code true}).
* Furthermore each {@code replacementProperty} need to be configured with a {@code token} and a {@code value}.
* The {@code token} can be seen as the needle and the {@code value} as the text to be written over any
* found tokens. If using regular expressions the value can reference grouped regex matches
* by using $1, $2, etc.
*
* Since 2.2.4 the plugin allows to define a even more sophisticated ruleset and allows to
* set an {@code propertyOutputSuffix} within each {@code replacementProperty}.
* If this option is empty the original property will be overwritten (default behaviour in 2.2.3).
* However when this configuration is set to {@code something} and a user wants to modify the
* {@code git.branch} property the plugin will keep {@code git.branch} as the original one (w/o modifications)
* but also will be creating a new {@code git.branch.something} property with the requested replacement.
*
* Furthermore with 2.2.4 the plugin allows to perform certain types of string manipulation
* either before or after the evaluation of the replacement.
* With this feature a user can currently easily manipulate the case (e.g. lower case VS upper case)
* of the input/output property.
* This behaviour can be achieved by defining a list of {@code transformationRules} for
* the property where those rules should take effect.
* Each {@code transformationRule} consist of two required fields {@cdoe apply} and {@code action}.
* The {@cdoe apply}-tag controls when the rule should be applied and can be set to {@code BEFORE}
* to have the rule being applied before or it can be set to {@code AFTER} to have the
* rule being applied after the replacement.
* The {@code action}-tag determines the string conversion rule that should be applied.
* Currently supported is {@code LOWER_CASE} and {@code UPPER_CASE}.
* Potential candidates in the feature are {@code CAPITALIZATION} and {@code INVERT_CASE}
* (open a ticket if you need them...).
*
* Since 4.0.1 the plugin allows to define a {@code forceValueEvaluation}-switch which forces the
* plugin to evaluate the given value on every project.
*
* This might come handy if every project needs a unique value and a user wants to
* project specific variables like {@code ${project.artifactId}}.
* Be advised that this essentially means that the plugin must run for every child-project of a
* reactor build and thus might cause some overhead (the git properties should be cached).
* For a use-case refer to issue 457
*
* Defaults to the empty list / not set (= no properties are being replaced by default)
*
* Example:
*
* {@code
*
*
*
* git.branch
* something
* ^([^\/]*)\/([^\/]*)$
* $1-$2
* true
* false
*
*
* BEFORE
* UPPER_CASE
*
*
* AFTER
* LOWER_CASE
*
*
*
*
* }
*
*
*
* @since 2.2.3
*/
@Parameter
List replacementProperties;
/**
* Allow to tell the plugin what commit should be used as reference to
* generate the properties from.
*
* In general this property can be set to something generic like {@code HEAD^1} or point to a
* branch or tag-name. To support any kind or use-case this configuration can also be set
* to an entire commit-hash or it's abbreviated version.
*
* A use-case for this feature can be found in
* here.
*
* Please note that for security purposes not all references might
* be allowed as configuration. If you have a specific use-case that is currently
* not white listed feel free to file an issue.
*
* By default this property is simply set to {@code HEAD} which should reference to the latest
* commit in your repository.
*
* Example:
*
* {@code
* HEAD
* }
*
*
*
* @since 2.2.4
*/
@Parameter(defaultValue = "HEAD")
String evaluateOnCommit;
/**
* Allow to specify a timeout (in milliseconds) for fetching information with the native
* Git executable. This option might come in handy in cases where fetching information
* about the repository with the native Git executable does not terminate.
*
* Note: This option will only be taken into consideration when using the native git
* executable ({@link #useNativeGit} is set to {@code true}).
*
*
By default this timeout is set to 30000 (30 seconds).
*
*
Example:
*
* {@code
* 30000
* }
*
*
*
* @since 3.0.0
*/
@Parameter(defaultValue = "30000")
long nativeGitTimeoutInMs;
/**
* When set to {@code true} this plugin will try to use the branch name from build environment.
* Set to {@code false} to use JGit/GIT to get current branch name which can be useful
* when using the JGitflow maven plugin.
* See https://github.com/git-commit-id/git-commit-id-maven-plugin/issues/24#issuecomment-203285398
*
* Note: If not using "Check out to specific local branch' and setting this to false may result in getting
* detached head state and therefore a commit id as branch name.
*
* By default this is set to {@code true}.
*
* Example:
*
* {@code
* true
* }
*
*
*
* @since 3.0.0
*/
@Parameter(defaultValue = "true")
boolean useBranchNameFromBuildEnvironment;
/**
* Controls if this plugin should expose the generated properties into {@code System.properties}
* When set to {@code true} this plugin will try to expose the generated properties into
* {@code System.getProperties()}. Set to {@code false} to avoid this exposure.
*
* Note that parameters provided via command-line (e.g. {@code -Dgit.commit.id=value}) still
* have precedence.
*
* By default this is set to {@code true}.
*
* Example:
*
* {@code
* true
* }
*
*
*
* @since 3.0.0
*/
@Parameter(defaultValue = "true")
boolean injectIntoSysProperties;
/**
* The plugin can generate certain properties that represents the count of commits
* that your local branch is ahead or behind in perspective to the remote branch.
*
* When your branch is "ahead" it means your local branch has committed changes that are not
* pushed yet to the remote branch.
* When your branch is "behind" it means there are commits in the remote branch that are not yet
* integrated into your local branch.
*
* This configuration allows you to control if the plugin should somewhat ensure
* that such properties are more accurate. More accurate means that the plugin will perform a
* {@code git fetch} before the properties are calculated.
* Certainly a {@code git fetch} is an operation that may alter your local git repository
* and thus the plugin will operate not perform such operation (offline is set to {@code true}).
* If you however desire more accurate properties you may want to set this to {@code false}.
*
* Before version 5.X.X the default was set to {@code false} causing the plugin to operate
* in online-mode by default. Now the default is set to {@code true} (offline-mode) so the plugin might generate
* inaccurate {@code git.local.branch.ahead} and {@code git.local.branch.behind} branch information.
*
*
Example:
*
* {@code
* true
* }
*
*
*
* @since 3.0.1
*/
@Parameter(defaultValue = "true")
boolean offline;
/**
* Timestamp for reproducible output archive entries
* (https://maven.apache.org/guides/mini/guide-reproducible-builds.html).
* The value from ${project.build.outputTimestamp}
is either formatted as ISO 8601
* yyyy-MM-dd'T'HH:mm:ssXXX
or as an int representing seconds since the epoch (like
* SOURCE_DATE_EPOCH.
*
* @since 4.0.2
*/
@Parameter(defaultValue = "${project.build.outputTimestamp}")
private String projectBuildOutputTimestamp;
// This is now the end of parameters that can be configured in the pom.xml
// Happy hacking!
// ============================================================================================================
/**
* Injected {@link BuildContext} to recognize incremental builds.
*/
@Component
private BuildContext buildContext;
/**
* Charset to read-write project sources.
*/
private Charset sourceCharset = StandardCharsets.UTF_8;
/**
* This method is used to mock the system environment in testing.
*
* @return unmodifiable string map view of the current system environment {@link System#getenv}.
*/
protected Map getCustomSystemEnv() {
return System.getenv();
}
@Override
public void execute() throws MojoExecutionException {
LogInterface log = new LogInterface() {
@Override
public void debug(String msg) {
if (verbose) {
getLog().debug(msg);
}
}
@Override
public void info(String msg) {
if (verbose) {
getLog().info(msg);
}
}
@Override
public void warn(String msg) {
if (verbose) {
getLog().warn(msg);
}
}
@Override
public void error(String msg) {
// TODO: Should we truly only report errors when verbose = true?
if (verbose) {
getLog().error(msg);
}
}
@Override
public void error(String msg, Throwable t) {
// TODO: Should we truly only report errors when verbose = true?
if (verbose) {
getLog().error(msg, t);
}
}
};
try {
// Skip mojo execution on incremental builds.
if (buildContext != null && buildContext.isIncremental()) {
// Except if properties file is missing at all
if (!generateGitPropertiesFile ||
PropertiesFileGenerator.craftPropertiesOutputFile(
project.getBasedir(), new File(generateGitPropertiesFilename)).exists()) {
return;
}
}
// read source encoding from project properties for those who still doesn't use UTF-8
String sourceEncoding = project.getProperties().getProperty("project.build.sourceEncoding");
if (null != sourceEncoding) {
sourceCharset = Charset.forName(sourceEncoding);
} else {
sourceCharset = Charset.defaultCharset();
}
if (skip || skipViaCommandLine) {
log.info("skip is enabled, skipping execution!");
return;
}
if (runOnlyOnce) {
List sortedProjects =
Optional.ofNullable(session.getProjectDependencyGraph())
.map(graph -> graph.getSortedProjects())
.orElseGet(() -> {
log.warn("Maven's dependency graph is null. Assuming project is the only one executed.");
return Collections.singletonList(session.getCurrentProject());
});
MavenProject firstProject = sortedProjects.stream()
// skipPoms == true => find first project that is not pom project
.filter(p -> {
if (skipPoms) {
return !isPomProject(p);
} else {
return true;
}
})
.findFirst()
.orElse(session.getCurrentProject());
log.info("Current project: '" + session.getCurrentProject().getName() +
"', first project to execute based on dependency graph: '" + firstProject.getName() + "'");
if (!session.getCurrentProject().equals(firstProject)) {
log.info("runOnlyOnce is enabled and this project is not the first project (perhaps skipPoms is configured?), skipping execution!");
return;
}
}
if (isPomProject(project) && skipPoms) {
log.info("isPomProject is true and skipPoms is true, return");
return;
}
dotGitDirectory = lookupGitDirectory();
if (failOnNoGitDirectory && !directoryExists(dotGitDirectory)) {
throw new GitCommitIdExecutionException(".git directory is not found! Please specify a valid [dotGitDirectory] in your pom.xml");
}
if (gitDescribe == null) {
gitDescribe = new GitDescribeConfig();
}
if (dotGitDirectory != null) {
log.info("dotGitDirectory '" + dotGitDirectory.getAbsolutePath() + "'");
} else {
log.info("dotGitDirectory is null, aborting execution!");
return;
}
try {
commitIdGenerationModeEnum = CommitIdGenerationMode.valueOf(commitIdGenerationMode.toUpperCase());
} catch (IllegalArgumentException e) {
log.warn("Detected wrong setting for 'commitIdGenerationMode'. Falling back to default 'flat' mode!");
commitIdGenerationModeEnum = CommitIdGenerationMode.FLAT;
}
try {
commitIdPropertiesOutputFormat = CommitIdPropertiesOutputFormat.valueOf(format.toUpperCase());
} catch (IllegalArgumentException e) {
log.warn("Detected wrong setting for 'format'. Falling back to default 'properties' mode!");
commitIdPropertiesOutputFormat = CommitIdPropertiesOutputFormat.PROPERTIES;
}
final GitCommitIdPlugin.Callback cb = new GitCommitIdPlugin.Callback() {
@Override
public Map getSystemEnv() {
return getCustomSystemEnv();
}
@Override
public Supplier supplyProjectVersion() {
return () -> project.getVersion();
}
@Nonnull
@Override
public LogInterface getLogInterface() {
return log;
}
@Nonnull
@Override
public String getDateFormat() {
return dateFormat;
}
@Nonnull
@Override
public String getDateFormatTimeZone() {
return dateFormatTimeZone;
}
@Nonnull
@Override
public String getPrefixDot() {
String trimmedPrefix = prefix.trim();
return trimmedPrefix.equals("") ? "" : trimmedPrefix + ".";
}
@Override
public List getExcludeProperties() {
return excludeProperties;
}
@Override
public List getIncludeOnlyProperties() {
return includeOnlyProperties;
}
@Nullable
@Override
public Date getReproducibleBuildOutputTimestamp() throws GitCommitIdExecutionException {
return parseOutputTimestamp(projectBuildOutputTimestamp);
}
@Override
public boolean useNativeGit() {
return useNativeGit || useNativeGitViaCommandLine;
}
@Override
public long getNativeGitTimeoutInMs() {
return nativeGitTimeoutInMs;
}
@Override
public int getAbbrevLength() {
return abbrevLength;
}
@Override
public GitDescribeConfig getGitDescribe() {
return gitDescribe;
}
@Override
public CommitIdGenerationMode getCommitIdGenerationMode() {
return commitIdGenerationModeEnum;
}
@Override
public boolean getUseBranchNameFromBuildEnvironment() {
return useBranchNameFromBuildEnvironment;
}
@Override
public boolean isOffline() {
return offline || settings.isOffline();
}
@Override
public String getEvaluateOnCommit() {
return evaluateOnCommit;
}
@Override
public File getDotGitDirectory() {
return dotGitDirectory;
}
@Override
public boolean shouldGenerateGitPropertiesFile() {
return generateGitPropertiesFile;
}
@Override
public void performPublishToAllSystemEnvironments(Properties properties) {
publishToAllSystemEnvironments(getLogInterface(), properties);
}
@Override
public void performPropertiesReplacement(Properties properties) {
PropertiesReplacer propertiesReplacer = new PropertiesReplacer(
log, new PluginParameterExpressionEvaluator(session, mojoExecution));
propertiesReplacer.performReplacement(properties, replacementProperties);
logProperties(getLogInterface(), properties);
}
@Override
public CommitIdPropertiesOutputFormat getPropertiesOutputFormat() {
return commitIdPropertiesOutputFormat;
}
@Override
public BuildFileChangeListener getBuildFileChangeListener() {
return file -> {
// this should only be null in our tests
if (buildContext != null) {
buildContext.refresh(file);
}
};
}
@Override
public String getProjectName() {
return project.getName();
}
@Override
public File getProjectBaseDir() {
return project.getBasedir();
}
@Override
public File getGenerateGitPropertiesFile() {
return new File(generateGitPropertiesFilename);
}
@Override
public Charset getPropertiesSourceCharset() {
return sourceCharset;
}
@Override
public boolean shouldPropertiesEscapeUnicode() {
return generateGitPropertiesFileWithEscapedUnicode;
}
};
Properties properties = null;
// check if properties have already been injected
Properties contextProperties = getContextProperties(project);
boolean alreadyInjected = injectAllReactorProjects && contextProperties != null;
if (alreadyInjected) {
log.info("injectAllReactorProjects is enabled - attempting to use the already computed values");
properties = contextProperties;
}
GitCommitIdPlugin.runPlugin(cb, properties);
} catch (GitCommitIdExecutionException e) {
throw new MojoExecutionException(e.getMessage(), e);
}
}
private void publishToAllSystemEnvironments(LogInterface log, Properties propertiesToPublish) {
publishPropertiesInto(propertiesToPublish, project.getProperties());
// some plugins rely on the user properties (e.g. flatten-maven-plugin)
publishPropertiesInto(propertiesToPublish, session.getUserProperties());
if (injectAllReactorProjects) {
appendPropertiesToReactorProjects(log, propertiesToPublish);
}
if (injectIntoSysProperties) {
publishPropertiesInto(propertiesToPublish, System.getProperties());
publishPropertiesInto(propertiesToPublish, session.getSystemProperties());
publishPropertiesInto(propertiesToPublish, session.getRequest().getSystemProperties());
}
}
@Nullable
private Properties getContextProperties(MavenProject project) {
Object stored = project.getContextValue(CONTEXT_KEY);
if (stored instanceof Properties) {
return (Properties)stored;
}
return null;
}
/**
* Parse output timestamp configured for Reproducible Builds' archive entries
* (https://maven.apache.org/guides/mini/guide-reproducible-builds.html).
* The value from ${project.build.outputTimestamp}
is either formatted as ISO 8601
* yyyy-MM-dd'T'HH:mm:ssXXX
or as an int representing seconds since the epoch (like
* SOURCE_DATE_EPOCH.
*
* Inspired by https://github.com/apache/maven-archiver/blob/a3103d99396cd8d3440b907ef932a33563225265/src/main/java/org/apache/maven/archiver/MavenArchiver.java#L765
*
* @param outputTimestamp the value of ${project.build.outputTimestamp}
(may be null
)
* @return the parsed timestamp, may be null
if null
input or input contains only 1
* character
*/
private Date parseOutputTimestamp(String outputTimestamp) throws GitCommitIdExecutionException {
if (outputTimestamp != null && !outputTimestamp.trim().isEmpty() && outputTimestamp.chars().allMatch(Character::isDigit)) {
return new Date(Long.parseLong(outputTimestamp) * 1000);
}
if ((outputTimestamp == null) || (outputTimestamp.length() < 2)) {
// no timestamp configured
return null;
}
DateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssXXX");
try {
return df.parse(outputTimestamp);
} catch (ParseException pe) {
throw new GitCommitIdExecutionException(
"Invalid 'project.build.outputTimestamp' value '" + outputTimestamp + "'",
pe);
}
}
private void publishPropertiesInto(Properties propertiesToPublish, Properties propertiesTarget) {
for (String propertyName : propertiesToPublish.stringPropertyNames()) {
propertiesTarget.setProperty(propertyName, propertiesToPublish.getProperty(propertyName));
}
}
private void appendPropertiesToReactorProjects(LogInterface log, Properties propertiesToPublish) {
for (MavenProject mavenProject : reactorProjects) {
log.debug("Adding properties to project: '" + mavenProject.getName() + "'");
publishPropertiesInto(propertiesToPublish, mavenProject.getProperties());
mavenProject.setContextValue(CONTEXT_KEY, propertiesToPublish);
}
log.info("Added properties to '" + reactorProjects.size() + "' projects");
}
/**
* Find the git directory of the currently used project.
* If it's not already specified, this method will try to find it.
*
* @return the File representation of the .git directory
*/
private File lookupGitDirectory() throws GitCommitIdExecutionException {
return new GitDirLocator(project, reactorProjects).lookupGitDirectory(dotGitDirectory);
}
private void logProperties(LogInterface log, Properties propertiesToPublish) {
for (String propertyName : propertiesToPublish.stringPropertyNames()) {
log.info("including property '" + propertyName + "' in results");
}
}
private boolean isPomProject(@Nonnull MavenProject project) {
return project.getPackaging().equalsIgnoreCase("pom");
}
private boolean directoryExists(@Nullable File fileLocation) {
return fileLocation != null && fileLocation.exists() && fileLocation.isDirectory();
}
}