Skip to content

Commit 65efa7d

Browse files
authored
Fix #2940 - Parse standard git year output in LicenseHeaderStep (#2961)
2 parents 2101fbc + 74091b3 commit 65efa7d

5 files changed

Lines changed: 52 additions & 8 deletions

File tree

CHANGES.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ We adhere to the [keepachangelog](https://keepachangelog.com/en/1.0.0/) format (
1515
### Fixed
1616
- `FenceStep.preserveWithin` now forwards lints from nested steps while still suppressing lints inside preserved blocks. ([#2962](https://github.com/diffplug/spotless/pull/2962))
1717
- Support `ktfmt` 0.63 and use its new builder API for formatting options to better avoid future breaking changes.
18+
- Parse standard git year output in LicenseHeaderStep. ([#2940](https://github.com/diffplug/spotless/issues/2940))
19+
1820
### Changes
1921
- Bump default `greclipse` version to latest `4.35` -> `4.39`. ([#2924](https://github.com/diffplug/spotless/pull/2924))
2022

lib/src/main/java/com/diffplug/spotless/generic/LicenseHeaderStep.java

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -349,7 +349,7 @@ private String replaceYear(String raw) {
349349
}
350350
}
351351

352-
private static final Pattern YYYY = Pattern.compile("[0-9]{4}");
352+
private static final Pattern YYYY = Pattern.compile("\\d{4}");
353353

354354
/** Calculates the year to inject. */
355355
private String calculateYearExact(String parsedYear) {
@@ -408,6 +408,13 @@ private String calculateYearBySearching(String content) {
408408
}
409409
}
410410

411+
// Default git log options to find the commit year.
412+
// --follow - Continue listing the history of a file beyond renames.
413+
// --find-renames=40% - Detect renames with a similarity threshold of 40%.
414+
// --format=%cd - Output the committer date using the format specified by --date.
415+
// --date=format:%Y - Format the date as a 4-digit year only.
416+
private static final List<String> GIT_LOG_DEFAULT_OPTIONS = Arrays.asList("--follow", "--find-renames=40%", "--format=%cd", "--date=format:%Y");
417+
411418
/** Sets copyright years on the given file by finding the oldest and most recent commits throughout git history. */
412419
private String setLicenseHeaderYearsFromGitHistory(String raw, File file) throws IOException {
413420
if (yearToday == null) {
@@ -420,14 +427,21 @@ private String setLicenseHeaderYearsFromGitHistory(String raw, File file) throws
420427

421428
String oldYear;
422429
try {
423-
oldYear = parseYear(Arrays.asList("git", "log", "--follow", "--find-renames=40%", "--diff-filter=A"), file);
430+
List<String> cmd = new ArrayList<>(Arrays.asList("git", "log", "--diff-filter=A"));
431+
cmd.addAll(GIT_LOG_DEFAULT_OPTIONS);
432+
oldYear = parseYear(cmd, file);
424433
} catch (IllegalArgumentException e) {
425434
// Ideally, git log would always find the commit where it was added.
426435
// For some reason, that is sometimes not possible - in that case,
427436
// we'll settle for just the most recent, even if it was just a modification.
428-
oldYear = parseYear(Arrays.asList("git", "log", "--follow", "--find-renames=40%", "--reverse"), file);
437+
List<String> cmd = new ArrayList<>(Arrays.asList("git", "log", "--reverse"));
438+
cmd.addAll(GIT_LOG_DEFAULT_OPTIONS);
439+
oldYear = parseYear(cmd, file);
429440
}
430-
String newYear = parseYear(Arrays.asList("git", "log", "--max-count=1"), file);
441+
442+
List<String> newYearCmd = new ArrayList<>(Arrays.asList("git", "log", "--max-count=1"));
443+
newYearCmd.addAll(GIT_LOG_DEFAULT_OPTIONS);
444+
String newYear = parseYear(newYearCmd, file);
431445
String yearRange;
432446
if (oldYear.equals(newYear)) {
433447
yearRange = oldYear;
@@ -462,15 +476,15 @@ private static String parseYear(List<String> cmd, File file) throws IOException
462476
if (!error.isEmpty()) {
463477
throw new IllegalArgumentException("Error for command '" + fullCmd + "':\n" + error);
464478
}
465-
Matcher matcher = FIND_YEAR.matcher(output);
479+
Matcher matcher = FIND_YEAR.matcher(output.trim());
466480
if (matcher.find()) {
467481
return matcher.group(1);
468482
} else {
469483
throw new IllegalArgumentException("Unable to parse date from command '" + fullCmd + "':\n" + output);
470484
}
471485
}
472486

473-
private static final Pattern FIND_YEAR = Pattern.compile("Date: .* ([0-9]{4}) ");
487+
private static final Pattern FIND_YEAR = Pattern.compile("^(\\d{4})?");
474488

475489
@SuppressFBWarnings("DM_DEFAULT_ENCODING")
476490
private static String drain(InputStream stream) throws IOException {

plugin-gradle/CHANGES.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ We adhere to the [keepachangelog](https://keepachangelog.com/en/1.0.0/) format (
99
### Fixed
1010
- `toggleOffOn` no longer disables lint-only steps such as `forbidWildcardImports`. ([#2962](https://github.com/diffplug/spotless/pull/2962))
1111
- Prevent build caches from interfering when executing under the `-PspotlessIdeHook` mode. ([#2365](https://github.com/diffplug/spotless/issues/2365))
12+
- Parse standard git year output in LicenseHeaderStep. ([#2940](https://github.com/diffplug/spotless/issues/2940))
13+
1214
### Changes
1315
- Bump default `greclipse` version to latest `4.35` -> `4.39`. ([#2924](https://github.com/diffplug/spotless/pull/2924))
1416

plugin-maven/CHANGES.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@ We adhere to the [keepachangelog](https://keepachangelog.com/en/1.0.0/) format (
44

55
## [Unreleased]
66
### Fixed
7+
- Parse standard git year output in LicenseHeaderStep. ([#2940](https://github.com/diffplug/spotless/issues/2940))
78
- `<toggleOffOn>` no longer disables lint-only steps such as `<forbidWildcardImports>`. ([#2962](https://github.com/diffplug/spotless/pull/2962))
8-
99
### Added
1010
- Add support for AsciiDoc formatting via `adocfmt`. ([#2960](https://github.com/diffplug/spotless/pull/2960))
1111

testlib/src/test/java/com/diffplug/spotless/generic/LicenseHeaderStepTest.java

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2016-2025 DiffPlug
2+
* Copyright 2016-2026 DiffPlug
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -21,9 +21,11 @@
2121
import java.time.YearMonth;
2222
import java.time.ZoneOffset;
2323

24+
import org.eclipse.jgit.api.Git;
2425
import org.junit.jupiter.api.Assertions;
2526
import org.junit.jupiter.api.Test;
2627

28+
import com.diffplug.spotless.ClearGitConfig;
2729
import com.diffplug.spotless.FormatterStep;
2830
import com.diffplug.spotless.ResourceHarness;
2931
import com.diffplug.spotless.SerializableEqualityTester;
@@ -309,4 +311,28 @@ void moduleInfo() throws Throwable {
309311
StepHarness.forStep(step)
310312
.test(ResourceHarness.getTestResource("license/module-info.test"), header + ResourceHarness.getTestResource("license/module-info.test"));
311313
}
314+
315+
@Test
316+
@ClearGitConfig
317+
void should_setFromGit_year_when_singleCommit() throws Exception {
318+
try (Git git = Git.init().setDirectory(rootFolder()).call()) {
319+
// Single commit: added once, so old/new year must be equal.
320+
setFile("Foo.java").toContent("package foo; // v1\n");
321+
git.add()
322+
.addFilepattern("Foo.java")
323+
.call();
324+
git.commit()
325+
.setMessage("add")
326+
.setAuthor("Test User", "test@example.com")
327+
.setCommitter("Test User", "test@example.com")
328+
.call();
329+
}
330+
331+
FormatterStep step = LicenseHeaderStep.headerDelimiter(header(HEADER_WITH_$YEAR), PACKAGE_)
332+
.withYearMode(YearMode.SET_FROM_GIT)
333+
.build();
334+
335+
StepHarnessWithFile.forStep(this, step)
336+
.test("Foo.java", getTestResource(FILE_NO_LICENSE), hasHeaderYear(currentYear()));
337+
}
312338
}

0 commit comments

Comments
 (0)