From 1d181aea1775cec40cfae589cc61fe97d4e4e49c Mon Sep 17 00:00:00 2001 From: Daniel Widdis Date: Sat, 15 Jan 2022 13:26:29 -0800 Subject: [PATCH 1/8] Optionally skip execution if repository is shallow --- .../plugin/license/AbstractLicenseMojo.java | 18 +++ .../plugin/license/util/ExecutingCommand.java | 149 ++++++++++++++++++ 2 files changed, 167 insertions(+) create mode 100644 license-maven-plugin/src/main/java/com/mycila/maven/plugin/license/util/ExecutingCommand.java diff --git a/license-maven-plugin/src/main/java/com/mycila/maven/plugin/license/AbstractLicenseMojo.java b/license-maven-plugin/src/main/java/com/mycila/maven/plugin/license/AbstractLicenseMojo.java index 1141a4958..82826a39c 100755 --- a/license-maven-plugin/src/main/java/com/mycila/maven/plugin/license/AbstractLicenseMojo.java +++ b/license-maven-plugin/src/main/java/com/mycila/maven/plugin/license/AbstractLicenseMojo.java @@ -26,6 +26,7 @@ import com.mycila.maven.plugin.license.header.HeaderDefinition; import com.mycila.maven.plugin.license.header.HeaderSource; import com.mycila.maven.plugin.license.header.HeaderType; +import com.mycila.maven.plugin.license.util.ExecutingCommand; import com.mycila.maven.plugin.license.util.Selection; import com.mycila.maven.plugin.license.util.resource.ResourceFinder; import com.mycila.xmltool.XMLDoc; @@ -295,6 +296,15 @@ public abstract class AbstractLicenseMojo extends AbstractMojo { @Parameter(property = "license.skip", defaultValue = "false") public boolean skip = false; + /** + * Whether to skip the plugin execution on a shallow git clone. + *

+ * We recommend setting this to {@code true} if you are generating copyright + * ranges from file creation dates in git history. + */ + @Parameter(property = "license.skipIfShallow", defaultValue = "false") + public boolean skipIfShallow; + /** * If you do not want to see the list of file having a missing header, you * can add the quiet flag that will shorten the output @@ -474,6 +484,14 @@ public void checkUnknown() throws MojoExecutionException { @SuppressWarnings({"unchecked"}) protected final void execute(final Callback callback) throws MojoExecutionException, MojoFailureException { if (!skip) { + // skip if shallow clone detected + if (skipIfShallow) { + List output = ExecutingCommand.runNative("git rev-parse --is-shallow-repository"); + if (!output.isEmpty() && "true".equals(output.get(0))) { + warn("Shallow repository detected, skipping."); + return; + } + } // make default base dir canonical this.defaultBasedir = this.getCanonicalFile(this.defaultBasedir, "license.basedir"); diff --git a/license-maven-plugin/src/main/java/com/mycila/maven/plugin/license/util/ExecutingCommand.java b/license-maven-plugin/src/main/java/com/mycila/maven/plugin/license/util/ExecutingCommand.java new file mode 100644 index 000000000..12d4370fd --- /dev/null +++ b/license-maven-plugin/src/main/java/com/mycila/maven/plugin/license/util/ExecutingCommand.java @@ -0,0 +1,149 @@ +/* + * Copyright (C) 2008-2021 Mycila (mathieu.carbou@gmail.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.mycila.maven.plugin.license.util; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * A class for executing on the command line and returning the result of + * execution. + */ +public final class ExecutingCommand { + + private static final boolean IS_WINDOWS = System.getProperty("os.name").startsWith("Windows"); + private static final boolean IS_SOLARIS = !IS_WINDOWS && System.getProperty("os.name").startsWith("SunOS") + || System.getProperty("os.name").startsWith("Solaris"); + private static final String[] DEFAULT_ENV = getDefaultEnv(); + + private ExecutingCommand() { + } + + private static String[] getDefaultEnv() { + if (IS_WINDOWS) { + return new String[] { "LANGUAGE=C" }; + } else { + return new String[] { "LC_ALL=C" }; + } + } + + /** + * Executes a command on the native command line and returns the result. This is + * a convenience method to call {@link java.lang.Runtime#exec(String)} and + * capture the resulting output in a list of Strings. On Windows, built-in + * commands not associated with an executable program may require + * {@code cmd.exe /c} to be prepended to the command. + * + * @param cmdToRun + * Command to run + * @return A list of Strings representing the result of the command, or empty + * list if the command failed + */ + public static List runNative(String cmdToRun) { + String[] cmd = cmdToRun.split(" "); + return runNative(cmd); + } + + /** + * Executes a command on the native command line and returns the result line by + * line. This is a convenience method to call + * {@link java.lang.Runtime#exec(String[])} and capture the resulting output in + * a list of Strings. On Windows, built-in commands not associated with an + * executable program may require the strings {@code cmd.exe} and {@code /c} to + * be prepended to the array. + * + * @param cmdToRunWithArgs + * Command to run and args, in an array + * @return A list of Strings representing the result of the command, or empty + * list if the command failed + */ + public static List runNative(String[] cmdToRunWithArgs) { + return runNative(cmdToRunWithArgs, DEFAULT_ENV); + } + + /** + * Executes a command on the native command line and returns the result line by + * line. This is a convenience method to call + * {@link java.lang.Runtime#exec(String[])} and capture the resulting output in + * a list of Strings. On Windows, built-in commands not associated with an + * executable program may require the strings {@code cmd.exe} and {@code /c} to + * be prepended to the array. + * + * @param cmdToRunWithArgs + * Command to run and args, in an array + * @param envp + * array of strings, each element of which has environment variable + * settings in the format name=value, or null if the subprocess should + * inherit the environment of the current process. + * @return A list of Strings representing the result of the command, or empty + * list if the command failed + */ + public static List runNative(String[] cmdToRunWithArgs, String[] envp) { + Process p = null; + try { + p = Runtime.getRuntime().exec(cmdToRunWithArgs, envp); + return getProcessOutput(p, cmdToRunWithArgs); + } catch (SecurityException | IOException e) { + return Collections.emptyList(); + } finally { + // Ensure all resources are released if exec fails + if (p != null) { + // Windows and Solaris don't close descriptors on destroy, + // so we must handle separately + if (IS_WINDOWS || IS_SOLARIS) { + try { + p.getOutputStream().close(); + } catch (IOException e) { + // do nothing on failure + } + try { + p.getInputStream().close(); + } catch (IOException e) { + // do nothing on failure + } + try { + p.getErrorStream().close(); + } catch (IOException e) { + // do nothing on failure + } + } + p.destroy(); + } + } + } + + private static List getProcessOutput(Process p, String[] cmd) { + ArrayList sa = new ArrayList<>(); + try (BufferedReader reader = new BufferedReader( + new InputStreamReader(p.getInputStream(), Charset.defaultCharset()))) { + String line; + while ((line = reader.readLine()) != null) { + sa.add(line); + } + p.waitFor(); + } catch (IOException e) { + return Collections.emptyList(); + } catch (InterruptedException ie) { + Thread.currentThread().interrupt(); + } + return sa; + } +} From 226fec2b94df611b0f2c0c61cc4f2daa1bc57de4 Mon Sep 17 00:00:00 2001 From: Daniel Widdis Date: Sun, 16 Jan 2022 15:05:56 -0800 Subject: [PATCH 2/8] Detect shallow repo by testing .git/shallow existence --- .../license/git/CopyrightAuthorProvider.java | 7 +- .../license/git/CopyrightRangeProvider.java | 5 +- .../maven/plugin/license/git/GitLookup.java | 6 + .../plugin/license/AbstractLicenseMojo.java | 19 +-- .../plugin/license/util/ExecutingCommand.java | 149 ------------------ 5 files changed, 19 insertions(+), 167 deletions(-) delete mode 100644 license-maven-plugin/src/main/java/com/mycila/maven/plugin/license/util/ExecutingCommand.java diff --git a/license-maven-plugin-git/src/main/java/com/mycila/maven/plugin/license/git/CopyrightAuthorProvider.java b/license-maven-plugin-git/src/main/java/com/mycila/maven/plugin/license/git/CopyrightAuthorProvider.java index 5347b4ce4..6ea4535ec 100644 --- a/license-maven-plugin-git/src/main/java/com/mycila/maven/plugin/license/git/CopyrightAuthorProvider.java +++ b/license-maven-plugin-git/src/main/java/com/mycila/maven/plugin/license/git/CopyrightAuthorProvider.java @@ -56,12 +56,15 @@ public Map getAdditionalProperties(AbstractLicenseMojo mojo, Pro try { Map result = new HashMap(3); GitLookup gitLookup = getGitLookup(document.getFile(), properties); - + + if (mojo.failIfShallow && gitLookup.isShallowRepository()) { + throw new RuntimeException("Shallow repository detected."); + } result.put(COPYRIGHT_CREATION_AUTHOR_NAME_KEY, gitLookup.getAuthorNameOfCreation(document.getFile())); result.put(COPYRIGHT_CREATION_AUTHOR_EMAIL_KEY, gitLookup.getAuthorEmailOfCreation(document.getFile())); return Collections.unmodifiableMap(result); } catch (Exception e) { - throw new RuntimeException("Could not compute the year of the last git commit for file " + throw new RuntimeException("Could not compute the author of first git commit for file " + document.getFile().getAbsolutePath(), e); } } diff --git a/license-maven-plugin-git/src/main/java/com/mycila/maven/plugin/license/git/CopyrightRangeProvider.java b/license-maven-plugin-git/src/main/java/com/mycila/maven/plugin/license/git/CopyrightRangeProvider.java index c024e77f5..47d19d416 100644 --- a/license-maven-plugin-git/src/main/java/com/mycila/maven/plugin/license/git/CopyrightRangeProvider.java +++ b/license-maven-plugin-git/src/main/java/com/mycila/maven/plugin/license/git/CopyrightRangeProvider.java @@ -79,6 +79,9 @@ public Map getAdditionalProperties(AbstractLicenseMojo mojo, Pro try { Map result = new HashMap(4); GitLookup gitLookup = getGitLookup(document.getFile(), properties); + if (mojo.failIfShallow && gitLookup.isShallowRepository()) { + throw new RuntimeException("Shallow repository detected."); + } int copyrightEnd = gitLookup.getYearOfLastChange(document.getFile()); result.put(COPYRIGHT_LAST_YEAR_KEY, Integer.toString(copyrightEnd)); final String copyrightYears; @@ -102,7 +105,7 @@ public Map getAdditionalProperties(AbstractLicenseMojo mojo, Pro return Collections.unmodifiableMap(result); } catch (Exception e) { - throw new RuntimeException("Could not compute the year of the last git commit for file " + throw new RuntimeException("Could not compute years requiring git commit history for file " + document.getFile().getAbsolutePath(), e); } } diff --git a/license-maven-plugin-git/src/main/java/com/mycila/maven/plugin/license/git/GitLookup.java b/license-maven-plugin-git/src/main/java/com/mycila/maven/plugin/license/git/GitLookup.java index 6e62f6028..00d017eab 100644 --- a/license-maven-plugin-git/src/main/java/com/mycila/maven/plugin/license/git/GitLookup.java +++ b/license-maven-plugin-git/src/main/java/com/mycila/maven/plugin/license/git/GitLookup.java @@ -184,6 +184,12 @@ String getAuthorEmailOfCreation(File file) throws IOException, GitAPIException { walk.dispose(); return authorEmail; } + + boolean isShallowRepository() { + File dotGit = repository.getDirectory(); + File shallow = new File(dotGit.getPath() + File.separator + "shallow"); + return shallow.exists(); + } private boolean isFileModifiedOrUnstaged(String repoRelativePath) throws GitAPIException { Status status = new Git(repository).status().addPath(repoRelativePath).call(); diff --git a/license-maven-plugin/src/main/java/com/mycila/maven/plugin/license/AbstractLicenseMojo.java b/license-maven-plugin/src/main/java/com/mycila/maven/plugin/license/AbstractLicenseMojo.java index 82826a39c..28fda87cf 100755 --- a/license-maven-plugin/src/main/java/com/mycila/maven/plugin/license/AbstractLicenseMojo.java +++ b/license-maven-plugin/src/main/java/com/mycila/maven/plugin/license/AbstractLicenseMojo.java @@ -26,7 +26,6 @@ import com.mycila.maven.plugin.license.header.HeaderDefinition; import com.mycila.maven.plugin.license.header.HeaderSource; import com.mycila.maven.plugin.license.header.HeaderType; -import com.mycila.maven.plugin.license.util.ExecutingCommand; import com.mycila.maven.plugin.license.util.Selection; import com.mycila.maven.plugin.license.util.resource.ResourceFinder; import com.mycila.xmltool.XMLDoc; @@ -297,13 +296,11 @@ public abstract class AbstractLicenseMojo extends AbstractMojo { public boolean skip = false; /** - * Whether to skip the plugin execution on a shallow git clone. - *

- * We recommend setting this to {@code true} if you are generating copyright - * ranges from file creation dates in git history. + * Whether to fail plugin execution on a shallow git clone if you are using + * git history for initial author or copyright ranges. */ - @Parameter(property = "license.skipIfShallow", defaultValue = "false") - public boolean skipIfShallow; + @Parameter(property = "license.failIfShallow", defaultValue = "true") + public boolean failIfShallow = true; /** * If you do not want to see the list of file having a missing header, you @@ -484,14 +481,6 @@ public void checkUnknown() throws MojoExecutionException { @SuppressWarnings({"unchecked"}) protected final void execute(final Callback callback) throws MojoExecutionException, MojoFailureException { if (!skip) { - // skip if shallow clone detected - if (skipIfShallow) { - List output = ExecutingCommand.runNative("git rev-parse --is-shallow-repository"); - if (!output.isEmpty() && "true".equals(output.get(0))) { - warn("Shallow repository detected, skipping."); - return; - } - } // make default base dir canonical this.defaultBasedir = this.getCanonicalFile(this.defaultBasedir, "license.basedir"); diff --git a/license-maven-plugin/src/main/java/com/mycila/maven/plugin/license/util/ExecutingCommand.java b/license-maven-plugin/src/main/java/com/mycila/maven/plugin/license/util/ExecutingCommand.java deleted file mode 100644 index 12d4370fd..000000000 --- a/license-maven-plugin/src/main/java/com/mycila/maven/plugin/license/util/ExecutingCommand.java +++ /dev/null @@ -1,149 +0,0 @@ -/* - * Copyright (C) 2008-2021 Mycila (mathieu.carbou@gmail.com) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.mycila.maven.plugin.license.util; - -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStreamReader; -import java.nio.charset.Charset; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -/** - * A class for executing on the command line and returning the result of - * execution. - */ -public final class ExecutingCommand { - - private static final boolean IS_WINDOWS = System.getProperty("os.name").startsWith("Windows"); - private static final boolean IS_SOLARIS = !IS_WINDOWS && System.getProperty("os.name").startsWith("SunOS") - || System.getProperty("os.name").startsWith("Solaris"); - private static final String[] DEFAULT_ENV = getDefaultEnv(); - - private ExecutingCommand() { - } - - private static String[] getDefaultEnv() { - if (IS_WINDOWS) { - return new String[] { "LANGUAGE=C" }; - } else { - return new String[] { "LC_ALL=C" }; - } - } - - /** - * Executes a command on the native command line and returns the result. This is - * a convenience method to call {@link java.lang.Runtime#exec(String)} and - * capture the resulting output in a list of Strings. On Windows, built-in - * commands not associated with an executable program may require - * {@code cmd.exe /c} to be prepended to the command. - * - * @param cmdToRun - * Command to run - * @return A list of Strings representing the result of the command, or empty - * list if the command failed - */ - public static List runNative(String cmdToRun) { - String[] cmd = cmdToRun.split(" "); - return runNative(cmd); - } - - /** - * Executes a command on the native command line and returns the result line by - * line. This is a convenience method to call - * {@link java.lang.Runtime#exec(String[])} and capture the resulting output in - * a list of Strings. On Windows, built-in commands not associated with an - * executable program may require the strings {@code cmd.exe} and {@code /c} to - * be prepended to the array. - * - * @param cmdToRunWithArgs - * Command to run and args, in an array - * @return A list of Strings representing the result of the command, or empty - * list if the command failed - */ - public static List runNative(String[] cmdToRunWithArgs) { - return runNative(cmdToRunWithArgs, DEFAULT_ENV); - } - - /** - * Executes a command on the native command line and returns the result line by - * line. This is a convenience method to call - * {@link java.lang.Runtime#exec(String[])} and capture the resulting output in - * a list of Strings. On Windows, built-in commands not associated with an - * executable program may require the strings {@code cmd.exe} and {@code /c} to - * be prepended to the array. - * - * @param cmdToRunWithArgs - * Command to run and args, in an array - * @param envp - * array of strings, each element of which has environment variable - * settings in the format name=value, or null if the subprocess should - * inherit the environment of the current process. - * @return A list of Strings representing the result of the command, or empty - * list if the command failed - */ - public static List runNative(String[] cmdToRunWithArgs, String[] envp) { - Process p = null; - try { - p = Runtime.getRuntime().exec(cmdToRunWithArgs, envp); - return getProcessOutput(p, cmdToRunWithArgs); - } catch (SecurityException | IOException e) { - return Collections.emptyList(); - } finally { - // Ensure all resources are released if exec fails - if (p != null) { - // Windows and Solaris don't close descriptors on destroy, - // so we must handle separately - if (IS_WINDOWS || IS_SOLARIS) { - try { - p.getOutputStream().close(); - } catch (IOException e) { - // do nothing on failure - } - try { - p.getInputStream().close(); - } catch (IOException e) { - // do nothing on failure - } - try { - p.getErrorStream().close(); - } catch (IOException e) { - // do nothing on failure - } - } - p.destroy(); - } - } - } - - private static List getProcessOutput(Process p, String[] cmd) { - ArrayList sa = new ArrayList<>(); - try (BufferedReader reader = new BufferedReader( - new InputStreamReader(p.getInputStream(), Charset.defaultCharset()))) { - String line; - while ((line = reader.readLine()) != null) { - sa.add(line); - } - p.waitFor(); - } catch (IOException e) { - return Collections.emptyList(); - } catch (InterruptedException ie) { - Thread.currentThread().interrupt(); - } - return sa; - } -} From 8a080083ed9645d230515767e5e175c40af8a5a6 Mon Sep 17 00:00:00 2001 From: Daniel Widdis Date: Sun, 16 Jan 2022 20:02:13 -0800 Subject: [PATCH 3/8] Fix NPE from getDirectory() by using known root directory. --- .../com/mycila/maven/plugin/license/git/GitLookup.java | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/license-maven-plugin-git/src/main/java/com/mycila/maven/plugin/license/git/GitLookup.java b/license-maven-plugin-git/src/main/java/com/mycila/maven/plugin/license/git/GitLookup.java index 00d017eab..671dff37a 100644 --- a/license-maven-plugin-git/src/main/java/com/mycila/maven/plugin/license/git/GitLookup.java +++ b/license-maven-plugin-git/src/main/java/com/mycila/maven/plugin/license/git/GitLookup.java @@ -59,6 +59,7 @@ public enum DateSource { private final GitPathResolver pathResolver; private final Repository repository; private final TimeZone timeZone; + private final boolean shallow; /** * Creates a new {@link GitLookup} for a repository that is detected from the supplied {@code anyFile}. @@ -78,7 +79,8 @@ public GitLookup(File anyFile, DateSource dateSource, TimeZone timeZone, int che /* A workaround for https://bugs.eclipse.org/bugs/show_bug.cgi?id=457961 */ this.repository.getObjectDatabase().newReader().getShallowCommits(); - this.pathResolver = new GitPathResolver(repository.getWorkTree().getAbsolutePath()); + String rootDir = repository.getWorkTree().getAbsolutePath(); + this.pathResolver = new GitPathResolver(rootDir); this.dateSource = dateSource; switch (dateSource) { case COMMITER: @@ -95,6 +97,8 @@ public GitLookup(File anyFile, DateSource dateSource, TimeZone timeZone, int che throw new IllegalStateException("Unexpected " + DateSource.class.getName() + " " + dateSource); } this.checkCommitsCount = checkCommitsCount; + String dotGitDir = rootDir + (rootDir.endsWith(File.separator) ? "" : File.separator) + ".git"; + this.shallow = new File(dotGitDir + File.separator + "shallow").exists(); } /** @@ -186,9 +190,7 @@ String getAuthorEmailOfCreation(File file) throws IOException, GitAPIException { } boolean isShallowRepository() { - File dotGit = repository.getDirectory(); - File shallow = new File(dotGit.getPath() + File.separator + "shallow"); - return shallow.exists(); + return this.shallow; } private boolean isFileModifiedOrUnstaged(String repoRelativePath) throws GitAPIException { From 3c76cc6655a0c7d17ef0393a9d57d662060ab151 Mon Sep 17 00:00:00 2001 From: Daniel Widdis Date: Mon, 17 Jan 2022 01:25:17 -0800 Subject: [PATCH 4/8] Pass a non-null mojo in the test cases --- .../maven/plugin/license/git/CopyrightAuthorProviderTest.java | 3 ++- .../maven/plugin/license/git/CopyrightRangeProviderTest.java | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/license-maven-plugin-git/src/test/java/com/mycila/maven/plugin/license/git/CopyrightAuthorProviderTest.java b/license-maven-plugin-git/src/test/java/com/mycila/maven/plugin/license/git/CopyrightAuthorProviderTest.java index adae778fd..ed2a990af 100644 --- a/license-maven-plugin-git/src/test/java/com/mycila/maven/plugin/license/git/CopyrightAuthorProviderTest.java +++ b/license-maven-plugin-git/src/test/java/com/mycila/maven/plugin/license/git/CopyrightAuthorProviderTest.java @@ -15,6 +15,7 @@ */ package com.mycila.maven.plugin.license.git; +import com.mycila.maven.plugin.license.LicenseCheckMojo; import com.mycila.maven.plugin.license.document.Document; import org.junit.AfterClass; import org.junit.Assert; @@ -48,7 +49,7 @@ private void assertAuthor(CopyrightAuthorProvider provider, String path, String Properties props = new Properties(); Document document = newDocument(path); - Map actual = provider.getAdditionalProperties(null, props, document); + Map actual = provider.getAdditionalProperties(new LicenseCheckMojo(), props, document); HashMap expected = new HashMap(); expected.put(CopyrightAuthorProvider.COPYRIGHT_CREATION_AUTHOR_NAME_KEY, copyrightAuthorName); diff --git a/license-maven-plugin-git/src/test/java/com/mycila/maven/plugin/license/git/CopyrightRangeProviderTest.java b/license-maven-plugin-git/src/test/java/com/mycila/maven/plugin/license/git/CopyrightRangeProviderTest.java index c5103b6b5..8074e9c55 100644 --- a/license-maven-plugin-git/src/test/java/com/mycila/maven/plugin/license/git/CopyrightRangeProviderTest.java +++ b/license-maven-plugin-git/src/test/java/com/mycila/maven/plugin/license/git/CopyrightRangeProviderTest.java @@ -15,6 +15,7 @@ */ package com.mycila.maven.plugin.license.git; +import com.mycila.maven.plugin.license.LicenseCheckMojo; import com.mycila.maven.plugin.license.document.Document; import org.junit.AfterClass; import org.junit.Assert; @@ -66,7 +67,7 @@ private void assertRange(CopyrightRangeProvider provider, String path, String in props.put(CopyrightRangeProvider.INCEPTION_YEAR_KEY, inceptionYear); Document document = newDocument(path); - Map actual = provider.getAdditionalProperties(null, props, document); + Map actual = provider.getAdditionalProperties(new LicenseCheckMojo(), props, document); HashMap expected = new HashMap(); expected.put(CopyrightRangeProvider.COPYRIGHT_CREATION_YEAR_KEY, copyrightStart); From f26d2bfba97f2c7d672778d42b957cdd9d607c94 Mon Sep 17 00:00:00 2001 From: Daniel Widdis Date: Mon, 17 Jan 2022 10:13:16 -0800 Subject: [PATCH 5/8] Use existing getShallowCommits call for shallow repo check --- .../com/mycila/maven/plugin/license/git/GitLookup.java | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/license-maven-plugin-git/src/main/java/com/mycila/maven/plugin/license/git/GitLookup.java b/license-maven-plugin-git/src/main/java/com/mycila/maven/plugin/license/git/GitLookup.java index 671dff37a..822197151 100644 --- a/license-maven-plugin-git/src/main/java/com/mycila/maven/plugin/license/git/GitLookup.java +++ b/license-maven-plugin-git/src/main/java/com/mycila/maven/plugin/license/git/GitLookup.java @@ -77,10 +77,10 @@ public GitLookup(File anyFile, DateSource dateSource, TimeZone timeZone, int che super(); this.repository = new FileRepositoryBuilder().findGitDir(anyFile).build(); /* A workaround for https://bugs.eclipse.org/bugs/show_bug.cgi?id=457961 */ - this.repository.getObjectDatabase().newReader().getShallowCommits(); + // Also contains contents of .git/shallow and can detect shallow repo + this.shallow = !this.repository.getObjectDatabase().newReader().getShallowCommits().isEmpty(); - String rootDir = repository.getWorkTree().getAbsolutePath(); - this.pathResolver = new GitPathResolver(rootDir); + this.pathResolver = new GitPathResolver(repository.getWorkTree().getAbsolutePath()); this.dateSource = dateSource; switch (dateSource) { case COMMITER: @@ -97,8 +97,6 @@ public GitLookup(File anyFile, DateSource dateSource, TimeZone timeZone, int che throw new IllegalStateException("Unexpected " + DateSource.class.getName() + " " + dateSource); } this.checkCommitsCount = checkCommitsCount; - String dotGitDir = rootDir + (rootDir.endsWith(File.separator) ? "" : File.separator) + ".git"; - this.shallow = new File(dotGitDir + File.separator + "shallow").exists(); } /** From 629cd5582703d7ad49604a39854fd3c65b041509 Mon Sep 17 00:00:00 2001 From: Daniel Widdis Date: Mon, 17 Jan 2022 10:47:28 -0800 Subject: [PATCH 6/8] More verbose documentation --- .../mycila/maven/plugin/license/AbstractLicenseMojo.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/license-maven-plugin/src/main/java/com/mycila/maven/plugin/license/AbstractLicenseMojo.java b/license-maven-plugin/src/main/java/com/mycila/maven/plugin/license/AbstractLicenseMojo.java index 28fda87cf..832ed5796 100755 --- a/license-maven-plugin/src/main/java/com/mycila/maven/plugin/license/AbstractLicenseMojo.java +++ b/license-maven-plugin/src/main/java/com/mycila/maven/plugin/license/AbstractLicenseMojo.java @@ -296,8 +296,11 @@ public abstract class AbstractLicenseMojo extends AbstractMojo { public boolean skip = false; /** - * Whether to fail plugin execution on a shallow git clone if you are using - * git history for initial author or copyright ranges. + * Determination of the year and author of the first commit and last change year + * of a file requires a full git history. By default the plugin will log an + * exception and take no action when attempting these substitutions on a shallow + * repository. If you are certain the repository depth will permit accurate + * determination of these values, you can disable this check. */ @Parameter(property = "license.failIfShallow", defaultValue = "true") public boolean failIfShallow = true; From 451c84af0bf3761de17024fa22a8c55cbfb9046b Mon Sep 17 00:00:00 2001 From: Daniel Widdis Date: Mon, 17 Jan 2022 13:25:19 -0800 Subject: [PATCH 7/8] Revert exception code, refine original warning code --- .../plugin/license/git/CopyrightAuthorProvider.java | 9 +++------ .../plugin/license/git/CopyrightRangeProvider.java | 7 ++----- .../plugin/license/git/GitPropertiesProvider.java | 8 +++++++- .../maven/plugin/license/AbstractLicenseMojo.java | 12 ++++++------ 4 files changed, 18 insertions(+), 18 deletions(-) diff --git a/license-maven-plugin-git/src/main/java/com/mycila/maven/plugin/license/git/CopyrightAuthorProvider.java b/license-maven-plugin-git/src/main/java/com/mycila/maven/plugin/license/git/CopyrightAuthorProvider.java index 6ea4535ec..eb99714f1 100644 --- a/license-maven-plugin-git/src/main/java/com/mycila/maven/plugin/license/git/CopyrightAuthorProvider.java +++ b/license-maven-plugin-git/src/main/java/com/mycila/maven/plugin/license/git/CopyrightAuthorProvider.java @@ -55,16 +55,13 @@ public Map getAdditionalProperties(AbstractLicenseMojo mojo, Pro try { Map result = new HashMap(3); - GitLookup gitLookup = getGitLookup(document.getFile(), properties); - - if (mojo.failIfShallow && gitLookup.isShallowRepository()) { - throw new RuntimeException("Shallow repository detected."); - } + GitLookup gitLookup = getGitLookup(mojo, document.getFile(), properties); + result.put(COPYRIGHT_CREATION_AUTHOR_NAME_KEY, gitLookup.getAuthorNameOfCreation(document.getFile())); result.put(COPYRIGHT_CREATION_AUTHOR_EMAIL_KEY, gitLookup.getAuthorEmailOfCreation(document.getFile())); return Collections.unmodifiableMap(result); } catch (Exception e) { - throw new RuntimeException("Could not compute the author of first git commit for file " + throw new RuntimeException("Could not compute the year of the last git commit for file " + document.getFile().getAbsolutePath(), e); } } diff --git a/license-maven-plugin-git/src/main/java/com/mycila/maven/plugin/license/git/CopyrightRangeProvider.java b/license-maven-plugin-git/src/main/java/com/mycila/maven/plugin/license/git/CopyrightRangeProvider.java index 47d19d416..b4a10246f 100644 --- a/license-maven-plugin-git/src/main/java/com/mycila/maven/plugin/license/git/CopyrightRangeProvider.java +++ b/license-maven-plugin-git/src/main/java/com/mycila/maven/plugin/license/git/CopyrightRangeProvider.java @@ -78,10 +78,7 @@ public Map getAdditionalProperties(AbstractLicenseMojo mojo, Pro } try { Map result = new HashMap(4); - GitLookup gitLookup = getGitLookup(document.getFile(), properties); - if (mojo.failIfShallow && gitLookup.isShallowRepository()) { - throw new RuntimeException("Shallow repository detected."); - } + GitLookup gitLookup = getGitLookup(mojo, document.getFile(), properties); int copyrightEnd = gitLookup.getYearOfLastChange(document.getFile()); result.put(COPYRIGHT_LAST_YEAR_KEY, Integer.toString(copyrightEnd)); final String copyrightYears; @@ -105,7 +102,7 @@ public Map getAdditionalProperties(AbstractLicenseMojo mojo, Pro return Collections.unmodifiableMap(result); } catch (Exception e) { - throw new RuntimeException("Could not compute years requiring git commit history for file " + throw new RuntimeException("Could not compute the year of the last git commit for file " + document.getFile().getAbsolutePath(), e); } } diff --git a/license-maven-plugin-git/src/main/java/com/mycila/maven/plugin/license/git/GitPropertiesProvider.java b/license-maven-plugin-git/src/main/java/com/mycila/maven/plugin/license/git/GitPropertiesProvider.java index 57ea08b60..9b9d8aa5b 100644 --- a/license-maven-plugin-git/src/main/java/com/mycila/maven/plugin/license/git/GitPropertiesProvider.java +++ b/license-maven-plugin-git/src/main/java/com/mycila/maven/plugin/license/git/GitPropertiesProvider.java @@ -21,6 +21,8 @@ import java.util.Properties; import java.util.TimeZone; +import com.mycila.maven.plugin.license.AbstractLicenseMojo; + /** * @author Peter Palaga */ @@ -43,7 +45,7 @@ public GitPropertiesProvider() {} * @return * @throws IOException */ - GitLookup getGitLookup(File file, Properties props) throws IOException { + GitLookup getGitLookup(AbstractLicenseMojo mojo, File file, Properties props) throws IOException { if (gitLookup == null) { synchronized (this) { if (gitLookup == null) { @@ -74,6 +76,10 @@ GitLookup getGitLookup(File file, Properties props) throws IOException { throw new IllegalStateException("Unexpected " + GitLookup.DateSource.class.getName() + " " + dateSource); } gitLookup = new GitLookup(file, dateSource, timeZone, checkCommitsCount); + // One-time warning for shallow repo + if (mojo.warnIfShallow && gitLookup.isShallowRepository()) { + mojo.warn("Shallow git repository detected. Year and author property values may not be accurate."); + } } } } diff --git a/license-maven-plugin/src/main/java/com/mycila/maven/plugin/license/AbstractLicenseMojo.java b/license-maven-plugin/src/main/java/com/mycila/maven/plugin/license/AbstractLicenseMojo.java index 832ed5796..6e6337d11 100755 --- a/license-maven-plugin/src/main/java/com/mycila/maven/plugin/license/AbstractLicenseMojo.java +++ b/license-maven-plugin/src/main/java/com/mycila/maven/plugin/license/AbstractLicenseMojo.java @@ -297,13 +297,13 @@ public abstract class AbstractLicenseMojo extends AbstractMojo { /** * Determination of the year and author of the first commit and last change year - * of a file requires a full git history. By default the plugin will log an - * exception and take no action when attempting these substitutions on a shallow - * repository. If you are certain the repository depth will permit accurate - * determination of these values, you can disable this check. + * of a file requires a full git or svn history. By default the plugin will log + * warning when using these properties on a shallow or sparse repository. If you + * are certain the repository depth will permit accurate determination of these + * values, you can disable this check. */ - @Parameter(property = "license.failIfShallow", defaultValue = "true") - public boolean failIfShallow = true; + @Parameter(property = "license.warnIfShallow", defaultValue = "true") + public boolean warnIfShallow = true; /** * If you do not want to see the list of file having a missing header, you From 8d9b642c72b0df9ac6eaa53a55bc497ea6b7842b Mon Sep 17 00:00:00 2001 From: Daniel Widdis Date: Mon, 17 Jan 2022 14:06:51 -0800 Subject: [PATCH 8/8] Include warning for sparse SVN history --- .../license/svn/SVNPropertiesProvider.java | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/license-maven-plugin-svn/src/main/java/com/mycila/maven/plugin/license/svn/SVNPropertiesProvider.java b/license-maven-plugin-svn/src/main/java/com/mycila/maven/plugin/license/svn/SVNPropertiesProvider.java index 33aae0e3e..a06710719 100755 --- a/license-maven-plugin-svn/src/main/java/com/mycila/maven/plugin/license/svn/SVNPropertiesProvider.java +++ b/license-maven-plugin-svn/src/main/java/com/mycila/maven/plugin/license/svn/SVNPropertiesProvider.java @@ -20,10 +20,12 @@ import com.mycila.maven.plugin.license.PropertiesProvider; import com.mycila.maven.plugin.license.document.Document; import org.tmatesoft.svn.core.ISVNLogEntryHandler; +import org.tmatesoft.svn.core.SVNDepth; import org.tmatesoft.svn.core.SVNException; import org.tmatesoft.svn.core.SVNLogEntry; import org.tmatesoft.svn.core.internal.wc.DefaultSVNOptions; import org.tmatesoft.svn.core.wc.SVNClientManager; +import org.tmatesoft.svn.core.wc.SVNInfo; import org.tmatesoft.svn.core.wc.SVNRevision; import java.io.File; @@ -58,6 +60,8 @@ protected SimpleDateFormat initialValue() { public static final String SVN_SERVER_ID_PLUGIN_KEY = "license.svn.serverId"; + private volatile boolean initialized = false; + /** * Provides information on the given document. The information is put in the * returned map using: SVN_COPYRIGHT_LAST_YEAR_KEY the year of the latest @@ -104,6 +108,20 @@ public void handleLogEntry(SVNLogEntry logEntry) throws SVNException { }; try { + if (!this.initialized) { + synchronized (this) { + if (!this.initialized) { + this.initialized = true; + // One-time warning for shallow repo + if (mojo.warnIfShallow) { + SVNInfo info = clientManager.getWCClient().doInfo(documentFile, SVNRevision.HEAD); + if (info.getDepth() != SVNDepth.INFINITY) { + mojo.warn("Sparse svn repository detected. Year and author property values may not be accurate."); + } + } + } + } + } clientManager.getLogClient().doLog(new File[]{documentFile}, SVNRevision.HEAD, SVNRevision.create(0), true, true, 1, lastChangeDateLogEntryHandler); } catch (SVNException ex) { IllegalStateException ise = new IllegalStateException("cannot query SVN latest date information for file: " + documentFile, ex);