Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions src/main/java/pl/project13/core/GitCommitIdPlugin.java
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,22 @@ default Map<String, String> getSystemEnv() {
boolean shouldPropertiesEscapeUnicode();

boolean shouldFailOnNoGitDirectory();

/**
* When set to {@code true}, the plugin will consider only commits affecting
* the folder containing this module.
*
* When set to {@code false}, the plugin will consider all commits in the
* repository.
*
* @return Controls whether the plugin only considers commits in the current module's directory.
*/
boolean getPerModuleVersions();

/**
* @return Base directory (folder) of the current module.
*/
File getModuleBaseDir();
}

protected static final Pattern allowedCharactersForEvaluateOnCommit = Pattern.compile("[a-zA-Z0-9\\_\\-\\^\\/\\.]+");
Expand Down Expand Up @@ -367,6 +383,9 @@ private static void loadGitDataWithNativeGit(
@Nonnull Callback cb,
@Nonnull File dotGitDirectory,
@Nonnull Properties properties) throws GitCommitIdExecutionException {
if (cb.getPerModuleVersions()) {
throw new GitCommitIdExecutionException("The native git provider does not support per module versions.");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

First thanks for your contribution, maybe it is due to the fact that I never fully understood this "per module" request, but could you outline why the native git does not support this feature (maybe by explaining what this "per module" even means?).
I personally would kinda dislike the fact that certain features of the plugin are not supported by all providers (jgit/git)....

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for considering this PR! Sorry for taking a while to get back to you, gmail sent the GitHub notification to the wrong folder.

There is no reason that I know of that native git couldn't support this. I guess that error message should have indicated it is not implemented (yet), not that it is not supported by the provider. I mostly just ported an existing PR to the current codebase, and that PR only implemented this functionality for JGit. I could look at adding it to the native git provider, if that's what is needed to get this PR done.


As to the "why" for this change:

Our project is structured like this (with many more modules in practice):

ParentPOM
| - ModuleA
| - ModuleB
| - ModuleC
  | - ModuleC1
  | - ModuleC2

Both the POM inheritance and the filesystem layout follow this hierarchy.

When building a module (e.g. ModuleC2), I want to determine the last commit ID that affected its folder—similar to running:

git log .\ModuleC\ModuleC2

I can then attach the git.properties file as an artifact when deploying the module. Later, when building the full application, I can aggregate the git.properties files and publish a document with the last commit ID to modify each module. This makes it easy to identify which modules changed between two releases.

This process is especially valuable because the software is subject to strict certification requirements. If we can show that certain modules have not changed between versions, we can significantly reduce the amount of documentation required for certification.

}
GitDataProvider nativeGitProvider = NativeGitProvider
.on(dotGitDirectory, cb.getNativeGitTimeoutInMs(), cb.getLogInterface())
.setPrefixDot(cb.getPrefixDot())
Expand All @@ -378,6 +397,7 @@ private static void loadGitDataWithNativeGit(
.setUseBranchNameFromBuildEnvironment(cb.getUseBranchNameFromBuildEnvironment())
.setExcludeProperties(cb.getExcludeProperties())
.setIncludeOnlyProperties(cb.getIncludeOnlyProperties())
.setModuleBaseDir(cb.getModuleBaseDir())
.setOffline(cb.isOffline());

nativeGitProvider.loadGitData(cb.getEvaluateOnCommit(), cb.getSystemEnv(), properties);
Expand All @@ -398,6 +418,8 @@ private static void loadGitDataWithJGit(
.setUseBranchNameFromBuildEnvironment(cb.getUseBranchNameFromBuildEnvironment())
.setExcludeProperties(cb.getExcludeProperties())
.setIncludeOnlyProperties(cb.getIncludeOnlyProperties())
.setPerModuleVersions(cb.getPerModuleVersions())
.setModuleBaseDir(cb.getModuleBaseDir())
.setOffline(cb.isOffline());

jGitProvider.loadGitData(cb.getEvaluateOnCommit(), cb.getSystemEnv(), properties);
Expand Down
41 changes: 41 additions & 0 deletions src/main/java/pl/project13/core/GitDataProvider.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@
import pl.project13.core.util.PropertyManager;

import javax.annotation.Nonnull;

import java.io.File;
import java.net.URI;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
Expand Down Expand Up @@ -108,6 +110,20 @@ public abstract class GitDataProvider implements GitProvider {
*/
protected List<String> includeOnlyProperties;

/**
* When set to {@code true}, the plugin will consider only commits affecting
* the folder containing this module.
*
* When set to {@code false}, the plugin will consider all commits in the
* repository.
*/
protected boolean perModuleVersions = false;

/**
* The directory containing this project.
*/
protected File moduleBaseDir;

/**
* When set to {@code true}, the plugin will not try to contact any remote repositories.
* Any operations will only use the local state of the repo.
Expand Down Expand Up @@ -242,6 +258,31 @@ public GitDataProvider setOffline(boolean offline) {
return this;
}

/**
* When set to {@code true}, the plugin will consider only commits affecting
* the folder containing this module.
*
* When set to {@code false}, the plugin will consider all commits in the
* repository.
* @param perModuleVersions Only consider commits affecting the folder containing this module.
* @return The {@code GitProvider} with the corresponding {@code perModuleVersions} flag set.
*/
public GitDataProvider setPerModuleVersions(boolean perModuleVersions) {
this.perModuleVersions = perModuleVersions;
return this;
}

/**
* Path to the module base directory.
*
* @param moduleBaseDir The path to the directory containing this module.
* @return The {@code GitProvider} with the corresponding {@code moduleBaseDir} set.
*/
public GitDataProvider setModuleBaseDir(File moduleBaseDir) {
this.moduleBaseDir = moduleBaseDir;
return this;
}

/**
* Main function that will attempt to load the desired properties from the git repository.
*
Expand Down
79 changes: 59 additions & 20 deletions src/main/java/pl/project13/core/JGitProvider.java
Original file line number Diff line number Diff line change
Expand Up @@ -84,29 +84,13 @@ public String getBuildAuthorEmail() throws GitCommitIdExecutionException {
@Override
public void prepareGitToExtractMoreDetailedRepoInformation() throws GitCommitIdExecutionException {
try {
// more details parsed out bellow
Ref evaluateOnCommitReference = git.findRef(evaluateOnCommit);
ObjectId evaluateOnCommitResolvedObjectId = git.resolve(evaluateOnCommit);

if ((evaluateOnCommitReference == null) && (evaluateOnCommitResolvedObjectId == null)) {
throw new GitCommitIdExecutionException(
"Could not get " + evaluateOnCommit + " Ref, are you sure you have set the dotGitDirectory " +
"property of this plugin to a valid path (currently set to " + dotGitDirectory + ")?");
}
// more details parsed out below
revWalk = new RevWalk(git);
ObjectId headObjectId;
if (evaluateOnCommitReference != null) {
headObjectId = evaluateOnCommitReference.getObjectId();
if (perModuleVersions && moduleBaseDir != null) {
evalCommit = getCommitFromModuleDirectory(moduleBaseDir);
} else {
headObjectId = evaluateOnCommitResolvedObjectId;
}

if (headObjectId == null) {
throw new GitCommitIdExecutionException(
"Could not get " + evaluateOnCommit + " Ref, are you sure you have some " +
"commits in the dotGitDirectory (currently set to " + dotGitDirectory + ")?");
evalCommit = getCommitFromRef();
}
evalCommit = revWalk.parseCommit(headObjectId);
revWalk.markStart(evalCommit);
} catch (GitCommitIdExecutionException e) {
throw e;
Expand All @@ -115,6 +99,61 @@ public void prepareGitToExtractMoreDetailedRepoInformation() throws GitCommitIdE
}
}

private RevCommit getCommitFromModuleDirectory(File moduleBaseDir) throws GitAPIException, GitCommitIdExecutionException {
//retrieve last commit in folder moduleBaseDir
try (Git gitInstance = new Git(git)) {
String relativePath = git.getDirectory().getParentFile().getAbsoluteFile().toPath().relativize(moduleBaseDir.getAbsoluteFile().toPath()).toString();
Iterator<RevCommit> iterator;
if (relativePath.trim().isEmpty()) {
// if the relative path is empty, we are in the root of the repository
iterator = gitInstance.log().call().iterator();
} else {
// otherwise, we need to specify the path to get commits for that specific directory
iterator = gitInstance.log()
.addPath(relativePath).call().iterator();
}
if (!iterator.hasNext()) {
throw new GitCommitIdExecutionException(
"Could not get commit from folder " + relativePath + " , are you sure you have some " +
"commits in the folder " + moduleBaseDir + "?");
}

RevCommit revCommit = iterator.next();
if (revCommit == null) {
throw new GitCommitIdExecutionException(
"Could not get commit from folder " + relativePath +
" , are you sure you have some commits in the folder " + moduleBaseDir + "?");
}

return revCommit;
}
}

private RevCommit getCommitFromRef() throws IOException, GitCommitIdExecutionException {
// more details parsed out below
Ref evaluateOnCommitReference = git.findRef(evaluateOnCommit);
ObjectId evaluateOnCommitResolvedObjectId = git.resolve(evaluateOnCommit);

if ((evaluateOnCommitReference == null) && (evaluateOnCommitResolvedObjectId == null)) {
throw new GitCommitIdExecutionException(
"Could not get " + evaluateOnCommit + " Ref, are you sure you have set the dotGitDirectory " +
"property of this plugin to a valid path (currently set to " + dotGitDirectory + ")?");
}
ObjectId headObjectId;
if (evaluateOnCommitReference != null) {
headObjectId = evaluateOnCommitReference.getObjectId();
} else {
headObjectId = evaluateOnCommitResolvedObjectId;
}

if (headObjectId == null) {
throw new GitCommitIdExecutionException(
"Could not get " + evaluateOnCommit + " Ref, are you sure you have some " +
"commits in the dotGitDirectory (currently set to " + dotGitDirectory + ")?");
}
return revWalk.parseCommit(headObjectId);
}

@Override
public String getBranchName() throws GitCommitIdExecutionException {
try {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1743,6 +1743,39 @@ public void verifyAllowedCharactersForEvaluateOnCommit() {
Assertions.assertFalse(p.matcher("&&cat /etc/passwd").matches());
}

@Test
public void shouldGiveCommitIdForEachFolderWhenPerModuleVersionsEnabled() throws Exception {
// given
File dotGitDirectory = createTmpDotGitDirectory(AvailableGitTestRepo.GIT_COMMIT_ID);

GitCommitIdPlugin.Callback cbSrc =
new GitCommitIdTestCallback()
.setDotGitDirectory(dotGitDirectory)
.setUseNativeGit(false)
.setPerModuleVersions(true)
.setModuleBaseDir(dotGitDirectory.getParentFile().toPath().resolve("src").toFile())
.build();
Properties propertiesSrcFolder = new Properties();

GitCommitIdPlugin.Callback cbSrcTest =
new GitCommitIdTestCallback()
.setDotGitDirectory(dotGitDirectory)
.setUseNativeGit(false)
.setPerModuleVersions(true)
.setModuleBaseDir(dotGitDirectory.getParentFile().toPath().resolve("src/test").toFile())
.build();
Properties propertiesSrcTestFolder = new Properties();

// when
GitCommitIdPlugin.runPlugin(cbSrc, propertiesSrcFolder);
GitCommitIdPlugin.runPlugin(cbSrcTest, propertiesSrcTestFolder);

// then
assertThat(propertiesSrcFolder).containsKey("git.commit.id");
assertThat(propertiesSrcTestFolder).containsKey("git.commit.id");
assertThat(propertiesSrcFolder.getProperty("git.commit.id")).isNotEqualTo(propertiesSrcTestFolder.getProperty("git.commit.id"));
}

private GitDescribeConfig createGitDescribeConfig(boolean forceLongFormat, int abbrev) {
GitDescribeConfig gitDescribeConfig = new GitDescribeConfig();
gitDescribeConfig.setTags(true);
Expand Down
22 changes: 22 additions & 0 deletions src/test/java/pl/project13/core/GitCommitIdTestCallback.java
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@ public class GitCommitIdTestCallback {
private Charset propertiesSourceCharset = StandardCharsets.UTF_8;
private boolean shouldPropertiesEscapeUnicode = false;
private boolean shouldFailOnNoGitDirectory = false;
private boolean perModuleVersions = false;
private File moduleBaseDir;

public GitCommitIdTestCallback() {
try {
Expand Down Expand Up @@ -200,6 +202,16 @@ public GitCommitIdTestCallback setShouldFailOnNoGitDirectory(boolean shouldFailO
return this;
}

public GitCommitIdTestCallback setPerModuleVersions(boolean perModuleVersions) {
this.perModuleVersions = perModuleVersions;
return this;
}

public GitCommitIdTestCallback setModuleBaseDir(File moduleBaseDir) {
this.moduleBaseDir = moduleBaseDir;
return this;
}

public GitCommitIdPlugin.Callback build() {
return new GitCommitIdPlugin.Callback() {
@Override
Expand Down Expand Up @@ -353,6 +365,16 @@ public boolean shouldPropertiesEscapeUnicode() {
public boolean shouldFailOnNoGitDirectory() {
return shouldFailOnNoGitDirectory;
}

@Override
public boolean getPerModuleVersions() {
return perModuleVersions;
}

@Override
public File getModuleBaseDir() {
return moduleBaseDir;
}
};
}

Expand Down