Skip to content

Commit

Permalink
Merge pull request #327 from philippn/features/ignore-commits
Browse files Browse the repository at this point in the history
Add feature to ignore git commits by id
  • Loading branch information
mathieucarbou authored Mar 30, 2022
2 parents 93a3c0a + 7f8396b commit 2fbfe95
Show file tree
Hide file tree
Showing 4 changed files with 95 additions and 76 deletions.
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)}.
*
* @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);
}

}
}

0 comments on commit 2fbfe95

Please sign in to comment.