From a42d4e9f244c6de9698b04e7e2ab1df278f84c63 Mon Sep 17 00:00:00 2001 From: creatorHead Date: Thu, 12 Mar 2026 16:00:16 +0530 Subject: [PATCH 1/5] Add ignore_year2 behavior with header creation/update edge-case handling --- README.md | 11 ++++- cmd/headers.go | 12 +++--- cmd/init.go | 5 +++ config/config.go | 26 +++++++++--- config/config_test.go | 33 +++++++++++++++ licensecheck/update.go | 42 ++++++++++++++----- licensecheck/update_test.go | 80 ++++++++++++++++++++++++++++++------- 7 files changed, 172 insertions(+), 37 deletions(-) diff --git a/README.md b/README.md index b546141..ac90160 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,7 @@ This repo provides utilities for managing copyright headers and license files across many repos at scale. 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 @@ -87,6 +88,7 @@ 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 source code file was modified since the copyright end year @@ -105,11 +107,14 @@ to validate if a repo is in compliance or not. ### Copyright Year Logic **Source File Headers:** + - End year: Set to current year when file's source code 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 +- If project.ignore_year2 is true, second-year updates are skipped **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 @@ -151,6 +156,11 @@ project { # Default: 0 (auto-detect) # copyright_year = 0 + # (OPTIONAL) Ignore updates to the second year in copyright ranges. + # This does not change how start year is resolved. + # Default: false + # ignore_year2 = false + # (OPTIONAL) A list of globs that should not have copyright or license headers . # Supports doublestar glob patterns for more flexibility in defining which # files or folders should be ignored @@ -196,7 +206,6 @@ Note: Using fetch-depth parameter is mandatory as the tool will not be able to e **Impact of not updating year information:** If year information is not updated time to time, then the repo can be out of compliance. IBM policy suggests keeping source code files updated with latest year of code changes in a source code file. - ```yaml - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 with: diff --git a/cmd/headers.go b/cmd/headers.go index a8be5cc..58441e9 100644 --- a/cmd/headers.go +++ b/cmd/headers.go @@ -116,7 +116,7 @@ config, see the "copywrite init" command.`, // STEP 2: Construct the configuration addLicense needs to properly format headers licenseData := addlicense.LicenseData{ - Year: conf.FormatCopyrightYears(), // Format year(s) for copyright statements + Year: conf.FormatCopyrightYearsForNewHeaders(), // New headers should use full year format Holder: conf.Project.CopyrightHolder, SPDXID: conf.Project.License, } @@ -189,6 +189,7 @@ func updateExistingHeaders(cmd *cobra.Command, ignoredPatterns []string, dryRun } configYear := conf.Project.CopyrightYear + ignoreYear2 := conf.Project.IgnoreYear2 repoFirstYear, _ := licensecheck.GetRepoFirstCommitYear(".") // Open git repository once for all file operations @@ -232,14 +233,14 @@ func updateExistingHeaders(cmd *cobra.Command, ignoredPatterns []string, dryRun } if !dryRun { - updated, err := licensecheck.UpdateCopyrightHeaderWithCache(path, targetHolder, configYear, false, repoFirstYear, repoRoot) + updated, err := licensecheck.UpdateCopyrightHeaderWithCache(path, targetHolder, configYear, false, ignoreYear2, repoFirstYear, repoRoot) if err == nil && updated { cmd.Printf(" %s\n", path) atomic.AddInt64(&updatedCount64, 1) atomic.StoreInt32(&anyFileUpdatedFlag, 1) } } else { - needsUpdate, err := licensecheck.NeedsUpdateWithCache(path, targetHolder, configYear, false, repoFirstYear, repoRoot) + needsUpdate, err := licensecheck.NeedsUpdateWithCache(path, targetHolder, configYear, false, ignoreYear2, repoFirstYear, repoRoot) if err == nil && needsUpdate { cmd.Printf(" %s\n", path) atomic.AddInt64(&updatedCount64, 1) @@ -294,18 +295,19 @@ func updateLicenseFile(cmd *cobra.Command, licensePath string, anyFileUpdated bo repoFirstYear, _ := licensecheck.GetRepoFirstCommitYear(".") configYear := conf.Project.CopyrightYear + ignoreYear2 := conf.Project.IgnoreYear2 // Open git repository for LICENSE file operations repoRoot, _ := licensecheck.GetRepoRoot(".") // Update LICENSE file, forcing current year if any file was updated if !dryRun { - updated, err := licensecheck.UpdateCopyrightHeaderWithCache(licensePath, targetHolder, configYear, anyFileUpdated, repoFirstYear, repoRoot) + updated, err := licensecheck.UpdateCopyrightHeaderWithCache(licensePath, targetHolder, configYear, anyFileUpdated, ignoreYear2, repoFirstYear, repoRoot) if err == nil && updated { cmd.Printf("\nUpdated LICENSE file: %s\n", licensePath) } } else { - needsUpdate, err := licensecheck.NeedsUpdateWithCache(licensePath, targetHolder, configYear, anyFileUpdated, repoFirstYear, repoRoot) + needsUpdate, err := licensecheck.NeedsUpdateWithCache(licensePath, targetHolder, configYear, anyFileUpdated, ignoreYear2, repoFirstYear, repoRoot) if err == nil && needsUpdate { cmd.Printf("\n[DRY RUN] Would update LICENSE file: %s\n", licensePath) } diff --git a/cmd/init.go b/cmd/init.go index 2fd89ad..7e7c9ec 100644 --- a/cmd/init.go +++ b/cmd/init.go @@ -135,6 +135,11 @@ project { license = "{{.Project.License}}" copyright_year = {{.Project.CopyrightYear}} + # (OPTIONAL) If true, ignore updating the second year in copyright ranges. + # Start-year logic remains unchanged. + # Default: false + # ignore_year2 = false + # (OPTIONAL) A list of globs that should not have copyright/license headers. # Supports doublestar glob patterns for more flexibility in defining which # files or folders should be ignored diff --git a/config/config.go b/config/config.go index 6e9a806..a045f97 100644 --- a/config/config.go +++ b/config/config.go @@ -31,6 +31,7 @@ var ( type Project struct { CopyrightYear int `koanf:"copyright_year"` CopyrightHolder string `koanf:"copyright_holder"` + IgnoreYear2 bool `koanf:"ignore_year2"` HeaderIgnore []string `koanf:"header_ignore"` License string `koanf:"license"` @@ -277,11 +278,7 @@ func (c *Config) detectFirstCommitYear() int { return year } -// FormatCopyrightYears returns a formatted year string for copyright statements. -// If copyrightYear is 0, attempts to auto-detect from git history. -// If copyrightYear equals current year, returns current year only. -// Otherwise returns "copyrightYear, currentYear" format (e.g., "2023, 2025"). -func (c *Config) FormatCopyrightYears() string { +func (c *Config) formatCopyrightYearsInternal(ignoreYear2 bool) string { currentYear := time.Now().Year() copyrightYear := c.Project.CopyrightYear @@ -295,6 +292,11 @@ func (c *Config) FormatCopyrightYears() string { } } + // If configured, ignore "year 2" and emit only the starting year. + if ignoreYear2 { + return strconv.Itoa(copyrightYear) + } + // If copyright year equals current year, return single year if copyrightYear == currentYear { return strconv.Itoa(currentYear) @@ -303,3 +305,17 @@ func (c *Config) FormatCopyrightYears() string { // Return year range: "startYear, currentYear" return fmt.Sprintf("%d, %d", copyrightYear, currentYear) } + +// FormatCopyrightYears returns a formatted year string for updating existing +// copyright statements. When project.ignore_year2 is true, this returns only +// year1. +func (c *Config) FormatCopyrightYears() string { + return c.formatCopyrightYearsInternal(c.Project.IgnoreYear2) +} + +// FormatCopyrightYearsForNewHeaders returns a formatted year string for adding +// missing headers. This always returns the full expected year format when +// applicable and intentionally ignores project.ignore_year2. +func (c *Config) FormatCopyrightYearsForNewHeaders() string { + return c.formatCopyrightYearsInternal(false) +} diff --git a/config/config_test.go b/config/config_test.go index b3f5f2c..a035cd8 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -230,6 +230,15 @@ func Test_LoadConfigFile(t *testing.T) { }, }, }, + { + description: "File with project.ignore_year2 populates accordingly", + inputCfgPath: "testdata/project/ignore_year2_only.hcl", + expectedOutput: &Config{ + Project: Project{ + IgnoreYear2: true, + }, + }, + }, { description: "File with partial project populates accordingly", inputCfgPath: "testdata/project/partial_project.hcl", @@ -409,6 +418,16 @@ func Test_FormatCopyrightYears(t *testing.T) { assert.Equal(t, tt.expectedOutput, actualOutput, tt.description) }) } + + t.Run("Ignore year2 should emit single start year", func(t *testing.T) { + c := MustNew() + c.Project.CopyrightYear = 2023 + c.Project.IgnoreYear2 = true + + actualOutput := c.FormatCopyrightYears() + + assert.Equal(t, "2023", actualOutput) + }) } func Test_FormatCopyrightYears_AutoDetect(t *testing.T) { @@ -453,6 +472,20 @@ func Test_FormatCopyrightYears_AutoDetect(t *testing.T) { }) } +func Test_FormatCopyrightYearsForNewHeaders(t *testing.T) { + currentYear := time.Now().Year() + + t.Run("Ignore year2 should not affect new header format", func(t *testing.T) { + c := MustNew() + c.Project.CopyrightYear = 2021 + c.Project.IgnoreYear2 = true + + actualOutput := c.FormatCopyrightYearsForNewHeaders() + + assert.Equal(t, fmt.Sprintf("2021, %d", currentYear), actualOutput) + }) +} + // Helper function to get current directory func getCurrentDir(t *testing.T) string { dir, err := os.Getwd() diff --git a/licensecheck/update.go b/licensecheck/update.go index b3fa37b..18cdb98 100644 --- a/licensecheck/update.go +++ b/licensecheck/update.go @@ -369,10 +369,12 @@ func calculateYearUpdates( lastCommitYear int, currentYear int, forceCurrentYear bool, + ignoreYear2 bool, ) (bool, int, int) { shouldUpdate := false newStartYear := info.StartYear newEndYear := info.EndYear + hasNoYears := info.StartYear == 0 && info.EndYear == 0 // Condition 1: Update start year if canonical year differs from file's start year if canonicalStartYear > 0 && info.StartYear != canonicalStartYear { @@ -381,7 +383,7 @@ func calculateYearUpdates( } // Condition 2: Only update end year if file was modified after the copyright end year, or forceCurrentYear is true - if lastCommitYear > info.EndYear { + if (!ignoreYear2 || hasNoYears) && lastCommitYear > info.EndYear { if info.EndYear < currentYear { newEndYear = currentYear shouldUpdate = true @@ -389,11 +391,28 @@ func calculateYearUpdates( } // Condition 3: Force current year if requested (e.g., for LICENSE when other files updated) - if forceCurrentYear && info.EndYear < currentYear { + if (!ignoreYear2 || hasNoYears) && forceCurrentYear && info.EndYear < currentYear { newEndYear = currentYear shouldUpdate = true } + // If the header has no years at all, ensure we write a complete year format. + if hasNoYears { + if newStartYear == 0 { + if canonicalStartYear > 0 { + newStartYear = canonicalStartYear + } else { + newStartYear = currentYear + } + shouldUpdate = true + } + + if newEndYear == 0 { + newEndYear = currentYear + shouldUpdate = true + } + } + return shouldUpdate, newStartYear, newEndYear } @@ -544,6 +563,7 @@ func evaluateCopyrightUpdates( lastCommitYear int, currentYear int, forceCurrentYear bool, + ignoreYear2 bool, repoFirstYear int, ) []*struct { info *CopyrightInfo @@ -570,7 +590,7 @@ func evaluateCopyrightUpdates( } shouldUpdate, newStartYear, newEndYear := calculateYearUpdates( - info, canonicalStartYear, lastCommitYear, currentYear, forceCurrentYear, + info, canonicalStartYear, lastCommitYear, currentYear, forceCurrentYear, ignoreYear2, ) if shouldUpdate { @@ -592,17 +612,17 @@ func evaluateCopyrightUpdates( // UpdateCopyrightHeader updates all copyright headers in a file if needed // If forceCurrentYear is true, forces end year to current year regardless of git history // Returns true if the file was modified -func UpdateCopyrightHeader(filePath string, targetHolder string, configYear int, forceCurrentYear bool) (bool, error) { +func UpdateCopyrightHeader(filePath string, targetHolder string, configYear int, forceCurrentYear bool, ignoreYear2 bool) (bool, error) { repoRoot, _ := GetRepoRoot(filepath.Dir(filePath)) repoFirstYear, _ := GetRepoFirstCommitYear(filepath.Dir(filePath)) - return UpdateCopyrightHeaderWithCache(filePath, targetHolder, configYear, forceCurrentYear, repoFirstYear, repoRoot) + return UpdateCopyrightHeaderWithCache(filePath, targetHolder, configYear, forceCurrentYear, ignoreYear2, repoFirstYear, repoRoot) } // UpdateCopyrightHeaderWithCache updates all copyright headers in a file if needed // If forceCurrentYear is true, forces end year to current year regardless of git history // repoFirstYear and repoRoot can be provided to avoid repeated git lookups when processing multiple files // Returns true if the file was modified -func UpdateCopyrightHeaderWithCache(filePath string, targetHolder string, configYear int, forceCurrentYear bool, repoFirstYear int, repoRoot string) (bool, error) { +func UpdateCopyrightHeaderWithCache(filePath string, targetHolder string, configYear int, forceCurrentYear bool, ignoreYear2 bool, repoFirstYear int, repoRoot string) (bool, error) { // Skip .copywrite.hcl config file if filepath.Base(filePath) == ".copywrite.hcl" { return false, nil @@ -641,7 +661,7 @@ func UpdateCopyrightHeaderWithCache(filePath string, targetHolder string, config // Evaluate which copyrights need updating updates := evaluateCopyrightUpdates( - copyrights, targetHolder, configYear, lastCommitYear, currentYear, forceCurrentYear, repoFirstYear, + copyrights, targetHolder, configYear, lastCommitYear, currentYear, forceCurrentYear, ignoreYear2, repoFirstYear, ) if len(updates) == 0 { @@ -695,17 +715,17 @@ func UpdateCopyrightHeaderWithCache(filePath string, targetHolder string, config // NeedsUpdate checks if a file would be updated without actually modifying it // If forceCurrentYear is true, forces end year to current year regardless of git history // Returns true if the file has copyrights matching targetHolder that need year updates -func NeedsUpdate(filePath string, targetHolder string, configYear int, forceCurrentYear bool) (bool, error) { +func NeedsUpdate(filePath string, targetHolder string, configYear int, forceCurrentYear bool, ignoreYear2 bool) (bool, error) { repoRoot, _ := GetRepoRoot(filepath.Dir(filePath)) repoFirstYear, _ := GetRepoFirstCommitYear(filepath.Dir(filePath)) - return NeedsUpdateWithCache(filePath, targetHolder, configYear, forceCurrentYear, repoFirstYear, repoRoot) + return NeedsUpdateWithCache(filePath, targetHolder, configYear, forceCurrentYear, ignoreYear2, repoFirstYear, repoRoot) } // NeedsUpdateWithCache checks if a file would be updated without actually modifying it // If forceCurrentYear is true, forces end year to current year regardless of git history // repoFirstYear and repoRoot can be provided to avoid repeated git lookups when processing multiple files // Returns true if the file has copyrights matching targetHolder that need year updates -func NeedsUpdateWithCache(filePath string, targetHolder string, configYear int, forceCurrentYear bool, repoFirstCommitYear int, repoRoot string) (bool, error) { +func NeedsUpdateWithCache(filePath string, targetHolder string, configYear int, forceCurrentYear bool, ignoreYear2 bool, repoFirstCommitYear int, repoRoot string) (bool, error) { // Skip .copywrite.hcl config file if filepath.Base(filePath) == ".copywrite.hcl" { return false, nil @@ -740,7 +760,7 @@ func NeedsUpdateWithCache(filePath string, targetHolder string, configYear int, // Evaluate which copyrights need updating updates := evaluateCopyrightUpdates( - copyrights, targetHolder, configYear, lastCommitYear, currentYear, forceCurrentYear, repoFirstCommitYear, + copyrights, targetHolder, configYear, lastCommitYear, currentYear, forceCurrentYear, ignoreYear2, repoFirstCommitYear, ) return len(updates) > 0, nil diff --git a/licensecheck/update_test.go b/licensecheck/update_test.go index ed8341b..24eac5a 100644 --- a/licensecheck/update_test.go +++ b/licensecheck/update_test.go @@ -412,7 +412,7 @@ package main err := os.WriteFile(testFile, []byte(tt.initialContent), 0644) require.NoError(t, err) - modified, err := UpdateCopyrightHeader(testFile, tt.targetHolder, tt.configYear, tt.forceCurrentYear) + modified, err := UpdateCopyrightHeader(testFile, tt.targetHolder, tt.configYear, tt.forceCurrentYear, false) require.NoError(t, err) assert.Equal(t, tt.expectModified, modified) @@ -493,7 +493,7 @@ package main err := os.WriteFile(testFile, []byte(tt.fileContent), 0644) require.NoError(t, err) - needsUpdate, err := NeedsUpdate(testFile, tt.targetHolder, tt.configYear, tt.forceCurrentYear) + needsUpdate, err := NeedsUpdate(testFile, tt.targetHolder, tt.configYear, tt.forceCurrentYear, false) require.NoError(t, err) assert.Equal(t, tt.expectNeedsUpdate, needsUpdate) }) @@ -511,7 +511,7 @@ schema_version = 1 err := os.WriteFile(testFile, []byte(fileContent), 0644) require.NoError(t, err) - modified, err := UpdateCopyrightHeader(testFile, "IBM Corp.", 2022, false) + modified, err := UpdateCopyrightHeader(testFile, "IBM Corp.", 2022, false, false) require.NoError(t, err) assert.False(t, modified, "Should skip .copywrite.hcl file") } @@ -527,7 +527,7 @@ schema_version = 1 err := os.WriteFile(testFile, []byte(fileContent), 0644) require.NoError(t, err) - needsUpdate, err := NeedsUpdate(testFile, "IBM Corp.", 2022, false) + needsUpdate, err := NeedsUpdate(testFile, "IBM Corp.", 2022, false, false) require.NoError(t, err) assert.False(t, needsUpdate, "Should skip .copywrite.hcl file") } @@ -664,7 +664,7 @@ func main() {} err := os.WriteFile(testFile, []byte(fileContent), 0644) require.NoError(t, err) - modified, err := UpdateCopyrightHeader(testFile, "IBM Corp.", 2023, false) + modified, err := UpdateCopyrightHeader(testFile, "IBM Corp.", 2023, false, false) require.NoError(t, err) assert.False(t, modified, "Should skip generated files") @@ -687,7 +687,7 @@ package main err := os.WriteFile(testFile, []byte(fileContent), 0644) require.NoError(t, err) - needsUpdate, err := NeedsUpdate(testFile, "IBM Corp.", 2023, false) + needsUpdate, err := NeedsUpdate(testFile, "IBM Corp.", 2023, false, false) require.NoError(t, err) assert.False(t, needsUpdate, "Should skip generated files") } @@ -787,7 +787,7 @@ func TestUpdateCopyrightHeader_InlineCommentPreserved(t *testing.T) { err := os.WriteFile(testFile, []byte(initial), 0644) require.NoError(t, err) - modified, err := UpdateCopyrightHeader(testFile, "IBM Corp.", 2022, true) + modified, err := UpdateCopyrightHeader(testFile, "IBM Corp.", 2022, true, false) require.NoError(t, err) assert.True(t, modified) @@ -810,7 +810,7 @@ package main err := os.WriteFile(testFile, []byte(fileContent), 0644) require.NoError(t, err) - modified, err := UpdateCopyrightHeader(testFile, "IBM Corp.", 2023, false) + modified, err := UpdateCopyrightHeader(testFile, "IBM Corp.", 2023, false, false) require.NoError(t, err) assert.False(t, modified, "Should not update different copyright holder") @@ -826,7 +826,7 @@ func TestCalculateYearUpdates(t *testing.T) { t.Run("Update start year when canonical differs", func(t *testing.T) { info := &CopyrightInfo{StartYear: 2023, EndYear: 2023} shouldUpdate, newStart, newEnd := calculateYearUpdates( - info, 2020, 2023, currentYear, false, + info, 2020, 2023, currentYear, false, false, ) assert.True(t, shouldUpdate) assert.Equal(t, 2020, newStart) @@ -836,7 +836,7 @@ func TestCalculateYearUpdates(t *testing.T) { t.Run("No update when already current", func(t *testing.T) { info := &CopyrightInfo{StartYear: 2020, EndYear: currentYear} shouldUpdate, _, _ := calculateYearUpdates( - info, 2020, currentYear, currentYear, false, + info, 2020, currentYear, currentYear, false, false, ) assert.False(t, shouldUpdate) }) @@ -844,7 +844,7 @@ func TestCalculateYearUpdates(t *testing.T) { t.Run("Force current year updates end year", func(t *testing.T) { info := &CopyrightInfo{StartYear: 2020, EndYear: currentYear - 1} shouldUpdate, newStart, newEnd := calculateYearUpdates( - info, 2020, currentYear-1, currentYear, true, + info, 2020, currentYear-1, currentYear, true, false, ) assert.True(t, shouldUpdate) assert.Equal(t, 2020, newStart) @@ -854,12 +854,22 @@ func TestCalculateYearUpdates(t *testing.T) { t.Run("No years uses config and force updates end", func(t *testing.T) { info := &CopyrightInfo{StartYear: 0, EndYear: 0} shouldUpdate, newStart, newEnd := calculateYearUpdates( - info, 2022, 0, currentYear, true, + info, 2022, 0, currentYear, true, false, ) assert.True(t, shouldUpdate) assert.Equal(t, 2022, newStart) assert.Equal(t, currentYear, newEnd) }) + + t.Run("Ignore year2 skips end year updates", func(t *testing.T) { + info := &CopyrightInfo{StartYear: 2020, EndYear: currentYear - 2} + shouldUpdate, newStart, newEnd := calculateYearUpdates( + info, 2020, currentYear, currentYear, true, true, + ) + assert.False(t, shouldUpdate) + assert.Equal(t, 2020, newStart) + assert.Equal(t, currentYear-2, newEnd) + }) } func TestUpdateCopyrightHeader_HandlebarsFiles(t *testing.T) { @@ -883,12 +893,12 @@ func TestUpdateCopyrightHeader_HandlebarsFiles(t *testing.T) { require.NoError(t, err) // Test that it needs an update - needsUpdate, err := NeedsUpdate(testFile, "IBM Corp.", 2021, true) + needsUpdate, err := NeedsUpdate(testFile, "IBM Corp.", 2021, true, false) require.NoError(t, err) assert.True(t, needsUpdate, "Should detect that .hbs file needs copyright update") // Test updating the copyright header - modified, err := UpdateCopyrightHeader(testFile, "IBM Corp.", 2021, true) + modified, err := UpdateCopyrightHeader(testFile, "IBM Corp.", 2021, true, false) require.NoError(t, err) assert.True(t, modified, "Should successfully update .hbs file copyright") @@ -910,7 +920,47 @@ func TestUpdateCopyrightHeader_HandlebarsFiles(t *testing.T) { assert.Equal(t, expectedContent, string(content)) // Test that it doesn't need another update - needsUpdate2, err := NeedsUpdate(testFile, "IBM Corp.", 2021, true) + needsUpdate2, err := NeedsUpdate(testFile, "IBM Corp.", 2021, true, false) require.NoError(t, err) assert.False(t, needsUpdate2, "Should not need another update after being updated to current year") } + +func TestUpdateCopyrightHeader_IgnoreYear2(t *testing.T) { + tempDir := t.TempDir() + testFile := filepath.Join(tempDir, "test.go") + + initial := `// Copyright IBM Corp. 2022, 2023 +package main +` + err := os.WriteFile(testFile, []byte(initial), 0644) + require.NoError(t, err) + + needsUpdate, err := NeedsUpdate(testFile, "IBM Corp.", 2022, true, true) + require.NoError(t, err) + assert.False(t, needsUpdate, "ignore_year2 should suppress end-year-only updates") + + modified, err := UpdateCopyrightHeader(testFile, "IBM Corp.", 2022, true, true) + require.NoError(t, err) + assert.False(t, modified, "ignore_year2 should avoid mutating end year") +} + +func TestUpdateCopyrightHeader_IgnoreYear2_NoYearsInHeader(t *testing.T) { + currentYear := time.Now().Year() + tempDir := t.TempDir() + testFile := filepath.Join(tempDir, "test.go") + + initial := `// Copyright IBM Corp. +package main +` + err := os.WriteFile(testFile, []byte(initial), 0644) + require.NoError(t, err) + + modified, err := UpdateCopyrightHeader(testFile, "IBM Corp.", 2021, false, true) + require.NoError(t, err) + assert.True(t, modified, "headers without years should be normalized to include year1 and year2") + + content, err := os.ReadFile(testFile) + require.NoError(t, err) + expected := fmt.Sprintf("// Copyright IBM Corp. 2021, %d\npackage main\n", currentYear) + assert.Equal(t, expected, string(content)) +} From c26500908c2bcf82d65855e1a206fe7916e732c6 Mon Sep 17 00:00:00 2001 From: creatorHead Date: Thu, 12 Mar 2026 16:02:47 +0530 Subject: [PATCH 2/5] Fix year-format API regression and remove ignore_year2 fixture dependency --- config/config_test.go | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/config/config_test.go b/config/config_test.go index a035cd8..59f84ee 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -39,6 +39,7 @@ func Test_LoadConfMap(t *testing.T) { mp := map[string]interface{}{ "schema_version": 12, "project.copyright_year": 9001, + "project.ignore_year2": true, "project.license": "MPL-2.0", "dispatch.ignored_repos": []string{"foo", "bar"}, } @@ -54,6 +55,7 @@ func Test_LoadConfMap(t *testing.T) { Project: Project{ CopyrightHolder: "IBM Corp.", CopyrightYear: 9001, + IgnoreYear2: true, License: "MPL-2.0", }, Dispatch: Dispatch{ @@ -230,15 +232,6 @@ func Test_LoadConfigFile(t *testing.T) { }, }, }, - { - description: "File with project.ignore_year2 populates accordingly", - inputCfgPath: "testdata/project/ignore_year2_only.hcl", - expectedOutput: &Config{ - Project: Project{ - IgnoreYear2: true, - }, - }, - }, { description: "File with partial project populates accordingly", inputCfgPath: "testdata/project/partial_project.hcl", From 954999fe4b713c4e6b5d9e901882b1dc5343de71 Mon Sep 17 00:00:00 2001 From: creatorHead Date: Wed, 18 Mar 2026 17:36:08 +0530 Subject: [PATCH 3/5] Remove ignore_year2 and add ignore_year1 behavioral tests - Remove all ignore_year2/IgnoreYear2 references from config, tests, init template, and README - Add explicit tests documenting that ignore_year1 suppresses start-year updates on existing headers but does not affect new-file header creation (addlicense always uses config year) - Fix stale extra argument in TestCalculateYearUpdates left over from ignore_year2 removal Co-Authored-By: Claude Sonnet 4.6 --- README.md | 8 +++--- cmd/headers.go | 12 ++++---- cmd/init.go | 6 ++-- config/config.go | 21 ++++---------- config/config_test.go | 31 ++++++++------------ licensecheck/update.go | 28 +++++++++---------- licensecheck/update_test.go | 56 ++++++++++++++++++++++++++++--------- 7 files changed, 88 insertions(+), 74 deletions(-) diff --git a/README.md b/README.md index ac90160..05f8712 100644 --- a/README.md +++ b/README.md @@ -111,7 +111,7 @@ to validate if a repo is in compliance or not. - End year: Set to current year when file's source code 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 -- If project.ignore_year2 is true, second-year updates are skipped +- If project.ignore_year1 is true, start-year updates are skipped **LICENSE Files:** @@ -156,10 +156,10 @@ project { # Default: 0 (auto-detect) # copyright_year = 0 - # (OPTIONAL) Ignore updates to the second year in copyright ranges. - # This does not change how start year is resolved. + # (OPTIONAL) Ignore updates to the first year (start year) in copyright ranges. + # This does not change how end year is resolved. # Default: false - # ignore_year2 = false + # ignore_year1 = false # (OPTIONAL) A list of globs that should not have copyright or license headers . # Supports doublestar glob patterns for more flexibility in defining which diff --git a/cmd/headers.go b/cmd/headers.go index 58441e9..fb978d5 100644 --- a/cmd/headers.go +++ b/cmd/headers.go @@ -189,7 +189,7 @@ func updateExistingHeaders(cmd *cobra.Command, ignoredPatterns []string, dryRun } configYear := conf.Project.CopyrightYear - ignoreYear2 := conf.Project.IgnoreYear2 + ignoreYear1 := conf.Project.IgnoreYear1 repoFirstYear, _ := licensecheck.GetRepoFirstCommitYear(".") // Open git repository once for all file operations @@ -233,14 +233,14 @@ func updateExistingHeaders(cmd *cobra.Command, ignoredPatterns []string, dryRun } if !dryRun { - updated, err := licensecheck.UpdateCopyrightHeaderWithCache(path, targetHolder, configYear, false, ignoreYear2, repoFirstYear, repoRoot) + updated, err := licensecheck.UpdateCopyrightHeaderWithCache(path, targetHolder, configYear, false, ignoreYear1, repoFirstYear, repoRoot) if err == nil && updated { cmd.Printf(" %s\n", path) atomic.AddInt64(&updatedCount64, 1) atomic.StoreInt32(&anyFileUpdatedFlag, 1) } } else { - needsUpdate, err := licensecheck.NeedsUpdateWithCache(path, targetHolder, configYear, false, ignoreYear2, repoFirstYear, repoRoot) + needsUpdate, err := licensecheck.NeedsUpdateWithCache(path, targetHolder, configYear, false, ignoreYear1, repoFirstYear, repoRoot) if err == nil && needsUpdate { cmd.Printf(" %s\n", path) atomic.AddInt64(&updatedCount64, 1) @@ -295,19 +295,19 @@ func updateLicenseFile(cmd *cobra.Command, licensePath string, anyFileUpdated bo repoFirstYear, _ := licensecheck.GetRepoFirstCommitYear(".") configYear := conf.Project.CopyrightYear - ignoreYear2 := conf.Project.IgnoreYear2 + ignoreYear1 := conf.Project.IgnoreYear1 // Open git repository for LICENSE file operations repoRoot, _ := licensecheck.GetRepoRoot(".") // Update LICENSE file, forcing current year if any file was updated if !dryRun { - updated, err := licensecheck.UpdateCopyrightHeaderWithCache(licensePath, targetHolder, configYear, anyFileUpdated, ignoreYear2, repoFirstYear, repoRoot) + updated, err := licensecheck.UpdateCopyrightHeaderWithCache(licensePath, targetHolder, configYear, anyFileUpdated, ignoreYear1, repoFirstYear, repoRoot) if err == nil && updated { cmd.Printf("\nUpdated LICENSE file: %s\n", licensePath) } } else { - needsUpdate, err := licensecheck.NeedsUpdateWithCache(licensePath, targetHolder, configYear, anyFileUpdated, ignoreYear2, repoFirstYear, repoRoot) + needsUpdate, err := licensecheck.NeedsUpdateWithCache(licensePath, targetHolder, configYear, anyFileUpdated, ignoreYear1, repoFirstYear, repoRoot) if err == nil && needsUpdate { cmd.Printf("\n[DRY RUN] Would update LICENSE file: %s\n", licensePath) } diff --git a/cmd/init.go b/cmd/init.go index 7e7c9ec..1c2727c 100644 --- a/cmd/init.go +++ b/cmd/init.go @@ -135,10 +135,10 @@ project { license = "{{.Project.License}}" copyright_year = {{.Project.CopyrightYear}} - # (OPTIONAL) If true, ignore updating the second year in copyright ranges. - # Start-year logic remains unchanged. + # (OPTIONAL) If true, ignore updating the first year (start year) in copyright ranges. + # End-year logic remains unchanged. # Default: false - # ignore_year2 = false + # ignore_year1 = false # (OPTIONAL) A list of globs that should not have copyright/license headers. # Supports doublestar glob patterns for more flexibility in defining which diff --git a/config/config.go b/config/config.go index a045f97..3a94a7c 100644 --- a/config/config.go +++ b/config/config.go @@ -31,7 +31,7 @@ var ( type Project struct { CopyrightYear int `koanf:"copyright_year"` CopyrightHolder string `koanf:"copyright_holder"` - IgnoreYear2 bool `koanf:"ignore_year2"` + IgnoreYear1 bool `koanf:"ignore_year1"` HeaderIgnore []string `koanf:"header_ignore"` License string `koanf:"license"` @@ -278,7 +278,7 @@ func (c *Config) detectFirstCommitYear() int { return year } -func (c *Config) formatCopyrightYearsInternal(ignoreYear2 bool) string { +func (c *Config) formatCopyrightYears() string { currentYear := time.Now().Year() copyrightYear := c.Project.CopyrightYear @@ -292,11 +292,6 @@ func (c *Config) formatCopyrightYearsInternal(ignoreYear2 bool) string { } } - // If configured, ignore "year 2" and emit only the starting year. - if ignoreYear2 { - return strconv.Itoa(copyrightYear) - } - // If copyright year equals current year, return single year if copyrightYear == currentYear { return strconv.Itoa(currentYear) @@ -306,16 +301,12 @@ func (c *Config) formatCopyrightYearsInternal(ignoreYear2 bool) string { return fmt.Sprintf("%d, %d", copyrightYear, currentYear) } -// FormatCopyrightYears returns a formatted year string for updating existing -// copyright statements. When project.ignore_year2 is true, this returns only -// year1. +// FormatCopyrightYears returns a formatted year string for copyright statements. func (c *Config) FormatCopyrightYears() string { - return c.formatCopyrightYearsInternal(c.Project.IgnoreYear2) + return c.formatCopyrightYears() } -// FormatCopyrightYearsForNewHeaders returns a formatted year string for adding -// missing headers. This always returns the full expected year format when -// applicable and intentionally ignores project.ignore_year2. +// FormatCopyrightYearsForNewHeaders returns a formatted year string for adding missing headers. func (c *Config) FormatCopyrightYearsForNewHeaders() string { - return c.formatCopyrightYearsInternal(false) + return c.formatCopyrightYears() } diff --git a/config/config_test.go b/config/config_test.go index 59f84ee..5fe6739 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -39,7 +39,7 @@ func Test_LoadConfMap(t *testing.T) { mp := map[string]interface{}{ "schema_version": 12, "project.copyright_year": 9001, - "project.ignore_year2": true, + "project.ignore_year1": true, "project.license": "MPL-2.0", "dispatch.ignored_repos": []string{"foo", "bar"}, } @@ -55,7 +55,7 @@ func Test_LoadConfMap(t *testing.T) { Project: Project{ CopyrightHolder: "IBM Corp.", CopyrightYear: 9001, - IgnoreYear2: true, + IgnoreYear1: true, License: "MPL-2.0", }, Dispatch: Dispatch{ @@ -412,15 +412,6 @@ func Test_FormatCopyrightYears(t *testing.T) { }) } - t.Run("Ignore year2 should emit single start year", func(t *testing.T) { - c := MustNew() - c.Project.CopyrightYear = 2023 - c.Project.IgnoreYear2 = true - - actualOutput := c.FormatCopyrightYears() - - assert.Equal(t, "2023", actualOutput) - }) } func Test_FormatCopyrightYears_AutoDetect(t *testing.T) { @@ -465,18 +456,20 @@ func Test_FormatCopyrightYears_AutoDetect(t *testing.T) { }) } -func Test_FormatCopyrightYearsForNewHeaders(t *testing.T) { +// Test_FormatCopyrightYearsForNewHeaders verifies that ignore_year1 does NOT suppress +// the config year when creating brand-new copyright headers. New files always receive +// the full "configYear, currentYear" string from the .hcl copyright_year setting. +func Test_FormatCopyrightYearsForNewHeaders_IgnoreYear1DoesNotAffectNewHeaders(t *testing.T) { currentYear := time.Now().Year() - t.Run("Ignore year2 should not affect new header format", func(t *testing.T) { - c := MustNew() - c.Project.CopyrightYear = 2021 - c.Project.IgnoreYear2 = true + c := MustNew() + c.Project.CopyrightYear = 2015 + c.Project.IgnoreYear1 = true - actualOutput := c.FormatCopyrightYearsForNewHeaders() + actualOutput := c.FormatCopyrightYearsForNewHeaders() - assert.Equal(t, fmt.Sprintf("2021, %d", currentYear), actualOutput) - }) + assert.Equal(t, fmt.Sprintf("2015, %d", currentYear), actualOutput, + "ignore_year1 must not affect new-header year format; config year should always be used") } // Helper function to get current directory diff --git a/licensecheck/update.go b/licensecheck/update.go index 18cdb98..6872ac1 100644 --- a/licensecheck/update.go +++ b/licensecheck/update.go @@ -369,7 +369,7 @@ func calculateYearUpdates( lastCommitYear int, currentYear int, forceCurrentYear bool, - ignoreYear2 bool, + ignoreYear1 bool, ) (bool, int, int) { shouldUpdate := false newStartYear := info.StartYear @@ -377,13 +377,13 @@ func calculateYearUpdates( hasNoYears := info.StartYear == 0 && info.EndYear == 0 // Condition 1: Update start year if canonical year differs from file's start year - if canonicalStartYear > 0 && info.StartYear != canonicalStartYear { + if (!ignoreYear1 || hasNoYears) && canonicalStartYear > 0 && info.StartYear != canonicalStartYear { newStartYear = canonicalStartYear shouldUpdate = true } // Condition 2: Only update end year if file was modified after the copyright end year, or forceCurrentYear is true - if (!ignoreYear2 || hasNoYears) && lastCommitYear > info.EndYear { + if lastCommitYear > info.EndYear { if info.EndYear < currentYear { newEndYear = currentYear shouldUpdate = true @@ -391,7 +391,7 @@ func calculateYearUpdates( } // Condition 3: Force current year if requested (e.g., for LICENSE when other files updated) - if (!ignoreYear2 || hasNoYears) && forceCurrentYear && info.EndYear < currentYear { + if forceCurrentYear && info.EndYear < currentYear { newEndYear = currentYear shouldUpdate = true } @@ -563,7 +563,7 @@ func evaluateCopyrightUpdates( lastCommitYear int, currentYear int, forceCurrentYear bool, - ignoreYear2 bool, + ignoreYear1 bool, repoFirstYear int, ) []*struct { info *CopyrightInfo @@ -590,7 +590,7 @@ func evaluateCopyrightUpdates( } shouldUpdate, newStartYear, newEndYear := calculateYearUpdates( - info, canonicalStartYear, lastCommitYear, currentYear, forceCurrentYear, ignoreYear2, + info, canonicalStartYear, lastCommitYear, currentYear, forceCurrentYear, ignoreYear1, ) if shouldUpdate { @@ -612,17 +612,17 @@ func evaluateCopyrightUpdates( // UpdateCopyrightHeader updates all copyright headers in a file if needed // If forceCurrentYear is true, forces end year to current year regardless of git history // Returns true if the file was modified -func UpdateCopyrightHeader(filePath string, targetHolder string, configYear int, forceCurrentYear bool, ignoreYear2 bool) (bool, error) { +func UpdateCopyrightHeader(filePath string, targetHolder string, configYear int, forceCurrentYear bool, ignoreYear1 bool) (bool, error) { repoRoot, _ := GetRepoRoot(filepath.Dir(filePath)) repoFirstYear, _ := GetRepoFirstCommitYear(filepath.Dir(filePath)) - return UpdateCopyrightHeaderWithCache(filePath, targetHolder, configYear, forceCurrentYear, ignoreYear2, repoFirstYear, repoRoot) + return UpdateCopyrightHeaderWithCache(filePath, targetHolder, configYear, forceCurrentYear, ignoreYear1, repoFirstYear, repoRoot) } // UpdateCopyrightHeaderWithCache updates all copyright headers in a file if needed // If forceCurrentYear is true, forces end year to current year regardless of git history // repoFirstYear and repoRoot can be provided to avoid repeated git lookups when processing multiple files // Returns true if the file was modified -func UpdateCopyrightHeaderWithCache(filePath string, targetHolder string, configYear int, forceCurrentYear bool, ignoreYear2 bool, repoFirstYear int, repoRoot string) (bool, error) { +func UpdateCopyrightHeaderWithCache(filePath string, targetHolder string, configYear int, forceCurrentYear bool, ignoreYear1 bool, repoFirstYear int, repoRoot string) (bool, error) { // Skip .copywrite.hcl config file if filepath.Base(filePath) == ".copywrite.hcl" { return false, nil @@ -661,7 +661,7 @@ func UpdateCopyrightHeaderWithCache(filePath string, targetHolder string, config // Evaluate which copyrights need updating updates := evaluateCopyrightUpdates( - copyrights, targetHolder, configYear, lastCommitYear, currentYear, forceCurrentYear, ignoreYear2, repoFirstYear, + copyrights, targetHolder, configYear, lastCommitYear, currentYear, forceCurrentYear, ignoreYear1, repoFirstYear, ) if len(updates) == 0 { @@ -715,17 +715,17 @@ func UpdateCopyrightHeaderWithCache(filePath string, targetHolder string, config // NeedsUpdate checks if a file would be updated without actually modifying it // If forceCurrentYear is true, forces end year to current year regardless of git history // Returns true if the file has copyrights matching targetHolder that need year updates -func NeedsUpdate(filePath string, targetHolder string, configYear int, forceCurrentYear bool, ignoreYear2 bool) (bool, error) { +func NeedsUpdate(filePath string, targetHolder string, configYear int, forceCurrentYear bool, ignoreYear1 bool) (bool, error) { repoRoot, _ := GetRepoRoot(filepath.Dir(filePath)) repoFirstYear, _ := GetRepoFirstCommitYear(filepath.Dir(filePath)) - return NeedsUpdateWithCache(filePath, targetHolder, configYear, forceCurrentYear, ignoreYear2, repoFirstYear, repoRoot) + return NeedsUpdateWithCache(filePath, targetHolder, configYear, forceCurrentYear, ignoreYear1, repoFirstYear, repoRoot) } // NeedsUpdateWithCache checks if a file would be updated without actually modifying it // If forceCurrentYear is true, forces end year to current year regardless of git history // repoFirstYear and repoRoot can be provided to avoid repeated git lookups when processing multiple files // Returns true if the file has copyrights matching targetHolder that need year updates -func NeedsUpdateWithCache(filePath string, targetHolder string, configYear int, forceCurrentYear bool, ignoreYear2 bool, repoFirstCommitYear int, repoRoot string) (bool, error) { +func NeedsUpdateWithCache(filePath string, targetHolder string, configYear int, forceCurrentYear bool, ignoreYear1 bool, repoFirstCommitYear int, repoRoot string) (bool, error) { // Skip .copywrite.hcl config file if filepath.Base(filePath) == ".copywrite.hcl" { return false, nil @@ -760,7 +760,7 @@ func NeedsUpdateWithCache(filePath string, targetHolder string, configYear int, // Evaluate which copyrights need updating updates := evaluateCopyrightUpdates( - copyrights, targetHolder, configYear, lastCommitYear, currentYear, forceCurrentYear, ignoreYear2, repoFirstCommitYear, + copyrights, targetHolder, configYear, lastCommitYear, currentYear, forceCurrentYear, ignoreYear1, repoFirstCommitYear, ) return len(updates) > 0, nil diff --git a/licensecheck/update_test.go b/licensecheck/update_test.go index 24eac5a..0d353c4 100644 --- a/licensecheck/update_test.go +++ b/licensecheck/update_test.go @@ -861,14 +861,14 @@ func TestCalculateYearUpdates(t *testing.T) { assert.Equal(t, currentYear, newEnd) }) - t.Run("Ignore year2 skips end year updates", func(t *testing.T) { - info := &CopyrightInfo{StartYear: 2020, EndYear: currentYear - 2} + t.Run("Ignore year1 skips start year updates", func(t *testing.T) { + info := &CopyrightInfo{StartYear: 2023, EndYear: 2023} shouldUpdate, newStart, newEnd := calculateYearUpdates( - info, 2020, currentYear, currentYear, true, true, + info, 2020, 2023, currentYear, false, true, ) assert.False(t, shouldUpdate) - assert.Equal(t, 2020, newStart) - assert.Equal(t, currentYear-2, newEnd) + assert.Equal(t, 2023, newStart) + assert.Equal(t, 2023, newEnd) }) } @@ -925,26 +925,56 @@ func TestUpdateCopyrightHeader_HandlebarsFiles(t *testing.T) { assert.False(t, needsUpdate2, "Should not need another update after being updated to current year") } -func TestUpdateCopyrightHeader_IgnoreYear2(t *testing.T) { + +func TestUpdateCopyrightHeader_IgnoreYear1(t *testing.T) { tempDir := t.TempDir() testFile := filepath.Join(tempDir, "test.go") - initial := `// Copyright IBM Corp. 2022, 2023 + // Header has start year 2023 but canonical (config) year is 2020 — normally Condition 1 would update it. + initial := `// Copyright IBM Corp. 2023, 2023 package main ` err := os.WriteFile(testFile, []byte(initial), 0644) require.NoError(t, err) - needsUpdate, err := NeedsUpdate(testFile, "IBM Corp.", 2022, true, true) + needsUpdate, err := NeedsUpdate(testFile, "IBM Corp.", 2020, false, true) + require.NoError(t, err) + assert.False(t, needsUpdate, "ignore_year1 should suppress start-year-only updates") + + modified, err := UpdateCopyrightHeader(testFile, "IBM Corp.", 2020, false, true) require.NoError(t, err) - assert.False(t, needsUpdate, "ignore_year2 should suppress end-year-only updates") + assert.False(t, modified, "ignore_year1 should avoid mutating start year") +} + +// TestUpdateCopyrightHeader_IgnoreYear1_NoCopyrightText verifies that a file with NO +// copyright text at all is left untouched by UpdateCopyrightHeader even when ignore_year1 +// is set. New-file header creation is delegated to addlicense, which always uses the +// config year — ignore_year1 must not prevent that from working. +func TestUpdateCopyrightHeader_IgnoreYear1_NoCopyrightText(t *testing.T) { + tempDir := t.TempDir() + testFile := filepath.Join(tempDir, "test.go") - modified, err := UpdateCopyrightHeader(testFile, "IBM Corp.", 2022, true, true) + // File with no copyright line at all (brand-new file scenario). + initial := `package main + +func main() {} +` + err := os.WriteFile(testFile, []byte(initial), 0644) + require.NoError(t, err) + + // UpdateCopyrightHeader should do nothing — there is nothing to update. + // The header will be added by addlicense using the config year (2015). + modified, err := UpdateCopyrightHeader(testFile, "IBM Corp.", 2015, false, true) + require.NoError(t, err) + assert.False(t, modified, "file with no copyright should not be modified by UpdateCopyrightHeader") + + // File content must remain unchanged. + content, err := os.ReadFile(testFile) require.NoError(t, err) - assert.False(t, modified, "ignore_year2 should avoid mutating end year") + assert.Equal(t, initial, string(content)) } -func TestUpdateCopyrightHeader_IgnoreYear2_NoYearsInHeader(t *testing.T) { +func TestUpdateCopyrightHeader_IgnoreYear1_NoYearsInHeader(t *testing.T) { currentYear := time.Now().Year() tempDir := t.TempDir() testFile := filepath.Join(tempDir, "test.go") @@ -957,7 +987,7 @@ package main modified, err := UpdateCopyrightHeader(testFile, "IBM Corp.", 2021, false, true) require.NoError(t, err) - assert.True(t, modified, "headers without years should be normalized to include year1 and year2") + assert.True(t, modified, "headers without years should be normalized even when ignore_year1 is set") content, err := os.ReadFile(testFile) require.NoError(t, err) From c99461e28acb4a80ea89386d37c2aef4d8ecc4b8 Mon Sep 17 00:00:00 2001 From: creatorHead Date: Mon, 23 Mar 2026 13:07:53 +0530 Subject: [PATCH 4/5] Document ignore_year1 effect on LICENSE file headers in README Co-Authored-By: Claude Sonnet 4.6 --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 05f8712..a58ef29 100644 --- a/README.md +++ b/README.md @@ -119,6 +119,7 @@ to validate if a repo is in compliance or not. - 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) +- If project.ignore_year1 is true, start-year updates are skipped (same behaviour as source file headers) **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. From 254c81ff133fb0ba9a6c5046cfce05f0a02d4846 Mon Sep 17 00:00:00 2001 From: creatorHead Date: Mon, 23 Mar 2026 13:12:28 +0530 Subject: [PATCH 5/5] Remove redundant FormatCopyrightYears in favour of FormatCopyrightYearsForNewHeaders Both were identical wrappers; FormatCopyrightYears had no production callers. Consolidate to the single function used in production and update tests. --- config/config.go | 5 ----- config/config_test.go | 10 +++++----- 2 files changed, 5 insertions(+), 10 deletions(-) diff --git a/config/config.go b/config/config.go index 3a94a7c..56e8360 100644 --- a/config/config.go +++ b/config/config.go @@ -301,11 +301,6 @@ func (c *Config) formatCopyrightYears() string { return fmt.Sprintf("%d, %d", copyrightYear, currentYear) } -// FormatCopyrightYears returns a formatted year string for copyright statements. -func (c *Config) FormatCopyrightYears() string { - return c.formatCopyrightYears() -} - // FormatCopyrightYearsForNewHeaders returns a formatted year string for adding missing headers. func (c *Config) FormatCopyrightYearsForNewHeaders() string { return c.formatCopyrightYears() diff --git a/config/config_test.go b/config/config_test.go index 5fe6739..cfb3c02 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -376,7 +376,7 @@ func Test_GetConfigPath(t *testing.T) { assert.Equal(t, abs, actualOutput.GetConfigPath(), "Loaded config should return abs file path") } -func Test_FormatCopyrightYears(t *testing.T) { +func Test_FormatCopyrightYearsForNewHeaders(t *testing.T) { currentYear := time.Now().Year() tests := []struct { @@ -406,7 +406,7 @@ func Test_FormatCopyrightYears(t *testing.T) { c := MustNew() c.Project.CopyrightYear = tt.copyrightYear - actualOutput := c.FormatCopyrightYears() + actualOutput := c.FormatCopyrightYearsForNewHeaders() assert.Equal(t, tt.expectedOutput, actualOutput, tt.description) }) @@ -414,7 +414,7 @@ func Test_FormatCopyrightYears(t *testing.T) { } -func Test_FormatCopyrightYears_AutoDetect(t *testing.T) { +func Test_FormatCopyrightYearsForNewHeaders_AutoDetect(t *testing.T) { currentYear := time.Now().Year() t.Run("Auto-detect from git when copyright_year not set", func(t *testing.T) { @@ -424,7 +424,7 @@ func Test_FormatCopyrightYears_AutoDetect(t *testing.T) { // Set config path to this repo's directory for git detection c.absCfgPath = filepath.Join(getCurrentDir(t), ".copywrite.hcl") - actualOutput := c.FormatCopyrightYears() + actualOutput := c.FormatCopyrightYearsForNewHeaders() // Should auto-detect and return a year range (this repo was created before 2025) // The format should be "YYYY, currentYear" where YYYY < currentYear @@ -448,7 +448,7 @@ func Test_FormatCopyrightYears_AutoDetect(t *testing.T) { // Set config path to non-existent directory (git will fail) c.absCfgPath = "/nonexistent/path/.copywrite.hcl" - actualOutput := c.FormatCopyrightYears() + actualOutput := c.FormatCopyrightYearsForNewHeaders() // Should fallback to current year only assert.Equal(t, strconv.Itoa(currentYear), actualOutput,