Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add feature to ignore git commits by id #327

Merged
merged 2 commits into from
Mar 30, 2022
Merged
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
1 change: 1 addition & 0 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ Basically, when you are developing a project either in open source or in a compa
* [@stain](https://github.com/stain)
* [@vromero](https://github.com/vromero)
* [@yoosiba](https://github.com/yoosiba)
* [@philippn](https://github.com/philippn)

Please let me know if your name is missing!

Expand Down
2 changes: 2 additions & 0 deletions license-maven-plugin-git/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ Which properties is license-maven-plugin-git adding?

The full git history for a file is required for accurate determination of the first commit (for the creation author name/email, creation year, or existence years). The plugin will warn if it detects a shallow git repository. If you are certain your shallow depth will still permit determination of these values you may suppress the warning by setting the parameter `license.warnIfShallow` to false.

It is possible to filter out specific commits by adding one or more (comma-separated) SHA-1 hashes to the property `license.git.commitsToIgnore`. This property will only affect the computation of the last change year, not of the creation year.

If you are using properties requiring the year of the last commit of a file but not using the "creation" properties requiring the first commit, you can improve performance by limiting the git history for each file using the property `license.git.maxCommitsLookup`. A value of 1 would provide the best performance but could be impacted if the last commit year is earlier than the year of previous commits.

How to use license-maven-plugin-git
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
*/
package com.mycila.maven.plugin.license.git;

import static java.util.Objects.requireNonNull;

import java.io.Closeable;
import java.io.File;
import java.io.IOException;
Expand All @@ -23,14 +25,19 @@
import java.util.Calendar;
import java.util.Date;
import java.util.Iterator;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.TimeZone;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.api.Status;
import org.eclipse.jgit.api.errors.GitAPIException;
import org.eclipse.jgit.diff.DiffConfig;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.PersonIdent;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.FollowFilter;
Expand All @@ -57,6 +64,7 @@ public class GitLookup implements Closeable {
private static final String COPYRIGHT_LAST_YEAR_MAX_COMMITS_LOOKUP_KEY = "license.git.copyrightLastYearMaxCommitsLookup";
public static final String COPYRIGHT_LAST_YEAR_SOURCE_KEY = "license.git.copyrightLastYearSource";
public static final String COPYRIGHT_LAST_YEAR_TIME_ZONE_KEY = "license.git.copyrightLastYearTimeZone";
public static final String COMMITS_TO_IGNORE_KEY = "license.git.commitsToIgnore";

public enum DateSource {
AUTHOR, COMMITER
Expand All @@ -68,47 +76,44 @@ public enum DateSource {
private final Repository repository;
private final TimeZone timeZone;
private final boolean shallow;
private final Set<ObjectId> commitsToIgnore;

/**
* Lazily initializes #gitLookup assuming that all subsequent calls to this method will be related
* to the same git repository.
*/
public static GitLookup create(File file, Map<String, String> props) {
String dateSourceString = props.get(COPYRIGHT_LAST_YEAR_SOURCE_KEY);
if (dateSourceString == null) {
dateSourceString = GitLookup.DateSource.AUTHOR.name();
}
GitLookup.DateSource dateSource = GitLookup.DateSource.valueOf(
dateSourceString.toUpperCase(Locale.US));
String checkCommitsCountString = props.get(MAX_COMMITS_LOOKUP_KEY);
// Backwads compatibility
if (checkCommitsCountString == null) {
checkCommitsCountString = props.get(COPYRIGHT_LAST_YEAR_MAX_COMMITS_LOOKUP_KEY);
}
int checkCommitsCount = Integer.MAX_VALUE;
if (checkCommitsCountString != null) {
checkCommitsCountString = checkCommitsCountString.trim();
checkCommitsCount = Integer.parseInt(checkCommitsCountString);
}
final TimeZone timeZone;
String tzString = props.get(COPYRIGHT_LAST_YEAR_TIME_ZONE_KEY);
switch (dateSource) {
case COMMITER:
timeZone = tzString == null ? GitLookup.DEFAULT_ZONE : TimeZone.getTimeZone(tzString);
break;
case AUTHOR:
if (tzString != null) {
throw new RuntimeException(COPYRIGHT_LAST_YEAR_TIME_ZONE_KEY + " must not be set with "
+ COPYRIGHT_LAST_YEAR_SOURCE_KEY + " = " + GitLookup.DateSource.AUTHOR.name()
+ " because git author name already contains time zone information.");
}
timeZone = null;
break;
default:
throw new IllegalStateException(
"Unexpected " + GitLookup.DateSource.class.getName() + " " + dateSource);
}
return new GitLookup(file, dateSource, timeZone, checkCommitsCount);
final GitLookup.DateSource dateSource = Optional.ofNullable(props.get(COPYRIGHT_LAST_YEAR_SOURCE_KEY))
.map(String::trim)
.map(String::toUpperCase)
.map(GitLookup.DateSource::valueOf)
.orElse(GitLookup.DateSource.AUTHOR);

final int checkCommitsCount = Stream.of(
MAX_COMMITS_LOOKUP_KEY,
COPYRIGHT_LAST_YEAR_MAX_COMMITS_LOOKUP_KEY) // Backwards compatibility
.map(props::get)
.filter(Objects::nonNull)
.map(String::trim)
.map(Integer::parseInt)
.findFirst()
.orElse(Integer.MAX_VALUE);

final Set<ObjectId> commitsToIgnore = Stream.of(COMMITS_TO_IGNORE_KEY)
.map(props::get)
.filter(Objects::nonNull)
.flatMap(s -> Stream.of(s.split(",")))
.map(String::trim)
.filter(s -> !s.isEmpty())
.map(ObjectId::fromString)
.collect(Collectors.toSet());

final TimeZone timeZone = Optional.ofNullable(props.get(COPYRIGHT_LAST_YEAR_TIME_ZONE_KEY))
.map(String::trim)
.map(TimeZone::getTimeZone)
.orElse(DEFAULT_ZONE);

return new GitLookup(file, dateSource, timeZone, checkCommitsCount, commitsToIgnore);
}

/**
Expand All @@ -122,10 +127,16 @@ public static GitLookup create(File file, Map<String, String> props) {
* @param dateSource where to read the commit dates from - committer date or author date
* @param timeZone the time zone if {@code dateSource} is {@link DateSource#COMMITER};
* otherwise must be {@code null}.
* @param checkCommitsCount
* @param checkCommitsCount the number of historical commits, per file, to check
* @param commitsToIgnore the commits to ignore while inspecting the history for {@code anyFile}
* @throws IOException
*/
private GitLookup(File anyFile, DateSource dateSource, TimeZone timeZone, int checkCommitsCount) {
private GitLookup(File anyFile, DateSource dateSource, TimeZone timeZone, int checkCommitsCount, Set<ObjectId> commitsToIgnore) {
requireNonNull(anyFile);
requireNonNull(dateSource);
requireNonNull(timeZone);
requireNonNull(commitsToIgnore);

try {
this.repository = new FileRepositoryBuilder().findGitDir(anyFile).build();
/* A workaround for https://bugs.eclipse.org/bugs/show_bug.cgi?id=457961 */
Expand All @@ -135,26 +146,11 @@ private GitLookup(File anyFile, DateSource dateSource, TimeZone timeZone, int ch
// Closing the repository will close the FileObjectDatabase.
// Here the newReader() is a WindowCursor which delegates the getShallowCommits() to the FileObjectDatabase.
this.shallow = !this.repository.getObjectDatabase().newReader().getShallowCommits().isEmpty();

this.pathResolver = new GitPathResolver(repository.getWorkTree().getAbsolutePath());
this.dateSource = dateSource;
switch (dateSource) {
case COMMITER:
this.timeZone = timeZone == null ? DEFAULT_ZONE : timeZone;
break;
case AUTHOR:
if (timeZone != null) {
throw new IllegalArgumentException(
"Time zone must be null with dateSource " + DateSource.AUTHOR.name()
+ " because git author name already contains time zone information.");
}
this.timeZone = null;
break;
default:
throw new IllegalStateException(
"Unexpected " + DateSource.class.getName() + " " + dateSource);
}
this.timeZone = timeZone;
this.checkCommitsCount = checkCommitsCount;
this.commitsToIgnore = commitsToIgnore;
} catch (IOException e) {
throw new UncheckedIOException(e);
}
Expand All @@ -165,7 +161,7 @@ private GitLookup(File anyFile, DateSource dateSource, TimeZone timeZone, int ch
* year is taken either from the committer date or from the author identity depending on how {@link #dateSource} was
* initialized.
* <p>
* See also the note on time zones in {@link #GitLookup(File, DateSource, TimeZone, int)}.
* See also the note on time zones in {@link #GitLookup(File, DateSource, TimeZone, int, Set)}.
mathieucarbou marked this conversation as resolved.
Show resolved Hide resolved
*
* @param file for which the year should be retrieved
* @return year of last modification of the file
Expand All @@ -182,6 +178,9 @@ int getYearOfLastChange(File file) throws GitAPIException, IOException {
int commitYear = 0;
RevWalk walk = getGitRevWalk(repoRelativePath, false);
for (RevCommit commit : walk) {
if (commitsToIgnore.contains(commit.getId())) {
continue;
}
int y = getYearFromCommit(commit);
if (y > commitYear) {
commitYear = y;
Expand Down Expand Up @@ -277,7 +276,7 @@ private RevWalk getGitRevWalk(String repoRelativePath, boolean oldestCommitsFirs
}

private int getCurrentYear() {
return toYear(System.currentTimeMillis(), timeZone != null ? timeZone : DEFAULT_ZONE);
return toYear(System.currentTimeMillis(), timeZone);
}

private int getYearFromCommit(RevCommit commit) {
Expand Down Expand Up @@ -314,4 +313,4 @@ private String getAuthorEmailFromCommit(RevCommit commit) {
public void close() {
repository.close();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -156,39 +156,31 @@ void reuseProvider() throws GitAPIException, IOException {

@Test
void timezone() throws GitAPIException, IOException {
try {
GitLookup.create(gitRepoRoot.toFile(), buildProps(DateSource.AUTHOR, "GMT", "10"));
Assertions.fail("RuntimeException expected");
} catch (RuntimeException e) {
if (e.getMessage().contains(
"license.git.copyrightLastYearTimeZone must not be set with license.git.copyrightLastYearSource = AUTHOR because git author name already contains time zone information.")) {
/* expected */
} else {
throw e;
}
}
// do not fail if a tz is./mv set, it will just be unused
// it allows parent poms to pre-defined properties and let sub modules use them if needed
GitLookup.create(gitRepoRoot.toFile(), buildProps(DateSource.AUTHOR, "GMT", "10", null));

/* null is GMT */
GitLookup nullTzLookup = GitLookup.create(gitRepoRoot.toFile(),
buildProps(DateSource.COMMITER, null, "10"));
buildProps(DateSource.COMMITER, null, "10", null));
assertLastChange(nullTzLookup, "dir1/file3.txt", 2010);

/* explicit GMT */
GitLookup gmtLookup = GitLookup.create(gitRepoRoot.toFile(),
buildProps(DateSource.COMMITER, "GMT", "10"));
buildProps(DateSource.COMMITER, "GMT", "10", null));
assertLastChange(gmtLookup, "dir1/file3.txt", 2010);

/*
* explicit non-GMT zome. Note that the relevant commit's (GMT) time stamp is 2010-12-31T23:30:00 which yealds
* 2011 in the CET (+01:00) time zone
*/
GitLookup cetLookup = GitLookup.create(gitRepoRoot.toFile(),
buildProps(DateSource.COMMITER, "CET", "10"));
buildProps(DateSource.COMMITER, "CET", "10", null));
assertLastChange(cetLookup, "dir1/file3.txt", 2011);

}

private Map<String, String> buildProps(DateSource ds, String tz, String history) {
private Map<String, String> buildProps(DateSource ds, String tz, String history, String commitsToIgnoreCSV) {
Map<String, String> props = new HashMap<>();
if (history != null) {
props.put(GitLookup.MAX_COMMITS_LOOKUP_KEY, history);
Expand All @@ -199,15 +191,40 @@ private Map<String, String> buildProps(DateSource ds, String tz, String history)
if (ds != null) {
props.put(GitLookup.COPYRIGHT_LAST_YEAR_SOURCE_KEY, ds.name());
}
if (commitsToIgnoreCSV != null) {
props.put(GitLookup.COMMITS_TO_IGNORE_KEY, commitsToIgnoreCSV);
}
return props;
}

private GitLookup newAuthorLookup() {
return GitLookup.create(gitRepoRoot.toFile(), buildProps(DateSource.AUTHOR, null, "10"));
return GitLookup.create(gitRepoRoot.toFile(), buildProps(DateSource.AUTHOR, null, "10", null));
}

private GitLookup newCommitterLookup() {
return GitLookup.create(gitRepoRoot.toFile(), buildProps(DateSource.COMMITER, null, "10"));
return GitLookup.create(gitRepoRoot.toFile(), buildProps(DateSource.COMMITER, null, "10", null));
}

@Test
public void ignoreCommitsInLastChange() throws GitAPIException, IOException {
assertLastChange(newAuthorLookup("95d52919cbe340dc271cf1f5ec68cf36705bd3a3"), "dir1/file1.txt", 2004);
assertLastChange(newCommitterLookup("95d52919cbe340dc271cf1f5ec68cf36705bd3a3"), "dir1/file1.txt", 2004);
}

@Test
public void doNotIgnoreCommitsInCreation() throws GitAPIException, IOException {
assertCreation(newAuthorLookup("53b44baedc5a378f9b665da12f298e1003793219"), "dir1/file1.txt", 2000);
assertCreation(newCommitterLookup("53b44baedc5a378f9b665da12f298e1003793219"), "dir1/file1.txt", 2000);
}

private GitLookup newAuthorLookup(String... commitsToIgnore) throws IOException {
String commitsToIgnoreCSV = String.join(",", commitsToIgnore);
return GitLookup.create(gitRepoRoot.toFile(), buildProps(DateSource.AUTHOR, null, "10", commitsToIgnoreCSV));
}

private GitLookup newCommitterLookup(String... commitsToIgnore) throws IOException {
String commitsToIgnoreCSV = String.join(",", commitsToIgnore);
return GitLookup.create(gitRepoRoot.toFile(), buildProps(DateSource.COMMITER, null, "10", commitsToIgnoreCSV));
}

private void assertLastChange(GitLookup provider, String relativePath, int expected) throws
Expand All @@ -224,4 +241,4 @@ private void assertCreation(GitLookup provider, String relativePath, int expecte
Assertions.assertEquals(expected, actual);
}

}
}