Update golangci-lint to v2.12.2 with canonical config (#62)
All checks were successful
check / check (push) Successful in 5s

Updates golangci-lint to v2.12.2 everywhere it is pinned and installs the canonical `.golangci.yml`, then remediates every finding the new linter/config surfaces so `make check` is green.

## Version bump

- `Dockerfile` lint stage: `golangci/golangci-lint:v2.11.3-alpine` -> `v2.12.2-alpine` (digest-pinned, date comment updated)
- `Makefile` `deps` target: `go install` moved from the old v1 module path at `@latest` to the pinned `github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.12.2`
- `.golangci.yml` replaced with the canonical config (v2 schema; settings under `linters.settings` so the thresholds actually apply; `default: all` with the standard six disables)
- `script/bootstrap` installs golangci-lint via the system package manager and carries no version pin, so it is unchanged
- CI (`.gitea/workflows/check.yml`) only runs `script/cibuild`, so it needed no change

## Lint remediation

The canonical config surfaced ~3,300 findings across 56k lines. All are fixed, behavior-preserving; incorporates and supersedes the per-package mechanical passes already merged to `main` (refs #61). Highlights:

- `err113`: dynamic errors replaced with package sentinels + `%w` wrapping; comparisons via `errors.Is`
- `goprintffuncname`: printf-style helpers renamed with an `f` suffix (`ui.Writer` message methods, `cli.ReportErrorf`, `database.Fatalf`) and all call sites updated
- `revive` stutter renames: `blob.Handler`, `blob.WithReader`, `blob.ChunkPosition`, `storage.URL`, `storage.Info`; missing doc comments added
- `contextcheck`/`noctx`: `context.Context` threaded through `blob.Packer` and the scanner call sites; context-aware `exec`/`sql` variants
- `funlen`/`cyclop`/`gocognit`/`dupl`: oversized and duplicated functions split into focused helpers (production and test code)
- tests: `t.Parallel()` added where safe (global logger init kept in the serial phase for `-race`), `t.TempDir()`/`t.Helper()` adopted, several suites converted to external test packages
- `gosec`: bounded integer conversions, `ReadHeaderTimeout` on the test HTTP server; remaining warnings suppressed per-site with justifications
- remaining `nolint` directives are rare, targeted, and each carries a reason (e.g. `nilnil` not-found contract in the repository layer, fx module globals, on-disk snake_case struct tags)
- removed the deprecated `log.LogOptions` alias (callers migrated to `log.Options`)

`make check` (tests with `-race`, lint, fmt-check) passes.

Co-authored-by: sneak <sneak@sneak.berlin>
Reviewed-on: #62
Co-authored-by: clawbot <clawbot@noreply.example.org>
Co-committed-by: clawbot <clawbot@noreply.example.org>
This commit was merged in pull request #62.
This commit is contained in:
2026-08-07 23:22:48 +02:00
committed by Jeffrey Paul
parent b87b72d4b9
commit cc58583130
126 changed files with 8184 additions and 5470 deletions

View File

@@ -10,16 +10,15 @@ import (
"github.com/spf13/afero"
"github.com/stretchr/testify/require"
"sneak.berlin/go/vaultik/internal/database"
"sneak.berlin/go/vaultik/internal/log"
"sneak.berlin/go/vaultik/internal/snapshot"
"sneak.berlin/go/vaultik/internal/types"
)
func setupExcludeTestFS(t *testing.T) afero.Fs {
func setupExcludeTestFS(t *testing.T) *afero.MemMapFs {
t.Helper()
// Create in-memory filesystem
fs := afero.NewMemMapFs()
fs := &afero.MemMapFs{}
// Create test directory structure:
// /backup/
@@ -77,12 +76,11 @@ func setupExcludeTestFS(t *testing.T) afero.Fs {
return fs
}
func createTestScanner(t *testing.T, fs afero.Fs, excludePatterns []string) (*snapshot.Scanner, *database.Repositories, func()) {
func createTestScanner(
t *testing.T, fs afero.Fs, excludePatterns []string,
) (*snapshot.Scanner, *database.Repositories, func()) {
t.Helper()
// Initialize logger
log.Initialize(log.Config{})
// Create test database
db, err := database.NewTestDB()
require.NoError(t, err)
@@ -95,8 +93,9 @@ func createTestScanner(t *testing.T, fs afero.Fs, excludePatterns []string) (*sn
Repositories: repos,
MaxBlobSize: 1024 * 1024,
CompressionLevel: 3,
AgeRecipients: []string{"age1ql3z7hjy54pw3hyww5ayyfg7zqgvc7w3j2elw8zmrj2kg5sfn9aqmcac8p"},
Exclude: excludePatterns,
AgeRecipients: []string{
"age1ql3z7hjy54pw3hyww5ayyfg7zqgvc7w3j2elw8zmrj2kg5sfn9aqmcac8p"},
Exclude: excludePatterns,
})
cleanup := func() {
@@ -106,14 +105,16 @@ func createTestScanner(t *testing.T, fs afero.Fs, excludePatterns []string) (*sn
return scanner, repos, cleanup
}
func createSnapshotRecord(t *testing.T, ctx context.Context, repos *database.Repositories, snapshotID string) {
func createSnapshotRecord(
ctx context.Context, t *testing.T, repos *database.Repositories, snapshotID string,
) {
t.Helper()
err := repos.WithTx(ctx, func(ctx context.Context, tx *sql.Tx) error {
snap := &database.Snapshot{
ID: types.SnapshotID(snapshotID),
Hostname: "test-host",
VaultikVersion: "test",
Hostname: testHost,
VaultikVersion: testVersion,
StartedAt: time.Now(),
CompletedAt: nil,
FileCount: 0,
@@ -130,6 +131,8 @@ func createSnapshotRecord(t *testing.T, ctx context.Context, repos *database.Rep
}
func TestExcludePatterns_ExcludeGitDirectory(t *testing.T) {
t.Parallel()
fs := setupExcludeTestFS(t)
scanner, repos, cleanup := createTestScanner(t, fs, []string{".git"})
@@ -138,13 +141,14 @@ func TestExcludePatterns_ExcludeGitDirectory(t *testing.T) {
require.NotNil(t, scanner)
ctx := context.Background()
createSnapshotRecord(t, ctx, repos, "test-snapshot")
createSnapshotRecord(ctx, t, repos, "test-snapshot")
result, err := scanner.Scan(ctx, "/backup", "test-snapshot")
require.NoError(t, err)
// Should have scanned files but NOT .git directory contents
// Expected: file1.txt, file2.log, src/main.go, src/test.go, node_modules/package/index.js,
// Expected: file1.txt, file2.log, src/main.go, src/test.go,
// node_modules/package/index.js,
// cache/temp.dat, build/output.bin, docs/readme.md, .DS_Store, thumbs.db,
// src/.hidden, important.log.bak
// Excluded: .git/config, .git/objects/pack/data.pack
@@ -152,6 +156,8 @@ func TestExcludePatterns_ExcludeGitDirectory(t *testing.T) {
}
func TestExcludePatterns_ExcludeByExtension(t *testing.T) {
t.Parallel()
fs := setupExcludeTestFS(t)
scanner, repos, cleanup := createTestScanner(t, fs, []string{"*.log"})
@@ -160,7 +166,7 @@ func TestExcludePatterns_ExcludeByExtension(t *testing.T) {
require.NotNil(t, scanner)
ctx := context.Background()
createSnapshotRecord(t, ctx, repos, "test-snapshot")
createSnapshotRecord(ctx, t, repos, "test-snapshot")
result, err := scanner.Scan(ctx, "/backup", "test-snapshot")
require.NoError(t, err)
@@ -171,6 +177,8 @@ func TestExcludePatterns_ExcludeByExtension(t *testing.T) {
}
func TestExcludePatterns_ExcludeNodeModules(t *testing.T) {
t.Parallel()
fs := setupExcludeTestFS(t)
scanner, repos, cleanup := createTestScanner(t, fs, []string{"node_modules"})
@@ -179,7 +187,7 @@ func TestExcludePatterns_ExcludeNodeModules(t *testing.T) {
require.NotNil(t, scanner)
ctx := context.Background()
createSnapshotRecord(t, ctx, repos, "test-snapshot")
createSnapshotRecord(ctx, t, repos, "test-snapshot")
result, err := scanner.Scan(ctx, "/backup", "test-snapshot")
require.NoError(t, err)
@@ -190,25 +198,32 @@ func TestExcludePatterns_ExcludeNodeModules(t *testing.T) {
}
func TestExcludePatterns_MultiplePatterns(t *testing.T) {
t.Parallel()
fs := setupExcludeTestFS(t)
scanner, repos, cleanup := createTestScanner(t, fs, []string{".git", "node_modules", "*.log", ".DS_Store", "thumbs.db", "cache", "build"})
scanner, repos, cleanup := createTestScanner(t, fs, []string{
".git", "node_modules", "*.log", ".DS_Store", "thumbs.db", "cache", "build"})
defer cleanup()
require.NotNil(t, scanner)
ctx := context.Background()
createSnapshotRecord(t, ctx, repos, "test-snapshot")
createSnapshotRecord(ctx, t, repos, "test-snapshot")
result, err := scanner.Scan(ctx, "/backup", "test-snapshot")
require.NoError(t, err)
// Should only have: file1.txt, src/main.go, src/test.go, docs/readme.md, src/.hidden, important.log.bak
// Excluded: .git/*, node_modules/*, *.log (file2.log), .DS_Store, thumbs.db, cache/*, build/*
// Should only have: file1.txt, src/main.go, src/test.go, docs/readme.md,
// src/.hidden, important.log.bak
// Excluded: .git/*, node_modules/*, *.log (file2.log), .DS_Store,
// thumbs.db, cache/*, build/*
require.Equal(t, 6, result.FilesScanned, "Should exclude multiple patterns")
}
func TestExcludePatterns_NoExclusions(t *testing.T) {
t.Parallel()
fs := setupExcludeTestFS(t)
scanner, repos, cleanup := createTestScanner(t, fs, []string{})
@@ -217,7 +232,7 @@ func TestExcludePatterns_NoExclusions(t *testing.T) {
require.NotNil(t, scanner)
ctx := context.Background()
createSnapshotRecord(t, ctx, repos, "test-snapshot")
createSnapshotRecord(ctx, t, repos, "test-snapshot")
result, err := scanner.Scan(ctx, "/backup", "test-snapshot")
require.NoError(t, err)
@@ -227,6 +242,8 @@ func TestExcludePatterns_NoExclusions(t *testing.T) {
}
func TestExcludePatterns_ExcludeHiddenFiles(t *testing.T) {
t.Parallel()
fs := setupExcludeTestFS(t)
scanner, repos, cleanup := createTestScanner(t, fs, []string{".*"})
@@ -235,17 +252,21 @@ func TestExcludePatterns_ExcludeHiddenFiles(t *testing.T) {
require.NotNil(t, scanner)
ctx := context.Background()
createSnapshotRecord(t, ctx, repos, "test-snapshot")
createSnapshotRecord(ctx, t, repos, "test-snapshot")
result, err := scanner.Scan(ctx, "/backup", "test-snapshot")
require.NoError(t, err)
// Should exclude: .git/*, .DS_Store, src/.hidden
// Total files: 14, excluded: 4 (.git/config, .git/objects/pack/data.pack, .DS_Store, src/.hidden)
require.Equal(t, 10, result.FilesScanned, "Should exclude hidden files and directories")
// Total files: 14, excluded: 4 (.git/config,
// .git/objects/pack/data.pack, .DS_Store, src/.hidden)
require.Equal(t, 10, result.FilesScanned,
"Should exclude hidden files and directories")
}
func TestExcludePatterns_DoubleStarGlob(t *testing.T) {
t.Parallel()
fs := setupExcludeTestFS(t)
scanner, repos, cleanup := createTestScanner(t, fs, []string{"**/*.pack"})
@@ -254,7 +275,7 @@ func TestExcludePatterns_DoubleStarGlob(t *testing.T) {
require.NotNil(t, scanner)
ctx := context.Background()
createSnapshotRecord(t, ctx, repos, "test-snapshot")
createSnapshotRecord(ctx, t, repos, "test-snapshot")
result, err := scanner.Scan(ctx, "/backup", "test-snapshot")
require.NoError(t, err)
@@ -265,6 +286,8 @@ func TestExcludePatterns_DoubleStarGlob(t *testing.T) {
}
func TestExcludePatterns_ExactFileName(t *testing.T) {
t.Parallel()
fs := setupExcludeTestFS(t)
scanner, repos, cleanup := createTestScanner(t, fs, []string{"thumbs.db", ".DS_Store"})
@@ -273,7 +296,7 @@ func TestExcludePatterns_ExactFileName(t *testing.T) {
require.NotNil(t, scanner)
ctx := context.Background()
createSnapshotRecord(t, ctx, repos, "test-snapshot")
createSnapshotRecord(ctx, t, repos, "test-snapshot")
result, err := scanner.Scan(ctx, "/backup", "test-snapshot")
require.NoError(t, err)
@@ -284,6 +307,8 @@ func TestExcludePatterns_ExactFileName(t *testing.T) {
}
func TestExcludePatterns_CaseSensitive(t *testing.T) {
t.Parallel()
// Pattern matching should be case-sensitive
fs := setupExcludeTestFS(t)
@@ -293,7 +318,7 @@ func TestExcludePatterns_CaseSensitive(t *testing.T) {
require.NotNil(t, scanner)
ctx := context.Background()
createSnapshotRecord(t, ctx, repos, "test-snapshot")
createSnapshotRecord(ctx, t, repos, "test-snapshot")
result, err := scanner.Scan(ctx, "/backup", "test-snapshot")
require.NoError(t, err)
@@ -304,6 +329,8 @@ func TestExcludePatterns_CaseSensitive(t *testing.T) {
}
func TestExcludePatterns_DirectoryWithTrailingSlash(t *testing.T) {
t.Parallel()
fs := setupExcludeTestFS(t)
// Some users might add trailing slashes to directory patterns
scanner, repos, cleanup := createTestScanner(t, fs, []string{"cache/", "build/"})
@@ -312,17 +339,20 @@ func TestExcludePatterns_DirectoryWithTrailingSlash(t *testing.T) {
require.NotNil(t, scanner)
ctx := context.Background()
createSnapshotRecord(t, ctx, repos, "test-snapshot")
createSnapshotRecord(ctx, t, repos, "test-snapshot")
result, err := scanner.Scan(ctx, "/backup", "test-snapshot")
require.NoError(t, err)
// Should exclude cache/temp.dat and build/output.bin
// Total files: 14, excluded: 2
require.Equal(t, 12, result.FilesScanned, "Should handle directory patterns with trailing slashes")
require.Equal(t, 12, result.FilesScanned,
"Should handle directory patterns with trailing slashes")
}
func TestExcludePatterns_PatternInSubdirectory(t *testing.T) {
t.Parallel()
fs := setupExcludeTestFS(t)
// Exclude .hidden file specifically in src directory
scanner, repos, cleanup := createTestScanner(t, fs, []string{"src/.hidden"})
@@ -331,7 +361,7 @@ func TestExcludePatterns_PatternInSubdirectory(t *testing.T) {
require.NotNil(t, scanner)
ctx := context.Background()
createSnapshotRecord(t, ctx, repos, "test-snapshot")
createSnapshotRecord(ctx, t, repos, "test-snapshot")
result, err := scanner.Scan(ctx, "/backup", "test-snapshot")
require.NoError(t, err)
@@ -350,13 +380,14 @@ func TestExcludePatterns_PatternInSubdirectory(t *testing.T) {
// file.txt (should be excluded with /projectname)
// otherproject/
// projectname/
// file.txt (should NOT be excluded with /projectname, only with projectname)
// file.txt (should NOT be excluded with /projectname,
// only with projectname)
// src/
// file.go
func setupAnchoredTestFS(t *testing.T) afero.Fs {
func setupAnchoredTestFS(t *testing.T) *afero.MemMapFs {
t.Helper()
fs := afero.NewMemMapFs()
fs := &afero.MemMapFs{}
files := map[string]string{
"/backup/projectname/file.txt": "root project file",
@@ -381,6 +412,8 @@ func setupAnchoredTestFS(t *testing.T) afero.Fs {
}
func TestExcludePatterns_AnchoredPattern(t *testing.T) {
t.Parallel()
// Pattern starting with / should only match from root of source dir
fs := setupAnchoredTestFS(t)
@@ -390,7 +423,7 @@ func TestExcludePatterns_AnchoredPattern(t *testing.T) {
require.NotNil(t, scanner)
ctx := context.Background()
createSnapshotRecord(t, ctx, repos, "test-snapshot")
createSnapshotRecord(ctx, t, repos, "test-snapshot")
result, err := scanner.Scan(ctx, "/backup", "test-snapshot")
require.NoError(t, err)
@@ -398,10 +431,13 @@ func TestExcludePatterns_AnchoredPattern(t *testing.T) {
// /projectname should ONLY exclude /backup/projectname/file.txt (1 file)
// /backup/otherproject/projectname/file.txt should NOT be excluded
// Total files: 4, excluded: 1
require.Equal(t, 3, result.FilesScanned, "Anchored pattern /projectname should only match at root of source dir")
require.Equal(t, 3, result.FilesScanned,
"Anchored pattern /projectname should only match at root of source dir")
}
func TestExcludePatterns_UnanchoredPattern(t *testing.T) {
t.Parallel()
// Pattern without leading / should match anywhere in path
fs := setupAnchoredTestFS(t)
@@ -411,7 +447,7 @@ func TestExcludePatterns_UnanchoredPattern(t *testing.T) {
require.NotNil(t, scanner)
ctx := context.Background()
createSnapshotRecord(t, ctx, repos, "test-snapshot")
createSnapshotRecord(ctx, t, repos, "test-snapshot")
result, err := scanner.Scan(ctx, "/backup", "test-snapshot")
require.NoError(t, err)
@@ -420,10 +456,13 @@ func TestExcludePatterns_UnanchoredPattern(t *testing.T) {
// - /backup/projectname/file.txt
// - /backup/otherproject/projectname/file.txt
// Total files: 4, excluded: 2
require.Equal(t, 2, result.FilesScanned, "Unanchored pattern should match anywhere in path")
require.Equal(t, 2, result.FilesScanned,
"Unanchored pattern should match anywhere in path")
}
func TestExcludePatterns_AnchoredPatternWithGlob(t *testing.T) {
t.Parallel()
// Anchored pattern with glob
fs := setupAnchoredTestFS(t)
@@ -433,7 +472,7 @@ func TestExcludePatterns_AnchoredPatternWithGlob(t *testing.T) {
require.NotNil(t, scanner)
ctx := context.Background()
createSnapshotRecord(t, ctx, repos, "test-snapshot")
createSnapshotRecord(ctx, t, repos, "test-snapshot")
result, err := scanner.Scan(ctx, "/backup", "test-snapshot")
require.NoError(t, err)
@@ -444,6 +483,8 @@ func TestExcludePatterns_AnchoredPatternWithGlob(t *testing.T) {
}
func TestExcludePatterns_AnchoredPatternFile(t *testing.T) {
t.Parallel()
// Anchored pattern for exact file at root
fs := setupAnchoredTestFS(t)
@@ -453,7 +494,7 @@ func TestExcludePatterns_AnchoredPatternFile(t *testing.T) {
require.NotNil(t, scanner)
ctx := context.Background()
createSnapshotRecord(t, ctx, repos, "test-snapshot")
createSnapshotRecord(ctx, t, repos, "test-snapshot")
result, err := scanner.Scan(ctx, "/backup", "test-snapshot")
require.NoError(t, err)
@@ -461,10 +502,13 @@ func TestExcludePatterns_AnchoredPatternFile(t *testing.T) {
// /file.txt should ONLY exclude /backup/file.txt
// NOT /backup/projectname/file.txt or /backup/otherproject/projectname/file.txt
// Total files: 4, excluded: 1
require.Equal(t, 3, result.FilesScanned, "Anchored pattern for file should only match at root")
require.Equal(t, 3, result.FilesScanned,
"Anchored pattern for file should only match at root")
}
func TestExcludePatterns_UnanchoredPatternFile(t *testing.T) {
t.Parallel()
// Unanchored pattern for file should match anywhere
fs := setupAnchoredTestFS(t)
@@ -474,7 +518,7 @@ func TestExcludePatterns_UnanchoredPatternFile(t *testing.T) {
require.NotNil(t, scanner)
ctx := context.Background()
createSnapshotRecord(t, ctx, repos, "test-snapshot")
createSnapshotRecord(ctx, t, repos, "test-snapshot")
result, err := scanner.Scan(ctx, "/backup", "test-snapshot")
require.NoError(t, err)
@@ -484,5 +528,6 @@ func TestExcludePatterns_UnanchoredPatternFile(t *testing.T) {
// - /backup/projectname/file.txt
// - /backup/otherproject/projectname/file.txt
// Total files: 4, excluded: 3
require.Equal(t, 1, result.FilesScanned, "Unanchored pattern for file should match anywhere")
require.Equal(t, 1, result.FilesScanned,
"Unanchored pattern for file should match anywhere")
}