Skip to content

feat: add header diff command to show header differences#276

Merged
wu-sheng merged 1 commit into
mainfrom
feat/header-diff
Jun 10, 2026
Merged

feat: add header diff command to show header differences#276
wu-sheng merged 1 commit into
mainfrom
feat/header-diff

Conversation

@wu-sheng

Copy link
Copy Markdown
Member

What's changed

Add a new subcommand license-eye header diff [paths...] (alias d) that shows where the license headers of the invalid files differ from the license configured in the config file, to help understand why header check fails, e.g. to spot a typo in an existing license header.

Resolves apache/skywalking#8002.

Example

For a test.go whose license header has a typo wwwhttp://www.apache.org/licenses/LICENSE-2.0 in the license URL (the example in the original issue), and a missing.py without any license header:

INFO Loading configuration from file: .licenserc.yaml
missing.py:
	[-licensed to the asf under one or more contributor license ... the specific language governing permissions and limitations under the license.-] ...
test.go:
	... copy of the license at [-http://www.apache.org/licenses/license-2.0-] {+wwwhttp://www.apache.org/licenses/license-2.0+} unless required by applicable law ... and limitations under the license. ...
INFO Totally checked 3 files, valid: 0, invalid: 2, ignored: 1, fixed: 0
ERROR one or more files does not have a valid license header
exit status 1

Design notes

  • The texts are compared in their normalized forms (comment markers stripped, whitespace flattened, case-insensitive, etc.), the same forms that header check compares in satisfy(), so every difference shown is a real cause of the check failure, instead of cosmetic noise such as comment style or line wrapping.
  • [-text-] marks text expected by the configured license but missing in the file, {+text+} marks text in the file but not expected by the configured license, and long runs of unchanged/missing words are collapsed into ....
  • A header that matches the license but sits too far into the file gets a precise message instead of a diff: license header is found at normalized offset N, which exceeds the license-location-threshold M, move it closer to the file start.
  • The word-level diff is built on github.com/sergi/go-diff, which was already in the module graph (an indirect dependency via go-git), so it is only promoted from indirect to direct in go.mod, no new dependency tree is introduced.
  • The GitHub Action passes mode straight through to header ${mode}, so mode: diff works out of the box; only the doc strings of the two action.yml files are updated.
  • The exit code is non-zero when any file has an invalid header, matching the header check semantics for CI usage.

🤖 Generated with Claude Code

The new command shows where the license headers of the invalid files
differ from the license configured in the config file, to help
understand why `header check` fails, e.g. to spot a typo in an
existing license header.

The texts are compared in the same normalized forms that the check
command compares, so every difference shown is a real cause of the
check failure.

Resolves apache/skywalking#8002

Co-Authored-By: Claude Fable 5 <[email protected]>
@wu-sheng wu-sheng added this to the 0.9.0 milestone Jun 10, 2026

@kezhenxu94 kezhenxu94 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Basically LGTM. One doubt, when there are regex patterns in the configured license pattern, are they printed as diff? Like if I set license pattern OK for skywalking-.* and the actual content is NK for skywalking-eyes, will the skywalking-eyes be a diff?

@wu-sheng

Copy link
Copy Markdown
Member Author

One doubt, when there are regex patterns in the configured license pattern, are they printed as diff?

No — the regex pattern is never printed in the diff. I validated with your exact scenario. The diff is always computed against the normalized license content (license.content or the spdx-id template), never against license.pattern; the pattern is only used (unchanged) to decide validity, same as in check:

  1. File matches the pattern (OK for skywalking-eyes vs pattern OK for skywalking-.*): the file is valid, so no diff is printed at all.

  2. File matches neither (NK for skywalking-eyes, with content OK for skywalking-team + the pattern above):

    fails_both.go:
    	[-ok-] {+nk+} for [-skywalking-team-] ...
    

    The diff is against the configured content, so regex fragments like .* can never appear in the output.

  3. Pattern-only config (no content/spdx-id): rather than producing a meaningless diff against regex source text, each failing file reports:

    ERROR no license content configured (spdx-id or content) to diff against
    

This is also covered in code: DiffFile uses config.NormalizedLicense() as the expected text and only passes config.NormalizedPattern() into the same satisfy() used by check to short-circuit valid files.

@wu-sheng wu-sheng merged commit f4b890e into main Jun 10, 2026
2 checks passed
@wu-sheng wu-sheng deleted the feat/header-diff branch June 10, 2026 12:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[INFRA/EYE] Support to show differences between file's header and license-eye's pattern content

2 participants