背景

maven的git-commit-id插件,可以在release jar包时,生成一个git.properties文件,文件中可以附带上git的一些信息。git.properties示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#Generated by Git-Commit-Id-Plugin
#Sun Jun 27 14:58:49 CST 2021
git.branch=6b3dbc38d106181da431300c928cc961d2454c66
git.build.host=2926090-11428607-20260424
git.build.time=20210627145849282
git.build.user.email=
git.build.user.name=
git.build.version=1.0.2458
git.commit.id=6b3dbc38d106181da431300c928cc961d2454c66
git.commit.id.abbrev=6b3dbc38
git.commit.time=20210627145707000
git.commit.user.email=xx@xx.com
git.commit.user.name=xx
git.remote.origin.url=git@git.xxx

使用过程中,会发现一些大的项目,执行这个插件的时间总是很长:

1
2
2021-07-16 21:23:25.000 [INFO] [INFO] --- git-commit-id-plugin:2.2.6:revision (get-the-git-infos) @ xxx ---
2021-07-16 21:24:03.000 [INFO] [INFO]

比如这个模块,就执行了38s,如果有多个模块的话,这个时间的花费就非常的客观了,一次release都能好几分钟。

原因

获取git的哪些属性可以通过xml来配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
 <plugin>
<groupId>pl.project13.maven</groupId>
<artifactId>git-commit-id-plugin</artifactId>
<version>${git-commit-id-plugin.version}</version>
<executions>
<execution>
<id>get-the-git-infos</id>
<goals>
<goal>revision</goal>
</goals>
</execution>
</executions>
<configuration>
<verbose>false</verbose>
<abbrevLength>8</abbrevLength>
<dateFormat>yyyyMMddHHmmssSSS</dateFormat>
<failOnNoGitDirectory>false</failOnNoGitDirectory>
<failOnUnableToExtractRepoInfo>false</failOnUnableToExtractRepoInfo>
<generateGitPropertiesFile>true</generateGitPropertiesFile>
<gitDescribe>
<skip>true</skip>
</gitDescribe>
<includeOnlyProperties>
<include>git.branch</include>
<include>git.build</include>
<include>git.commit.id</include>
<include>git.commit.time</include>
<include>git.commit.user</include>
<include>git.remote.origin.url</include>
</includeOnlyProperties>
</configuration>
</plugin>

有些属性的获取是比较耗时的,需要遍历所有的commit记录(比如Tags等)。但是我们的配置中并没有这个属性,时间还是很长,执行火焰图发现:

从火焰图中,可以明细的看出,在递归地遍历git的history,而且这个操作是getTags触发的。阅读源码发现,低版本的插件,是先计算,后过滤。也就是不管你配置了没有配置这个属性,都会参与一遍计算:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// pl.project13.maven.git.GitCommitIdMojo#execute

// 1. 获取所有git的数据,包含tags
loadGitData(properties);
loadBuildData(properties);
loadShortDescribe(properties);
propertiesReplacer.performReplacement(properties, replacementProperties);
// 2. 根据传入的参数进行过滤,相当于总是获取全量数据,然后给用户的视图进行了过滤
propertiesFilterer.filter(properties, includeOnlyProperties, this.prefixDot);
propertiesFilterer.filterNot(properties, excludeProperties, this.prefixDot);
logProperties();


//1. pl.project13.maven.git.GitDataProvider#loadGitData
// 这里直接是getTags(),而不是provider的模式,所以直接计算了所有的tags,很耗时
put(properties, GitCommitPropertyConstant.TAGS, getTags());
put(properties,GitCommitPropertyConstant.CLOSEST_TAG_NAME, getClosestTagName());
put(properties,GitCommitPropertyConstant.CLOSEST_TAG_COMMIT_COUNT, getClosestTagCommitCount());

loadGitData时,将所有的属性都计算了一遍,然后扔到properties中,后续再propertiesFilterer进行过滤。切换到最新版的代码,已经修改为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// pl.project13.core.GitDataProvider#loadGitData
// 已经改成了provider的形式,这里是方法引用,压栈时不会触发属性的计算
maybePut(properties, GitCommitPropertyConstant.TAGS, this::getTags);
maybePut(properties,GitCommitPropertyConstant.CLOSEST_TAG_NAME, this::getClosestTagName);


// pl.project13.core.GitDataProvider#maybePut
protected void maybePut(@Nonnull Properties properties, String key, SupplierEx<String> value)
throws GitCommitIdExecutionException {
String keyWithPrefix = prefixDot + key;
if (properties.stringPropertyNames().contains(keyWithPrefix)) {
String propertyValue = properties.getProperty(keyWithPrefix);
log.info("Using cached {} with value {}", keyWithPrefix, propertyValue);
} else if (PropertiesFilterer.isIncluded(keyWithPrefix, includeOnlyProperties, excludeProperties)) {
// 符合条件(配置文件中配置了对应的属性)的才会get,触发计算
String propertyValue = value.get();
log.info("Collected {} with value {}", keyWithPrefix, propertyValue);
PropertyManager.putWithoutPrefix(properties, keyWithPrefix, propertyValue);
}
}

新的代码中已经改为provider的模式了,这种是懒加载的,实际去get的时候,才会触发计算。因此直接升级之后就好了,升级之后,耗时直接变为毫秒级。

JGit

除了计算逻辑上的bug,还有一个jgit与native git的性能差异,

issue中也有人反馈tag过多导致执行慢的,但是通过使用本地的git替换之后从38s到3.6s

Long execution times with jgit · Issue #408 · git-commit-id/git-commit-id-maven-plugin

作者的回复中也比较了JGit和NativeGit的区别,JGit可以不用关心git的版本导致的输出形式的变化(这些问题由JGit来负责);如果使用Native Git的话,是自己解析的git的输出,如果git版本变了,这个解析可能出错。所以默认是使用JGit。

using the native git binary 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. As rule of thumb, keep using the default jgit implementation until you notice performance problems within your build

结论

  • Git-commit-id的低版本(2.2.6至少是有问题的)有计算逻辑的问题(先计算,后过滤),升级之后就好了,时间从秒级下降至毫秒级
  • JGit的性能不如native git,必要时可以进行替换
  • 发布脚本,在clone代码时,可以加上–depth=1,避免不必要的提交历史

参考