Skip to content

Commit

Permalink
dev: Add copyright header year check
Browse files Browse the repository at this point in the history
  • Loading branch information
kschiffer committed Feb 5, 2024
1 parent 26d9125 commit 791a41c
Show file tree
Hide file tree
Showing 2 changed files with 68 additions and 3 deletions.
4 changes: 3 additions & 1 deletion .github/workflows/general.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,16 @@ jobs:
steps:
- name: Check out code
uses: actions/checkout@v4
- name: Fetch base ref
run: git fetch origin ${{ github.base_ref }}
- name: Install Go and Dependencies
uses: ./.github/actions/install-go-and-deps
- name: Build Mage
uses: ./.github/actions/build-mage
- name: Install Node and Dependencies
uses: ./.github/actions/install-node-and-deps
- name: Check headers
run: tools/bin/mage headers:check
run: tools/bin/mage headers:check headers:checkNewFiles
- name: Fix common spelling mistakes
run: tools/bin/mage dev:misspell
- name: Format SQL files
Expand Down
67 changes: 65 additions & 2 deletions tools/mage/headers.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,12 @@ import (
"bytes"
"fmt"
"os"
"os/exec"
"path/filepath"
"regexp"
"strconv"
"strings"
"time"

"github.com/magefile/mage/mg"
"gopkg.in/yaml.v2"
Expand Down Expand Up @@ -163,17 +166,33 @@ func (errs errorSlice) Error() string {
case 0:
return ""
case 1:
return errs[0].Error()
// Return the formatted error string for a single error.
return formatErrorForGitHub(errs[0])
default:
var b strings.Builder
b.WriteString("multiple errors:\n")
for _, err := range errs {
b.WriteString(" - " + err.Error() + "\n")
formattedError := formatErrorForGitHub(err)
b.WriteString(formattedError + "\n")
}
return b.String()
}
}

// formatErrorForGitHub formats an error for GitHub annotations.
func formatErrorForGitHub(err error) string {
switch e := err.(type) {
case *checkErr:
// GitHub expects the path to be relative to the repository root.
relativePath, _ := filepath.Rel(".", e.Path)
// Escape the message to prevent command injection.
message := strings.ReplaceAll(e.Reason, "\"", "\\\"")
return fmt.Sprintf("::error file=%s::%s", relativePath, message)
default:
return err.Error()
}
}

// Check checks that all files contain the required file header.
func (h Headers) Check() error {
mg.Deps(Headers.loadFile)
Expand Down Expand Up @@ -203,6 +222,50 @@ func (h Headers) Check() error {
return nil
}

// CheckNewFiles checks that all new files contain the required file header with the correct year.
func (h Headers) CheckNewFiles() error {
mg.Deps(Headers.loadFile)
base := "origin/" + os.Getenv("GITHUB_BASE_REF")

currentYear := time.Now().Year()
correctHeader := fmt.Sprintf("// Copyright © %d ", currentYear)

cmd := exec.Command("git", "diff", "--name-only", "--diff-filter=A", base)
output, err := cmd.Output()
if err != nil {
return fmt.Errorf("failed to get list of new files: %w", err)
}

var checkErrs errorSlice
for _, path := range strings.Split(strings.TrimSpace(string(output)), "\n") {
// Check if the file matches the HeaderRule before checking its header.
if rule := headerConfig.get(path); rule == nil {
continue // Skip files that do not match any HeaderRule.
}

fileContent, err := os.ReadFile(path)
if err != nil {
return fmt.Errorf("failed to read file %s: %w", path, err)
}

// Check if the first line of the file contains the correct copyright header.
scanner := bufio.NewScanner(bytes.NewReader(fileContent))
if scanner.Scan() {
firstLine := scanner.Text()
if !strings.Contains(firstLine, correctHeader) {
checkErrs = append(checkErrs, &checkErr{Path: path, Reason: "incorrect year in copyright header; should be " + strconv.Itoa(currentYear)})
}
} else {
checkErrs = append(checkErrs, &checkErr{Path: path, Reason: "empty file or missing header"})
}
}

if len(checkErrs) > 0 {
return checkErrs
}
return nil
}

func init() {
preCommitChecks = append(preCommitChecks, Headers.Check)
}

0 comments on commit 791a41c

Please sign in to comment.