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

@@ -28,6 +28,8 @@ type ReusableChunker struct {
}
// reusableChunkerPool pools ReusableChunker instances to avoid allocations.
//
//nolint:gochecknoglobals // process-wide object pool by design
var reusableChunkerPool = sync.Pool{
New: func() any {
return &ReusableChunker{}
@@ -36,6 +38,8 @@ var reusableChunkerPool = sync.Pool{
// bufferPools contains pools for different buffer sizes.
// Key is the buffer size.
//
//nolint:gochecknoglobals // process-wide buffer pools by design
var bufferPools = sync.Map{}
func getBuffer(size int) []byte {
@@ -46,9 +50,18 @@ func getBuffer(size int) []byte {
return &buf
},
})
pool := poolI.(*sync.Pool)
return *pool.Get().(*[]byte)
pool, ok := poolI.(*sync.Pool)
if !ok {
panic("bufferPools holds a non-pool value")
}
buf, ok := pool.Get().(*[]byte)
if !ok {
panic("buffer pool holds a non-buffer value")
}
return *buf
}
func putBuffer(buf []byte) {
@@ -56,7 +69,11 @@ func putBuffer(buf []byte) {
poolI, ok := bufferPools.Load(size)
if ok {
pool := poolI.(*sync.Pool)
pool, isPool := poolI.(*sync.Pool)
if !isPool {
panic("bufferPools holds a non-pool value")
}
b := buf[:size]
pool.Put(&b)
}
@@ -70,11 +87,21 @@ type FastCDCChunk struct {
Fingerprint uint64
}
// AcquireReusableChunker gets a chunker from the pool and initializes it for the given reader.
func AcquireReusableChunker(rd io.Reader, minSize, avgSize, maxSize int) *ReusableChunker {
c := reusableChunkerPool.Get().(*ReusableChunker)
// bufSizeFactor sizes the internal read buffer relative to the maximum
// chunk size so a full chunk plus read-ahead always fits.
const bufSizeFactor = 2
bufSize := maxSize * 2
// AcquireReusableChunker gets a chunker from the pool and initializes it
// for the given reader.
func AcquireReusableChunker(
rd io.Reader, minSize, avgSize, maxSize int,
) *ReusableChunker {
c, ok := reusableChunkerPool.Get().(*ReusableChunker)
if !ok {
panic("reusableChunkerPool holds a non-chunker value")
}
bufSize := maxSize * bufSizeFactor
// Reuse buffer if it's the right size, otherwise get a new one
if c.buf == nil || cap(c.buf) != bufSize {
@@ -113,37 +140,6 @@ func (c *ReusableChunker) Release() {
reusableChunkerPool.Put(c)
}
func (c *ReusableChunker) fillBuffer() error {
n := len(c.buf) - c.cursor
if n >= c.maxSize {
return nil
}
// Move all data after the cursor to the start of the buffer
copy(c.buf[:n], c.buf[c.cursor:])
c.cursor = 0
if c.eof {
c.buf = c.buf[:n]
return nil
}
// Restore buffer to full capacity for reading
c.buf = c.buf[:c.bufSize]
// Fill the rest of the buffer
m, err := io.ReadFull(c.rd, c.buf[n:])
if err == io.EOF || errors.Is(err, io.ErrUnexpectedEOF) {
c.buf = c.buf[:n+m]
c.eof = true
} else if err != nil {
return err
}
return nil
}
// Next returns the next chunk or io.EOF when done.
// The returned Data slice is only valid until the next call to Next.
func (c *ReusableChunker) Next() (FastCDCChunk, error) {
@@ -171,6 +167,37 @@ func (c *ReusableChunker) Next() (FastCDCChunk, error) {
return chunk, nil
}
func (c *ReusableChunker) fillBuffer() error {
n := len(c.buf) - c.cursor
if n >= c.maxSize {
return nil
}
// Move all data after the cursor to the start of the buffer
copy(c.buf[:n], c.buf[c.cursor:])
c.cursor = 0
if c.eof {
c.buf = c.buf[:n]
return nil
}
// Restore buffer to full capacity for reading
c.buf = c.buf[:c.bufSize]
// Fill the rest of the buffer
m, err := io.ReadFull(c.rd, c.buf[n:])
if errors.Is(err, io.EOF) || errors.Is(err, io.ErrUnexpectedEOF) {
c.buf = c.buf[:n+m]
c.eof = true
} else if err != nil {
return err
}
return nil
}
func (c *ReusableChunker) nextChunk(data []byte) (int, uint64) {
fp := uint64(0)
i := c.minSize
@@ -199,6 +226,8 @@ func (c *ReusableChunker) nextChunk(data []byte) (int, uint64) {
}
// 256 random uint64s for the rolling hash function (from FastCDC paper)
//
//nolint:gochecknoglobals // immutable FastCDC gear lookup table
var table = [256]uint64{
0xe80e8d55032474b3, 0x11b25b61f5924e15, 0x03aa5bd82a9eb669, 0xc45a153ef107a38c,
0xeac874b86f0f57b9, 0xa5ccedec95ec79c7, 0xe15a3320ad42ac0a, 0x5ed3583fa63cec15,