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 the ability to populate header years from git history. #604

Merged
merged 4 commits into from
Jun 5, 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
2 changes: 2 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ This document is intended for Spotless developers.
We adhere to the [keepachangelog](https://keepachangelog.com/en/1.0.0/) format (starting after version `1.27.0`).

## [Unreleased]
### Added
* `LicenseHeaderStep.setLicenseHeaderYearsFromGitHistory`, which does an expensive search through git history to determine the oldest and newest commits for each file, and uses that to determine license header years. ([#604](https://github.com/diffplug/spotless/pull/604))

## [1.33.1] - 2020-06-04
* We are now running CI on windows. ([#596](https://github.com/diffplug/spotless/pull/596))
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 @@ -15,7 +15,10 @@
*/
package com.diffplug.spotless.generic;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.Serializable;
import java.nio.charset.Charset;
import java.nio.file.Files;
Expand All @@ -33,6 +36,8 @@
import com.diffplug.spotless.LineEnding;
import com.diffplug.spotless.SerializableFileFilter;

import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;

/** Prefixes a license header before the package statement. */
public final class LicenseHeaderStep implements Serializable {
private static final long serialVersionUID = 1L;
Expand Down Expand Up @@ -207,4 +212,74 @@ public String format(String raw) {
}
}
}

private static final String spotlessSetLicenseHeaderYearsFromGitHistory = "spotlessSetLicenseHeaderYearsFromGitHistory";

public static final String FLAG_SET_LICENSE_HEADER_YEARS_FROM_GIT_HISTORY() {
return spotlessSetLicenseHeaderYearsFromGitHistory;
}

/** Sets copyright years on the given file by finding the oldest and most recent commits throughout git history. */
public String setLicenseHeaderYearsFromGitHistory(String raw, File file) throws IOException {
if (yearToday == null) {
return raw;
}
Matcher contentMatcher = delimiterPattern.matcher(raw);
if (!contentMatcher.find()) {
throw new IllegalArgumentException("Unable to find delimiter regex " + delimiterPattern);
}

String oldYear;
try {
oldYear = parseYear("git log --follow --find-renames=40% --diff-filter=A", file);
} catch (IllegalArgumentException e) {
// Ideally, git log would always find the commit where it was added.
// For some reason, that is sometimes not possible - in that case,
// we'll settle for just the most recent, even if it was just a modification.
oldYear = parseYear("git log --follow --find-renames=40% --reverse", file);
}
String newYear = parseYear("git log --max-count=1", file);
String yearRange;
if (oldYear.equals(newYear)) {
yearRange = oldYear;
} else {
yearRange = oldYear + yearSepOrFull + newYear;
}
return beforeYear + yearRange + afterYear + raw.substring(contentMatcher.start());
}

private static String parseYear(String cmd, File file) throws IOException {
String fullCmd = cmd + " " + file.getAbsolutePath();
ProcessBuilder builder = new ProcessBuilder().directory(file.getParentFile());
if (LineEnding.nativeIsWin()) {
builder.command("cmd", "/c", fullCmd);
} else {
builder.command("bash", "-c", fullCmd);
}
Process process = builder.start();
String output = drain(process.getInputStream());
String error = drain(process.getErrorStream());
if (!error.isEmpty()) {
throw new IllegalArgumentException("Error for command '" + fullCmd + "':\n" + error);
}
Matcher matcher = FIND_YEAR.matcher(output);
if (matcher.find()) {
return matcher.group(1);
} else {
throw new IllegalArgumentException("Unable to parse date from command '" + fullCmd + "':\n" + output);
}
}

private static final Pattern FIND_YEAR = Pattern.compile("Date: .* ([0-9]{4}) ");

@SuppressFBWarnings("DM_DEFAULT_ENCODING")
private static String drain(InputStream stream) throws IOException {
ByteArrayOutputStream output = new ByteArrayOutputStream();
byte[] buf = new byte[1024];
int numRead;
while ((numRead = stream.read(buf)) != -1) {
output.write(buf, 0, numRead);
}
return new String(output.toByteArray());
}
}
1 change: 1 addition & 0 deletions plugin-gradle/CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ We adhere to the [keepachangelog](https://keepachangelog.com/en/1.0.0/) format (
* If you are integrating with git, try the much easier (and faster) [`ratchetFrom 'origin/master'`](https://github.com/diffplug/spotless/tree/master/plugin-gradle#ratchet)
* If neither of these work for you, let us know in [this PR](https://github.com/diffplug/spotless/pull/602).
### Added
* If you specify `-PspotlessSetLicenseHeaderYearsFromGitHistory=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. ([#604](https://github.com/diffplug/spotless/pull/604))
* (spotless devs only) if you specify `-PspotlessModern=true` Spotless will run the in-progress Gradle `5.4+` code. The `modernTest` build task runs our test suite in this way. It will be weeks/months before this is recommended for end-users. ([#598](https://github.com/diffplug/spotless/pull/598))

## [4.2.1] - 2020-06-04
Expand Down
14 changes: 9 additions & 5 deletions plugin-gradle/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,9 @@ output = [
-->
[![Gradle plugin](https://img.shields.io/badge/plugins.gradle.org-com.diffplug.gradle.spotless-blue.svg)](https://plugins.gradle.org/plugin/com.diffplug.gradle.spotless)
[![Maven central](https://img.shields.io/badge/mavencentral-yes-blue.svg)](https://search.maven.org/#search%7Cgav%7C1%7Cg%3A%22com.diffplug.spotless%22%20AND%20a%3A%22spotless-plugin-gradle%22)
[![Javadoc](https://img.shields.io/badge/javadoc-yes-blue.svg)](https://javadoc.io/doc/com.diffplug.spotless/spotless-plugin-gradle/4.2.0/index.html)
[![Javadoc](https://img.shields.io/badge/javadoc-yes-blue.svg)](https://javadoc.io/doc/com.diffplug.spotless/spotless-plugin-gradle/4.2.1/index.html)
[![License Apache](https://img.shields.io/badge/license-apache-blue.svg)](https://tldrlegal.com/license/apache-license-2.0-(apache-2.0))
[![Changelog](https://img.shields.io/badge/changelog-4.2.0-blue.svg)](CHANGES.md)
[![Changelog](https://img.shields.io/badge/changelog-4.2.1-blue.svg)](CHANGES.md)

[![Circle CI](https://circleci.com/gh/diffplug/spotless/tree/master.svg?style=shield)](https://circleci.com/gh/diffplug/spotless/tree/master)
[![Live chat](https://img.shields.io/badge/gitter-chat-brightgreen.svg)](https://gitter.im/diffplug/spotless)
Expand Down Expand Up @@ -80,7 +80,7 @@ spotless {
}
```

Spotless can check and apply formatting to any plain-text file, using simple rules ([javadoc](https://javadoc.io/static/com.diffplug.spotless/spotless-plugin-gradle/4.2.0/com/diffplug/gradle/spotless/FormatExtension.html)) like those above. It also supports more powerful formatters:
Spotless can check and apply formatting to any plain-text file, using simple rules ([javadoc](https://javadoc.io/static/com.diffplug.spotless/spotless-plugin-gradle/4.2.1/com/diffplug/gradle/spotless/FormatExtension.html)) like those above. It also supports more powerful formatters:

* Eclipse's [CDT](#eclipse-cdt) C/C++ code formatter
* Eclipse's java code formatter (including style and import ordering)
Expand Down Expand Up @@ -551,7 +551,11 @@ Once a file's license header has a valid year, whether it is a year (`2020`) or
* `2017` -> `2017-2020`
* `2017-2019` -> `2017-2020`

See the [javadoc](https://javadoc.io/static/com.diffplug.spotless/spotless-plugin-gradle/4.2.0/com/diffplug/gradle/spotless/FormatExtension.LicenseHeaderConfig.html) for a complete listing of options.
See the [javadoc](https://javadoc.io/static/com.diffplug.spotless/spotless-plugin-gradle/4.2.1/com/diffplug/gradle/spotless/FormatExtension.LicenseHeaderConfig.html) for a complete listing of options.

### 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 `-PspotlessSetLicenseHeaderYearsFromGitHistory=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="custom"></a>

Expand Down Expand Up @@ -599,7 +603,7 @@ spotless {
}
```

If you use `custom` or `customLazy`, you might want to take a look at [this javadoc](https://javadoc.io/static/com.diffplug.spotless/spotless-plugin-gradle/4.2.0/com/diffplug/gradle/spotless/FormatExtension.html#bumpThisNumberIfACustomStepChanges-int-) for a big performance win.
If you use `custom` or `customLazy`, you might want to take a look at [this javadoc](https://javadoc.io/static/com.diffplug.spotless/spotless-plugin-gradle/4.2.1/com/diffplug/gradle/spotless/FormatExtension.html#bumpThisNumberIfACustomStepChanges-int-) for a big performance win.

See [`JavaExtension.java`](src/main/java/com/diffplug/gradle/spotless/JavaExtension.java) if you'd like to see how a language-specific set of custom rules is implemented. We'd love PR's which add support for other languages.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -461,11 +461,29 @@ public LicenseHeaderConfig updateYearWithLatest(boolean updateYearWithLatest) {
protected abstract String licenseHeader() throws IOException;

FormatterStep createStep() {
return FormatterStep.createLazy(LicenseHeaderStep.name(), () -> {
// by default, we should update the year if the user is using ratchetFrom
boolean updateYear = updateYearWithLatest == null ? FormatExtension.this.spotless.getRatchetFrom() != null : updateYearWithLatest;
return new LicenseHeaderStep(licenseHeader(), delimiter, yearSeparator, updateYear);
}, step -> step::format);
if ("true".equals(spotless.project.findProperty(LicenseHeaderStep.FLAG_SET_LICENSE_HEADER_YEARS_FROM_GIT_HISTORY()))) {
return FormatterStep.createNeverUpToDateLazy(LicenseHeaderStep.name(), () -> {
boolean updateYear = false; // doesn't matter
LicenseHeaderStep step = new LicenseHeaderStep(licenseHeader(), delimiter, yearSeparator, updateYear);
return new FormatterFunc() {
@Override
public String apply(String input, File source) throws Exception {
return step.setLicenseHeaderYearsFromGitHistory(input, source);
}

@Override
public String apply(String input) throws Exception {
throw new UnsupportedOperationException();
}
};
});
} else {
return FormatterStep.createLazy(LicenseHeaderStep.name(), () -> {
// by default, we should update the year if the user is using ratchetFrom
boolean updateYear = updateYearWithLatest == null ? FormatExtension.this.spotless.getRatchetFrom() != null : updateYearWithLatest;
return new LicenseHeaderStep(licenseHeader(), delimiter, yearSeparator, updateYear);
}, step -> step::format);
}
}
}

Expand Down