From 15f0c263c8ddccc5a2f43d827637c68f25efe4d2 Mon Sep 17 00:00:00 2001 From: clawbot <35+clawbot@noreply.example.org> Date: Mon, 5 Oct 2026 23:33:39 +0000 Subject: [PATCH] Make the golangci-lint settings take effect: v2 config layout, linter pinned at v2.12.2 (closes #10) golangci-lint v2 ignores a top-level linters-settings block without a warning, so limits such as the 88-column line length were never applied. .golangci.yml is now the canonical copy from sneak/prompts, verbatim, which keeps its settings under linters.settings and also configures depguard and gomodguard_v2. golangci-lint is pinned at v2.12.2 by commit in the Dockerfile and script/bootstrap. The long lines in attrsum.go are rewrapped and one nolint directive moves to its own line; behaviour is unchanged. Model: opus-5-5 --- .golangci.yml | 94 ++++++++++++++++++++++++++++----- Dockerfile | 4 +- TODO.md | 5 ++ attrsum.go | 135 +++++++++++++++++++++++++++-------------------- script/bootstrap | 6 +-- 5 files changed, 169 insertions(+), 75 deletions(-) diff --git a/.golangci.yml b/.golangci.yml index 34a8e31..a7a74c2 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -1,32 +1,98 @@ version: "2" +# Config schema uses the golangci-lint v2 layout (settings live under +# linters.settings, not top-level linters-settings) so that the +# thresholds below are actually applied by golangci-lint >= v2. + run: timeout: 5m modules-download-mode: readonly linters: default: all + enable: + # Successor to the deprecated gomodguard. Named explicitly, rather than + # left to `default: all`, because it carries the module policy below. + - gomodguard_v2 disable: # Genuinely incompatible with project patterns - exhaustruct # Requires all struct fields - - depguard # Dependency allow/block lists - godot # Requires comments to end with periods - - wsl # Deprecated, replaced by wsl_v5 - wrapcheck # Too verbose for internal packages - varnamelen # Short names like db, id are idiomatic Go - -linters-settings: - lll: - line-length: 88 - funlen: - lines: 80 - statements: 50 - cyclop: - max-complexity: 15 - dupl: - threshold: 100 + # Deprecated: the warning is attached to the old name, so it is + # silenced by disabling that name, not by enabling the successor. + - wsl # Deprecated, replaced by wsl_v5 + - gomodguard # Deprecated, replaced by gomodguard_v2 + settings: + lll: + line-length: 88 + funlen: + lines: 80 + statements: 50 + cyclop: + max-complexity: 15 + dupl: + threshold: 100 + depguard: + # Test-support code must not be compiled into the shipped binary. A + # test-support package exists to hand a test privileges the program + # itself must never have, so a file that is not a test must not import + # one. Test files, and the files inside a package whose directory name + # ends in `test`, are where that code belongs, and are exempt. + # + # The deny list below is the one part of this file a repository is + # expected to extend, and the only part it may. depguard matches an + # import path against a list of prefixes, so it cannot be told "any path + # whose last segment ends in test"; a repository's own test-support + # packages have to be named here one at a time, by full import path, + # under a module path that differs from repository to repository. Add + # them; change nothing else. + rules: + test-support: + list-mode: lax + files: + - "$all" + - "!$test" + - "!**/*test/**" + deny: + - pkg: net/http/httptest + desc: >- + Test-support code belongs in test files and in packages whose + directory name ends in test, not in the shipped binary. + # Only decisions already recorded in the Go package defaults are + # listed here. Every entry matches the module path exactly. + gomodguard_v2: + blocked: + - module: github.com/rs/zerolog + recommendations: + - log/slog + reason: "Structured logging is stdlib log/slog." + # One entry per pre-fork module path, because the later releases + # are separate paths. A prefix match would be shorter but would + # also reach github.com/go-redis/redismock, the test double for + # the successor these entries recommend. + - module: github.com/go-redis/redis + recommendations: + - github.com/redis/go-redis/v9 + reason: "Pre-fork module; use the maintained go-redis v9." + - module: github.com/go-redis/redis/v7 + recommendations: + - github.com/redis/go-redis/v9 + reason: "Pre-fork module; use the maintained go-redis v9." + - module: github.com/go-redis/redis/v8 + recommendations: + - github.com/redis/go-redis/v9 + reason: "Pre-fork module; use the maintained go-redis v9." + - module: github.com/sergi/go-diff + recommendations: + - github.com/aymanbagabas/go-udiff + reason: "No unified diff output; use go-udiff." + - module: github.com/hexops/gotextdiff + recommendations: + - github.com/aymanbagabas/go-udiff + reason: "Unmaintained fork; use go-udiff." issues: - exclude-use-default: false max-issues-per-linter: 0 max-same-issues: 0 diff --git a/Dockerfile b/Dockerfile index f584e84..020f59f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -4,8 +4,8 @@ FROM golang@sha256:f6751d823c26342f9506c03797d2527668d095b0a15f1862cddb4d927a7a4 RUN apk add --no-cache git make gcc musl-dev binutils-gold -# golangci-lint v2.10.1 -RUN go install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@5d1e709b7be35cb2025444e19de266b056b7b7ee +# golangci-lint v2.12.2, 2026-10-05 +RUN go install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@c0d3ddc9cf3faa61a4e378e879ece580256d76e5 # goimports v0.42.0 RUN go install golang.org/x/tools/cmd/goimports@009367f5c17a8d4c45a961a3a509277190a9a6f0 diff --git a/TODO.md b/TODO.md index f07fc05..bc56565 100644 --- a/TODO.md +++ b/TODO.md @@ -24,6 +24,11 @@ today) with lint, fmt, fmt-check, check, and hooks targets. # Completed Steps +* 2026-10-05: golangci-lint settings take effect: canonical + `.golangci.yml` (v2 layout, settings under `linters.settings`), + golangci-lint pinned at v2.12.2 in `Dockerfile` and + `script/bootstrap`, and the code fixed for what the settings now + report (long lines in `attrsum.go` rewrapped) * 2026-10-05: `make try` runs on three small files in a temporary directory that it removes afterwards, also when a step fails, instead of on a fixed directory on one person's machine diff --git a/attrsum.go b/attrsum.go index fce1043..54f25be 100644 --- a/attrsum.go +++ b/attrsum.go @@ -76,7 +76,8 @@ func (s *Stats) Print(opts *options, operation string) { return } - fmt.Fprintf(os.Stderr, "\n%s complete: %d files processed, %d skipped, %d failed, %s bytes in %s\n", + fmt.Fprintf(os.Stderr, + "\n%s complete: %d files processed, %d skipped, %d failed, %s bytes in %s\n", operation, s.FilesProcessed, s.FilesSkipped, @@ -171,11 +172,15 @@ func expandPaths(args []string) ([]string, error) { } // processFunc processes a single path within a command's run loop. -type processFunc func(opts *options, path string, stats *Stats, bar *progressbar.ProgressBar) error +type processFunc func( + opts *options, path string, stats *Stats, bar *progressbar.ProgressBar, +) error // countAndBar counts the files under paths and returns a progress bar sized // to that total. It always returns either a non-nil bar or a non-nil error. -func countAndBar(opts *options, paths []string, desc string) (*progressbar.ProgressBar, error) { +func countAndBar( + opts *options, paths []string, desc string, +) (*progressbar.ProgressBar, error) { total, err := countFilesMultiple(opts, paths) if err != nil { return nil, err @@ -193,7 +198,9 @@ func finishBar(bar *progressbar.ProgressBar) { // runOverPaths runs process over each path, sharing the progress/stats // bookkeeping common to the sum-add, sum-update and clear commands. -func runOverPaths(opts *options, args []string, desc, op string, process processFunc) error { +func runOverPaths( + opts *options, args []string, desc, op string, process processFunc, +) error { paths, err := expandPaths(args) if err != nil { return err @@ -258,46 +265,54 @@ func newSumCmd(opts *options) *cobra.Command { return cmd } -func processSumAdd(opts *options, dir string, stats *Stats, bar *progressbar.ProgressBar) error { - return walkAndProcess(opts, dir, stats, bar, func(p string, info os.FileInfo, s *Stats) error { - if hasXattr(p, checksumKey) { - atomic.AddInt64(&s.FilesSkipped, 1) +func processSumAdd( + opts *options, dir string, stats *Stats, bar *progressbar.ProgressBar, +) error { + return walkAndProcess(opts, dir, stats, bar, + func(p string, info os.FileInfo, s *Stats) error { + if hasXattr(p, checksumKey) { + atomic.AddInt64(&s.FilesSkipped, 1) - return nil - } + return nil + } - err := writeChecksumAndTime(opts, p, info, s) - if err != nil { - atomic.AddInt64(&s.FilesFailed, 1) - - return err - } - - return nil - }) -} - -func processSumUpdate(opts *options, dir string, stats *Stats, bar *progressbar.ProgressBar) error { - return walkAndProcess(opts, dir, stats, bar, func(p string, info os.FileInfo, s *Stats) error { - t, err := readSumTime(p) - if err != nil || info.ModTime().After(t) { - werr := writeChecksumAndTime(opts, p, info, s) - if werr != nil { + err := writeChecksumAndTime(opts, p, info, s) + if err != nil { atomic.AddInt64(&s.FilesFailed, 1) - return werr + return err } return nil - } - - atomic.AddInt64(&s.FilesSkipped, 1) - - return nil - }) + }) } -func writeChecksumAndTime(opts *options, path string, info os.FileInfo, stats *Stats) error { +func processSumUpdate( + opts *options, dir string, stats *Stats, bar *progressbar.ProgressBar, +) error { + return walkAndProcess(opts, dir, stats, bar, + func(p string, info os.FileInfo, s *Stats) error { + t, err := readSumTime(p) + if err != nil || info.ModTime().After(t) { + werr := writeChecksumAndTime(opts, p, info, s) + if werr != nil { + atomic.AddInt64(&s.FilesFailed, 1) + + return werr + } + + return nil + } + + atomic.AddInt64(&s.FilesSkipped, 1) + + return nil + }) +} + +func writeChecksumAndTime( + opts *options, path string, info os.FileInfo, stats *Stats, +) error { // Record mtime before hashing to detect modifications during hash. mtimeBefore := info.ModTime() @@ -368,24 +383,27 @@ func newClearCmd(opts *options) *cobra.Command { } } -func processClear(opts *options, dir string, stats *Stats, bar *progressbar.ProgressBar) error { - return walkAndProcess(opts, dir, stats, bar, func(p string, info os.FileInfo, s *Stats) error { - cleared, err := clearOne(opts, p) - if err != nil { - atomic.AddInt64(&s.FilesFailed, 1) +func processClear( + opts *options, dir string, stats *Stats, bar *progressbar.ProgressBar, +) error { + return walkAndProcess(opts, dir, stats, bar, + func(p string, info os.FileInfo, s *Stats) error { + cleared, err := clearOne(opts, p) + if err != nil { + atomic.AddInt64(&s.FilesFailed, 1) - return err - } + return err + } - if cleared { - atomic.AddInt64(&s.FilesProcessed, 1) - atomic.AddInt64(&s.BytesProcessed, info.Size()) - } else { - atomic.AddInt64(&s.FilesSkipped, 1) - } + if cleared { + atomic.AddInt64(&s.FilesProcessed, 1) + atomic.AddInt64(&s.BytesProcessed, info.Size()) + } else { + atomic.AddInt64(&s.FilesSkipped, 1) + } - return nil - }) + return nil + }) } // clearOne removes both checksum xattrs from a single path, reporting @@ -433,7 +451,8 @@ func newCheckCmd(opts *options) *cobra.Command { return runCheck(opts, a, cont) }, } - cmd.Flags().BoolVar(&cont, "continue", false, "continue after errors and report each file") + cmd.Flags().BoolVar(&cont, "continue", false, + "continue after errors and report each file") return cmd } @@ -477,13 +496,16 @@ func runCheck(opts *options, args []string, cont bool) error { return finalErr } -func processCheck(opts *options, dir string, cont bool, stats *Stats, bar *progressbar.ProgressBar) error { +func processCheck( + opts *options, dir string, cont bool, stats *Stats, bar *progressbar.ProgressBar, +) error { // Track initial failed count to detect failures during this walk. initialFailed := atomic.LoadInt64(&stats.FilesFailed) - err := walkAndProcess(opts, dir, stats, bar, func(p string, _ os.FileInfo, s *Stats) error { - return checkOne(opts, p, cont, s) - }) + err := walkAndProcess(opts, dir, stats, bar, + func(p string, _ os.FileInfo, s *Stats) error { + return checkOne(opts, p, cont, s) + }) if err != nil { if errors.Is(err, errVerification) { return errVerification @@ -747,7 +769,8 @@ func hasXattr(path, key string) bool { func fileMultihash(path string) ([]byte, int64, error) { // The path is supplied by the operator as the tree to checksum; reading // it is the entire purpose of the tool. - f, err := os.Open(path) //nolint:gosec // G304: operator-specified path is the intended input + //nolint:gosec // G304: operator-specified path is the intended input + f, err := os.Open(path) if err != nil { return nil, 0, err } diff --git a/script/bootstrap b/script/bootstrap index ffcb29a..1d6577e 100755 --- a/script/bootstrap +++ b/script/bootstrap @@ -9,9 +9,9 @@ set -eu ROOT="$(cd "$(dirname "$0")/.." && pwd -P)" -# Pinned versions, 2026-07-07 (same pins as the Dockerfile) -# golangci-lint v2.10.1 -GOLANGCI_LINT_REF="github.com/golangci/golangci-lint/v2/cmd/golangci-lint@5d1e709b7be35cb2025444e19de266b056b7b7ee" +# Pinned versions, 2026-10-05 (same pins as the Dockerfile) +# golangci-lint v2.12.2 +GOLANGCI_LINT_REF="github.com/golangci/golangci-lint/v2/cmd/golangci-lint@c0d3ddc9cf3faa61a4e378e879ece580256d76e5" # goimports v0.42.0 GOIMPORTS_REF="golang.org/x/tools/cmd/goimports@009367f5c17a8d4c45a961a3a509277190a9a6f0"