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..2ebac08e5 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 = false; + /** * 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..e74b7948e --- /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("Solaris") || System.getProperty("os.name").startsWith("SunOS")); + 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; + } +}