-
Notifications
You must be signed in to change notification settings - Fork 22
Update feature added for headers command. #181
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
Changes from 9 commits
a9cf724
85701d1
23c1a5b
8d9e651
4f00f8a
ca5b9f9
29b6786
55011d0
a5c98c7
df33765
6eaaa52
43b901c
10b8e97
35676e0
8c3dad3
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -3,8 +3,11 @@ | |||||||||
| This repo provides utilities for managing copyright headers and license files | ||||||||||
| across many repos at scale. | ||||||||||
|
|
||||||||||
| You can use it to add or validate copyright headers on source code files, add a | ||||||||||
| LICENSE file to a repo, report on what licenses repos are using, and more. | ||||||||||
| Features: | ||||||||||
| - Add or validate copyright headers on source code files | ||||||||||
| - Add and/or manage LICENSE files with git-aware copyright year detection | ||||||||||
| - Report on licenses used across multiple repositories | ||||||||||
| - Automate compliance checks in CI/CD pipelines | ||||||||||
|
|
||||||||||
| ## Getting Started | ||||||||||
|
|
||||||||||
|
|
@@ -33,7 +36,7 @@ Usage: | |||||||||
| copywrite [command] | ||||||||||
|
|
||||||||||
| Common Commands: | ||||||||||
| headers Adds missing copyright headers to all source code files | ||||||||||
| headers Adds missing copyright headers and updates existing headers' year information. | ||||||||||
| init Generates a .copywrite.hcl config for a new project | ||||||||||
| license Validates that a LICENSE file is present and remediates any issues if found | ||||||||||
|
|
||||||||||
|
|
@@ -62,8 +65,18 @@ scan all files in your repo and copyright headers to any that are missing: | |||||||||
| copywrite headers --spdx "MPL-2.0" | ||||||||||
| ``` | ||||||||||
|
|
||||||||||
| You may omit the `--spdx` flag if you add a `.copywrite.hcl` config, as outlined | ||||||||||
| [here](#config-structure). | ||||||||||
| The `copywrite license` command validates and manages LICENSE files with git-aware copyright years: | ||||||||||
|
|
||||||||||
| ```sh | ||||||||||
| copywrite license --spdx "MPL-2.0" | ||||||||||
| ``` | ||||||||||
|
|
||||||||||
| **Copyright Year Behavior:** | ||||||||||
| - **Start Year**: Auto-detected from config file and if not found defaults to repository's first commit | ||||||||||
| - **End Year**: Set to current year when an update is triggered (git history only determines if update is needed) | ||||||||||
| - **Update Trigger**: Git detects if file was modified since the copyright end year | ||||||||||
|
|
||||||||||
| You may omit the `--spdx` flag if you add a `.copywrite.hcl` config, as outlined [here](#config-structure). | ||||||||||
|
|
||||||||||
| ### `--plan` Flag | ||||||||||
|
|
||||||||||
|
|
@@ -72,6 +85,23 @@ performs a dry-run and will outline what changes would be made. This flag also | |||||||||
| returns a non-zero exit code if any changes are needed. As such, it can be used | ||||||||||
| to validate if a repo is in compliance or not. | ||||||||||
|
|
||||||||||
| ## Technical Details | ||||||||||
|
|
||||||||||
| ### Copyright Year Logic | ||||||||||
|
|
||||||||||
| **Source File Headers:** | ||||||||||
| - End year: Set to current year when file is modified | ||||||||||
| - Git history determines if update is needed (compares file's last commit year to copyright end year) | ||||||||||
| - When triggered, end year updates to current year | ||||||||||
|
|
||||||||||
| **LICENSE Files:** | ||||||||||
| - End year: Set to current year when any project file is modified | ||||||||||
| - Git history determines if update is needed (compares repo's last commit year to copyright end year) | ||||||||||
| - When triggered, end year updates to current year | ||||||||||
| - Preserves historical accuracy for archived projects (no forced updates) | ||||||||||
|
|
||||||||||
| **Key Distinction:** Git history is used as a trigger to determine *whether* an update is needed, but the actual end year value is always set to the current year when an update occurs. | ||||||||||
|
|
||||||||||
| ## Config Structure | ||||||||||
|
|
||||||||||
| > :bulb: You can automatically generate a new `.copywrite.hcl` config with the | ||||||||||
|
|
@@ -99,8 +129,8 @@ project { | |||||||||
|
|
||||||||||
| # (OPTIONAL) Represents the year that the project initially began | ||||||||||
| # This is used as the starting year in copyright statements | ||||||||||
| # If set and different from current year, headers will show: "copyright_year, current_year" | ||||||||||
| # If set and same as current year, headers will show: "current_year" | ||||||||||
| # If set and different from current year, headers will show: "copyright_year, year-2" | ||||||||||
| # If set and same as year-2, headers will show: "copyright_year" | ||||||||||
| # If not set (0), the tool will auto-detect from git history (first commit year) | ||||||||||
| # If auto-detection fails, it will fallback to current year only | ||||||||||
|
Comment on lines
-102
to
136
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this is now (still) incorrect. I don't know if we want/need either of the two years to be configurable during updating (I think it would be nice for consistence). Currently they are always inferred during updating - i.e. there is no way of overriding them via configuration. For new additions, the end year is not configurable either. Lines 147 to 150 in 421a509
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As discussed privately, year-1 can still be overridden via config file. |
||||||||||
| # Default: 0 (auto-detect) | ||||||||||
|
|
||||||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -6,8 +6,11 @@ package cmd | |||||||||||||||||||||||||||||||||||||||||||||||||
| import ( | ||||||||||||||||||||||||||||||||||||||||||||||||||
| "fmt" | ||||||||||||||||||||||||||||||||||||||||||||||||||
| "os" | ||||||||||||||||||||||||||||||||||||||||||||||||||
| "path/filepath" | ||||||||||||||||||||||||||||||||||||||||||||||||||
| "strings" | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| "github.com/hashicorp/copywrite/addlicense" | ||||||||||||||||||||||||||||||||||||||||||||||||||
| "github.com/hashicorp/copywrite/licensecheck" | ||||||||||||||||||||||||||||||||||||||||||||||||||
| "github.com/hashicorp/go-hclog" | ||||||||||||||||||||||||||||||||||||||||||||||||||
| "github.com/jedib0t/go-pretty/v6/text" | ||||||||||||||||||||||||||||||||||||||||||||||||||
| "github.com/samber/lo" | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -87,10 +90,23 @@ config, see the "copywrite init" command.`, | |||||||||||||||||||||||||||||||||||||||||||||||||
| ".github/workflows/**", | ||||||||||||||||||||||||||||||||||||||||||||||||||
| ".github/dependabot.yml", | ||||||||||||||||||||||||||||||||||||||||||||||||||
| "**/node_modules/**", | ||||||||||||||||||||||||||||||||||||||||||||||||||
| ".copywrite.hcl", | ||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||
| ignoredPatterns := lo.Union(conf.Project.HeaderIgnore, autoSkippedPatterns) | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| // Construct the configuration addLicense needs to properly format headers | ||||||||||||||||||||||||||||||||||||||||||||||||||
| // STEP 1: Update existing copyright headers | ||||||||||||||||||||||||||||||||||||||||||||||||||
| gha.StartGroup("Updating existing copyright headers:") | ||||||||||||||||||||||||||||||||||||||||||||||||||
| updatedCount, anyFileUpdated, licensePath := updateExistingHeaders(cmd, ignoredPatterns, plan) | ||||||||||||||||||||||||||||||||||||||||||||||||||
| gha.EndGroup() | ||||||||||||||||||||||||||||||||||||||||||||||||||
| if updatedCount > 0 { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| if plan { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| cmd.Printf("\n%s\n\n", text.FgYellow.Sprintf("[DRY RUN] Would update %d file(s) with new copyright years", updatedCount)) | ||||||||||||||||||||||||||||||||||||||||||||||||||
| } else { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| cmd.Printf("\n%s\n\n", text.FgGreen.Sprintf("Successfully updated %d file(s) with new copyright years", updatedCount)) | ||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| // STEP 2: Construct the configuration addLicense needs to properly format headers | ||||||||||||||||||||||||||||||||||||||||||||||||||
| licenseData := addlicense.LicenseData{ | ||||||||||||||||||||||||||||||||||||||||||||||||||
| Year: conf.FormatCopyrightYears(), // Format year(s) for copyright statements | ||||||||||||||||||||||||||||||||||||||||||||||||||
| Holder: conf.Project.CopyrightHolder, | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -112,10 +128,21 @@ config, see the "copywrite init" command.`, | |||||||||||||||||||||||||||||||||||||||||||||||||
| // cobra.CheckErr on the return, which will indeed output to stderr and | ||||||||||||||||||||||||||||||||||||||||||||||||||
| // return a non-zero error code. | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| gha.StartGroup("The following files are missing headers:") | ||||||||||||||||||||||||||||||||||||||||||||||||||
| // STEP 3: Add missing headers | ||||||||||||||||||||||||||||||||||||||||||||||||||
| gha.StartGroup("Adding missing copyright headers:") | ||||||||||||||||||||||||||||||||||||||||||||||||||
| err := addlicense.Run(ignoredPatterns, "only", licenseData, "", verbose, plan, []string{"."}, stdcliLogger) | ||||||||||||||||||||||||||||||||||||||||||||||||||
| gha.EndGroup() | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| // STEP 4: Update LICENSE file if any files were modified (either updated or added headers) | ||||||||||||||||||||||||||||||||||||||||||||||||||
| // In plan mode: if addlicense found missing headers (returns error), assume files would be modified | ||||||||||||||||||||||||||||||||||||||||||||||||||
| // In normal mode: if addlicense succeeded, assume files were modified | ||||||||||||||||||||||||||||||||||||||||||||||||||
| if err != nil || (!plan && err == nil) { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| anyFileUpdated = true | ||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| updateLicenseFile(cmd, licensePath, anyFileUpdated, plan) | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| // Check for errors after LICENSE file update so we still show what would happen | ||||||||||||||||||||||||||||||||||||||||||||||||||
| cobra.CheckErr(err) | ||||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -131,3 +158,86 @@ func init() { | |||||||||||||||||||||||||||||||||||||||||||||||||
| headersCmd.Flags().StringP("spdx", "s", "", "SPDX-compliant license identifier (e.g., 'MPL-2.0')") | ||||||||||||||||||||||||||||||||||||||||||||||||||
| headersCmd.Flags().StringP("copyright-holder", "c", "", "Copyright holder (default \"IBM Corp.\")") | ||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| // updateExistingHeaders walks through files and updates copyright headers based on config and git history | ||||||||||||||||||||||||||||||||||||||||||||||||||
| // Returns the count of updated files, a boolean indicating if any file was updated, and the LICENSE file path (if found) | ||||||||||||||||||||||||||||||||||||||||||||||||||
| func updateExistingHeaders(cmd *cobra.Command, ignoredPatterns []string, dryRun bool) (int, bool, string) { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| targetHolder := conf.Project.CopyrightHolder | ||||||||||||||||||||||||||||||||||||||||||||||||||
| if targetHolder == "" { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| targetHolder = "IBM Corp." | ||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| configYear := conf.Project.CopyrightYear | ||||||||||||||||||||||||||||||||||||||||||||||||||
| updatedCount := 0 | ||||||||||||||||||||||||||||||||||||||||||||||||||
| anyFileUpdated := false | ||||||||||||||||||||||||||||||||||||||||||||||||||
| var licensePath string | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| // Walk through all files in current directory | ||||||||||||||||||||||||||||||||||||||||||||||||||
| _ = filepath.Walk(".", func(path string, info os.FileInfo, err error) error { | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| // process at most 1000 files in parallel | |
| ch := make(chan *file, 1000) | |
| done := make(chan struct{}) | |
| var out error | |
| go func() { | |
| var wg errgroup.Group | |
| for f := range ch { | |
| f := f // https://golang.org/doc/faq#closures_and_goroutines | |
| wg.Go(func() error { | |
| err := processFile(f, t, license, checkonly, verbose, logger) | |
| return err | |
| }) | |
| } | |
| out = wg.Wait() | |
| close(done) | |
| }() | |
| for _, d := range patterns { | |
| if err := walk(ch, d, logger); err != nil { | |
| return err | |
| } | |
| } | |
| close(ch) | |
| <-done |
That way it would scale better for larger repositories and we could save some $$ on CI minutes in the long run.
I would even consider walking the repo just once (not twice) and then processing it for addition or update as necessary - not both.
For example, if you know that a new header was added when it was missing, it should not need to be checked again for updating. For that reason I think we should be first adding and then updating, not the other way around like it is now.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Introduced decoupling the IO-heavy operation of walk and update functions.
I would even consider walking the repo just once (not twice) and then processing it for addition or update as necessary - not both.
Will take into account these suggestions. Make enhancements in future releases.
C.C - @CreatorHead, @mallikabandaru
mohanmanikanta2299 marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We discussed this privately already but just to remember, this will likely need updating to recognise our BUSL LICENSE files, e.g. https://github.com/hashicorp/terraform/blob/main/LICENSE
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This has been added to our backlog. We will initiate the conversation with legal and make necessary modifications to accommodate BUSL License updates for copyrights.
C.C. - @CreatorHead @mallikabandaru
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This change should probably be applied to the builtin help page too:
copywrite/cmd/headers.go
Line 24 in 421a509