diff --git a/lib/src/main/java/com/diffplug/spotless/generic/LicenseHeaderStep.java b/lib/src/main/java/com/diffplug/spotless/generic/LicenseHeaderStep.java index 0824046366..47d2289905 100644 --- a/lib/src/main/java/com/diffplug/spotless/generic/LicenseHeaderStep.java +++ b/lib/src/main/java/com/diffplug/spotless/generic/LicenseHeaderStep.java @@ -213,7 +213,7 @@ public String format(String raw) { } } - private static final String spotlessSetLicenseHeaderYearsFromGitHistory = "spotlessSetLicenseHeaderYearsFromGitHistory"; + public static final String spotlessSetLicenseHeaderYearsFromGitHistory = "spotlessSetLicenseHeaderYearsFromGitHistory"; public static final String FLAG_SET_LICENSE_HEADER_YEARS_FROM_GIT_HISTORY() { return spotlessSetLicenseHeaderYearsFromGitHistory; diff --git a/plugin-maven/CHANGES.md b/plugin-maven/CHANGES.md index 8e999a4897..17bfdf80c4 100644 --- a/plugin-maven/CHANGES.md +++ b/plugin-maven/CHANGES.md @@ -6,6 +6,7 @@ We adhere to the [keepachangelog](https://keepachangelog.com/en/1.0.0/) format ( ### Added * You can now ratchet a project's style by limiting Spotless only to files which have changed since a given [git reference](https://javadoc.io/static/org.eclipse.jgit/org.eclipse.jgit/5.6.1.202002131546-r/org/eclipse/jgit/lib/Repository.html#resolve-java.lang.String-), e.g. `ratchetFrom 'origin/main'`. ([#590](https://github.com/diffplug/spotless/pull/590)) * Huge speed improvement for multi-module projects thanks to improved cross-project classloader caching ([#571](https://github.com/diffplug/spotless/pull/571), fixes [#559](https://github.com/diffplug/spotless/issues/559)). +* If you specify `-DspotlessSetLicenseHeaderYearsFromGitHistory=true`, Spotless will perform an expensive search through git history to determine the oldest and newest commits for each file, and uses that to determine license header years. ([#626](https://github.com/diffplug/spotless/pull/626)) * `prettier` will now autodetect the parser (and formatter) to use based on the filename, unless you override this using `config` or `configFile` with the option `parser` or `filepath` ([#620](https://github.com/diffplug/spotless/pull/620)). ## [1.31.3] - 2020-06-17 diff --git a/plugin-maven/README.md b/plugin-maven/README.md index bcba6f6e3b..d27620abf3 100644 --- a/plugin-maven/README.md +++ b/plugin-maven/README.md @@ -526,6 +526,36 @@ Unlike Eclipse, Spotless WTP ignores per default external URIs in schema locatio external entities. To allow the access of external URIs, set the property `resolveExternalURI` to true. + + +## License header + +Spotless can inject a license header into your files, including populating an accurate copyright header from today's date or from git history. + +```xml + + + + /* Licensed under Apache-2.0 */ + ${basedir}/license-header + + + # + +``` + +If the license header (specified with `content` or `file`) contains `$YEAR` or `$today.year`, then that token will be replaced with the current 4-digit year. For example, if Spotless is launched in 2020, then `/* Licensed under Apache-2.0 $YEAR. */` will produce `/* Licensed under Apache-2.0 2020. */` + +Once a file's license header has a valid year, whether it is a year (`2020`) or a year range (`2017-2020`), it will not be changed. If you want the date to be updated when it changes, enable the [`ratchetFrom` functionality](#ratchet), and the year will be automatically set to today's year according to the following table (assuming the current year is 2020): + +* No license header -> `2020` +* `2017` -> `2017-2020` +* `2017-2019` -> `2017-2020` + +### Retroactively populating year range from git history + +If your project has not been rigorous with copyright headers, and you'd like to use git history to repair this retroactively, you can do so with `-DspotlessSetLicenseHeaderYearsFromGitHistory=true`. When run in this mode, Spotless will do an expensive search through git history for each file, and set the copyright header based on the oldest and youngest commits for that file. This is intended to be a one-off sort of thing. + ## Line endings and encodings (invisible stuff) diff --git a/plugin-maven/src/main/java/com/diffplug/spotless/maven/AbstractSpotlessMojo.java b/plugin-maven/src/main/java/com/diffplug/spotless/maven/AbstractSpotlessMojo.java index d3f410a754..0ce5cbae2d 100644 --- a/plugin-maven/src/main/java/com/diffplug/spotless/maven/AbstractSpotlessMojo.java +++ b/plugin-maven/src/main/java/com/diffplug/spotless/maven/AbstractSpotlessMojo.java @@ -46,6 +46,7 @@ import com.diffplug.spotless.Formatter; import com.diffplug.spotless.LineEnding; import com.diffplug.spotless.Provisioner; +import com.diffplug.spotless.generic.LicenseHeaderStep; import com.diffplug.spotless.maven.cpp.Cpp; import com.diffplug.spotless.maven.generic.Format; import com.diffplug.spotless.maven.generic.LicenseHeader; @@ -120,6 +121,9 @@ public abstract class AbstractSpotlessMojo extends AbstractMojo { @Parameter(property = "spotlessFiles") private String filePatterns; + @Parameter(property = LicenseHeaderStep.spotlessSetLicenseHeaderYearsFromGitHistory) + private String setLicenseHeaderYearsFromGitHistory; + protected abstract void process(Iterable files, Formatter formatter) throws MojoExecutionException; @Override @@ -189,7 +193,7 @@ private FormatterConfig getFormatterConfig() { Provisioner provisioner = MavenProvisioner.create(resolver); List formatterStepFactories = getFormatterStepFactories(); FileLocator fileLocator = getFileLocator(); - return new FormatterConfig(baseDir, encoding, lineEndings, Optional.ofNullable(ratchetFrom), provisioner, fileLocator, formatterStepFactories); + return new FormatterConfig(baseDir, encoding, lineEndings, Optional.ofNullable(ratchetFrom), provisioner, fileLocator, formatterStepFactories, Optional.ofNullable(setLicenseHeaderYearsFromGitHistory)); } private FileLocator getFileLocator() { diff --git a/plugin-maven/src/main/java/com/diffplug/spotless/maven/FormatterConfig.java b/plugin-maven/src/main/java/com/diffplug/spotless/maven/FormatterConfig.java index 6127d93d58..52f2a871f3 100644 --- a/plugin-maven/src/main/java/com/diffplug/spotless/maven/FormatterConfig.java +++ b/plugin-maven/src/main/java/com/diffplug/spotless/maven/FormatterConfig.java @@ -32,15 +32,17 @@ public class FormatterConfig { private final Provisioner provisioner; private final FileLocator fileLocator; private final List globalStepFactories; + private final Optional spotlessSetLicenseHeaderYearsFromGitHistory; public FormatterConfig(File baseDir, String encoding, LineEnding lineEndings, Optional ratchetFrom, Provisioner provisioner, - FileLocator fileLocator, List globalStepFactories) { + FileLocator fileLocator, List globalStepFactories, Optional spotlessSetLicenseHeaderYearsFromGitHistory) { this.encoding = encoding; this.lineEndings = lineEndings; this.ratchetFrom = ratchetFrom; this.provisioner = provisioner; this.fileLocator = fileLocator; this.globalStepFactories = globalStepFactories; + this.spotlessSetLicenseHeaderYearsFromGitHistory = spotlessSetLicenseHeaderYearsFromGitHistory; } public String getEncoding() { @@ -63,6 +65,10 @@ public List getGlobalStepFactories() { return unmodifiableList(globalStepFactories); } + public Optional getSpotlessSetLicenseHeaderYearsFromGitHistory() { + return spotlessSetLicenseHeaderYearsFromGitHistory; + } + public FileLocator getFileLocator() { return fileLocator; } diff --git a/plugin-maven/src/main/java/com/diffplug/spotless/maven/FormatterFactory.java b/plugin-maven/src/main/java/com/diffplug/spotless/maven/FormatterFactory.java index bbed6ad1a4..d74634382c 100644 --- a/plugin-maven/src/main/java/com/diffplug/spotless/maven/FormatterFactory.java +++ b/plugin-maven/src/main/java/com/diffplug/spotless/maven/FormatterFactory.java @@ -144,7 +144,7 @@ Optional ratchetFrom(FormatterConfig config) { } private FormatterStepConfig stepConfig(Charset encoding, FormatterConfig config) { - return new FormatterStepConfig(encoding, licenseHeaderDelimiter(), config.getProvisioner(), config.getFileLocator()); + return new FormatterStepConfig(encoding, licenseHeaderDelimiter(), ratchetFrom(config), config.getProvisioner(), config.getFileLocator(), config.getSpotlessSetLicenseHeaderYearsFromGitHistory()); } private static List gatherStepFactories(List allGlobal, List allConfigured) { diff --git a/plugin-maven/src/main/java/com/diffplug/spotless/maven/FormatterStepConfig.java b/plugin-maven/src/main/java/com/diffplug/spotless/maven/FormatterStepConfig.java index dfe038a4c0..a1f2e52b41 100644 --- a/plugin-maven/src/main/java/com/diffplug/spotless/maven/FormatterStepConfig.java +++ b/plugin-maven/src/main/java/com/diffplug/spotless/maven/FormatterStepConfig.java @@ -1,5 +1,5 @@ /* - * Copyright 2016 DiffPlug + * Copyright 2016-2020 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,6 +16,7 @@ package com.diffplug.spotless.maven; import java.nio.charset.Charset; +import java.util.Optional; import com.diffplug.spotless.Provisioner; @@ -23,14 +24,18 @@ public class FormatterStepConfig { private final Charset encoding; private final String licenseHeaderDelimiter; + private final Optional ratchetFrom; private final Provisioner provisioner; private final FileLocator fileLocator; + private final Optional spotlessSetLicenseHeaderYearsFromGitHistory; - public FormatterStepConfig(Charset encoding, String licenseHeaderDelimiter, Provisioner provisioner, FileLocator fileLocator) { + public FormatterStepConfig(Charset encoding, String licenseHeaderDelimiter, Optional ratchetFrom, Provisioner provisioner, FileLocator fileLocator, Optional spotlessSetLicenseHeaderYearsFromGitHistory) { this.encoding = encoding; this.licenseHeaderDelimiter = licenseHeaderDelimiter; + this.ratchetFrom = ratchetFrom; this.provisioner = provisioner; this.fileLocator = fileLocator; + this.spotlessSetLicenseHeaderYearsFromGitHistory = spotlessSetLicenseHeaderYearsFromGitHistory; } public Charset getEncoding() { @@ -41,6 +46,10 @@ public String getLicenseHeaderDelimiter() { return licenseHeaderDelimiter; } + public Optional getRatchetFrom() { + return ratchetFrom; + } + public Provisioner getProvisioner() { return provisioner; } @@ -48,4 +57,8 @@ public Provisioner getProvisioner() { public FileLocator getFileLocator() { return fileLocator; } + + public Optional spotlessSetLicenseHeaderYearsFromGitHistory() { + return spotlessSetLicenseHeaderYearsFromGitHistory; + } } diff --git a/plugin-maven/src/main/java/com/diffplug/spotless/maven/generic/LicenseHeader.java b/plugin-maven/src/main/java/com/diffplug/spotless/maven/generic/LicenseHeader.java index 1ea925d5fe..dc7a200000 100644 --- a/plugin-maven/src/main/java/com/diffplug/spotless/maven/generic/LicenseHeader.java +++ b/plugin-maven/src/main/java/com/diffplug/spotless/maven/generic/LicenseHeader.java @@ -1,5 +1,5 @@ /* - * Copyright 2016 DiffPlug + * Copyright 2016-2020 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,9 +16,12 @@ package com.diffplug.spotless.maven.generic; import java.io.File; +import java.io.IOException; +import java.nio.file.Files; import org.apache.maven.plugins.annotations.Parameter; +import com.diffplug.spotless.FormatterFunc; import com.diffplug.spotless.FormatterStep; import com.diffplug.spotless.generic.LicenseHeaderStep; import com.diffplug.spotless.maven.FormatterStepConfig; @@ -41,24 +44,44 @@ public final FormatterStep newFormatterStep(FormatterStepConfig config) { if (delimiterString == null) { throw new IllegalArgumentException("You need to specify 'delimiter'."); } - if (file != null ^ content != null) { - FormatterStep step = file != null - ? createStepFromFile(config, delimiterString) - : createStepFromContent(delimiterString); + FormatterStep unfiltered; + if ("true".equals(config.spotlessSetLicenseHeaderYearsFromGitHistory().orElse(""))) { + unfiltered = FormatterStep.createNeverUpToDateLazy(LicenseHeaderStep.name(), () -> { + boolean updateYear = false; // doesn't matter + LicenseHeaderStep step = new LicenseHeaderStep(readFileOrContent(config), delimiterString, LicenseHeaderStep.defaultYearDelimiter(), updateYear); + return new FormatterFunc() { + @Override + public String apply(String input, File source) throws Exception { + return step.setLicenseHeaderYearsFromGitHistory(input, source); + } - return step.filterByFile(LicenseHeaderStep.unsupportedJvmFilesFilter()); + @Override + public String apply(String input) throws Exception { + throw new UnsupportedOperationException(); + } + }; + }); + } else { + unfiltered = FormatterStep.createLazy(LicenseHeaderStep.name(), () -> { + // by default, we should update the year if the user is using ratchetFrom + boolean updateYear = config.getRatchetFrom().isPresent(); + String header = readFileOrContent(config); + return new LicenseHeaderStep(header, delimiterString, LicenseHeaderStep.defaultYearDelimiter(), updateYear); + }, step -> step::format); + } + return unfiltered.filterByFile(LicenseHeaderStep.unsupportedJvmFilesFilter()); } else { throw new IllegalArgumentException("Must specify exactly one of 'file' or 'content'."); } } - private FormatterStep createStepFromFile(FormatterStepConfig config, String delimiterString) { - File licenseHeaderFile = config.getFileLocator().locateFile(file); - return LicenseHeaderStep.createFromFile(licenseHeaderFile, config.getEncoding(), delimiterString); - } - - private FormatterStep createStepFromContent(String delimiterString) { - return LicenseHeaderStep.createFromHeader(content, delimiterString); + private String readFileOrContent(FormatterStepConfig config) throws IOException { + if (content != null) { + return content; + } else { + byte[] raw = Files.readAllBytes(config.getFileLocator().locateFile(file).toPath()); + return new String(raw, config.getEncoding()); + } } }