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 maven support for git-based copyright headers #626

Merged
merged 8 commits into from
Jun 30, 2020
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
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
1 change: 1 addition & 0 deletions plugin-maven/CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
30 changes: 30 additions & 0 deletions plugin-maven/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

<a name="license-header"></a>

## 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
<format> <!-- or java, scala, kotlin, etc. -->
<licenseHeader>
<!-- Specify either content or file, but not both -->
<content>/* Licensed under Apache-2.0 */</content>
<file>${basedir}/license-header</file>
<!-- content until first occurrence of the delimiter regex will be interpreted as header section -->
<!-- for language-specific formats (java, etc.) this is optional, and the regex is provided automatically. -->
<delimiter>#</delimiter>
</licenseHeader>
```

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.

<a name="invisible"></a>

## Line endings and encodings (invisible stuff)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -120,6 +121,9 @@ public abstract class AbstractSpotlessMojo extends AbstractMojo {
@Parameter(property = "spotlessFiles")
private String filePatterns;

@Parameter(property = LicenseHeaderStep.spotlessSetLicenseHeaderYearsFromGitHistory)
private String setLicenseHeaderYearsFromGitHistory;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can this field/parameter be boolean instead of string?
I think this could simplify type signatures (boolean instead of Optional<String>) and the check in LicenseHeader

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think there's a decent chance that we'll allow values besides "true" in the future. For example, you might want to exclude certain commits, say formatting commits, from the git history that you care about, and this is the pipe that we'll put that through.


protected abstract void process(Iterable<File> files, Formatter formatter) throws MojoExecutionException;

@Override
Expand Down Expand Up @@ -189,7 +193,7 @@ private FormatterConfig getFormatterConfig() {
Provisioner provisioner = MavenProvisioner.create(resolver);
List<FormatterStepFactory> 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() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,15 +32,17 @@ public class FormatterConfig {
private final Provisioner provisioner;
private final FileLocator fileLocator;
private final List<FormatterStepFactory> globalStepFactories;
private final Optional<String> spotlessSetLicenseHeaderYearsFromGitHistory;

public FormatterConfig(File baseDir, String encoding, LineEnding lineEndings, Optional<String> ratchetFrom, Provisioner provisioner,
FileLocator fileLocator, List<FormatterStepFactory> globalStepFactories) {
FileLocator fileLocator, List<FormatterStepFactory> globalStepFactories, Optional<String> 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() {
Expand All @@ -63,6 +65,10 @@ public List<FormatterStepFactory> getGlobalStepFactories() {
return unmodifiableList(globalStepFactories);
}

public Optional<String> getSpotlessSetLicenseHeaderYearsFromGitHistory() {
return spotlessSetLicenseHeaderYearsFromGitHistory;
}

public FileLocator getFileLocator() {
return fileLocator;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ Optional<String> 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<FormatterStepFactory> gatherStepFactories(List<FormatterStepFactory> allGlobal, List<FormatterStepFactory> allConfigured) {
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -16,21 +16,26 @@
package com.diffplug.spotless.maven;

import java.nio.charset.Charset;
import java.util.Optional;

import com.diffplug.spotless.Provisioner;

public class FormatterStepConfig {

private final Charset encoding;
private final String licenseHeaderDelimiter;
private final Optional<String> ratchetFrom;
private final Provisioner provisioner;
private final FileLocator fileLocator;
private final Optional<String> spotlessSetLicenseHeaderYearsFromGitHistory;

public FormatterStepConfig(Charset encoding, String licenseHeaderDelimiter, Provisioner provisioner, FileLocator fileLocator) {
public FormatterStepConfig(Charset encoding, String licenseHeaderDelimiter, Optional<String> ratchetFrom, Provisioner provisioner, FileLocator fileLocator, Optional<String> spotlessSetLicenseHeaderYearsFromGitHistory) {
this.encoding = encoding;
this.licenseHeaderDelimiter = licenseHeaderDelimiter;
this.ratchetFrom = ratchetFrom;
this.provisioner = provisioner;
this.fileLocator = fileLocator;
this.spotlessSetLicenseHeaderYearsFromGitHistory = spotlessSetLicenseHeaderYearsFromGitHistory;
}

public Charset getEncoding() {
Expand All @@ -41,11 +46,19 @@ public String getLicenseHeaderDelimiter() {
return licenseHeaderDelimiter;
}

public Optional<String> getRatchetFrom() {
return ratchetFrom;
}

public Provisioner getProvisioner() {
return provisioner;
}

public FileLocator getFileLocator() {
return fileLocator;
}

public Optional<String> spotlessSetLicenseHeaderYearsFromGitHistory() {
return spotlessSetLicenseHeaderYearsFromGitHistory;
}
}
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -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;
Expand All @@ -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());
}
}
}