Repository: kopia/kopia
Pull request #4500 (2025-04-13 19:58:46)

chore(ci): bump golangci-lint to v2.1.2

jkowalski: thanks for doing this!
julio-lopez: @mmorel-35 To facilitate and speed up the review process, please push separate commits with the new changes when this PR, instead of squashing everything into a single commit.
All the commits will be squashed at the end before merging the PR.

Patch:
diff --git a/.golangci.yml b/.golangci.yml
index 92be8a1e..21e8556e 100644
--- a/.golangci.yml
+++ b/.golangci.yml
@@ -1,87 +1,81 @@
-linters-settings:
-  cyclop:
-    max-complexity: 20
-    skip-tests: true
-  exhaustive:
-    # indicates that switch statements are to be considered exhaustive if a
-    # 'default' case is present, even if all enum members aren't listed in the
-    # switch
-    default-signifies-exhaustive: true
-  forbidigo:
-    forbid:
-    - filepath.IsAbs # use ospath.IsAbs which supports windows UNC paths
-    - ioutil.Discard # use io.Discard
-    - ioutil.NopCloser # use io.NopCloser
-    - ioutil.ReadAll # use io.ReadAll
-    - ioutil.ReadDir # use os.ReadDir
-    - ioutil.ReadFile # use os.ReadFile
-    - ioutil.TempDir # use os.MkdirTemp
-    - ioutil.TempFile # use os.CreateTemp
-    - ioutil.WriteFile # use os.WriteFile
-    - time.Now # do not use outside of 'clock' and 'timetrack' packages use clock.Now or timetrack.StartTimer
-    - time.Since # use timetrack.Timer.Elapsed()
-    - time.Until # never use this
-    - Envar\(\" # do not use envar literals, always wrap with EnvName()
-  funlen:
-    lines: 100
-    statements: 60
-  gci:
-    sections:
-    - standard
-    - default
-    - prefix(github.com/kopia/kopia)
-  gocognit:
-    min-complexity: 40
-  goconst:
-    min-len: 5
-    min-occurrences: 3
-  gocritic:
-    settings:
-      hugeParam:
-        # size in bytes that makes the warning trigger (default 80)
-        sizeThreshold: 160
-    enabled-tags:
-      - diagnostic
-      - experimental
-      - opinionated
-      - performance
-      - style
-    disabled-checks:
-      - whyNoLint
-      - wrapperFunc
-  gocyclo:
-    min-complexity: 15
-  goimports:
-    local-prefixes: github.com/kopia/kopia
-  govet:
-    shadow: true
-    settings:
-      printf:
-        funcs:
-          - (*go.uber.org/zap.SugaredLogger).Debugf
-          - (*go.uber.org/zap.SugaredLogger).Infof
-          - (*go.uber.org/zap.SugaredLogger).Warnf
-          - (*go.uber.org/zap.SugaredLogger).Errorf
-  lll:
-    line-length: 256
-  loggercheck:
-    zap: true
-    no-printf-like: true
-    rules:
-      - (*go.uber.org/zap.SugaredLogger).With
-  maligned:
-    suggest-new: true
-  misspell:
-    locale: US
-
+version: "2"
 linters:
-  enable-all: true
+  settings:
+    cyclop:
+      max-complexity: 20
+    exhaustive:
+      # indicates that switch statements are to be considered exhaustive if a
+      # 'default' case is present, even if all enum members aren't listed in the
+      # switch
+      default-signifies-exhaustive: true
+    forbidigo:
+      forbid:
+        - pattern: filepath.IsAbs # use ospath.IsAbs which supports windows UNC paths
+        - pattern: ioutil.Discard # use io.Discard
+        - pattern: ioutil.NopCloser # use io.NopCloser
+        - pattern: ioutil.ReadAll # use io.ReadAll
+        - pattern: ioutil.ReadDir # use os.ReadDir
+        - pattern: ioutil.ReadFile # use os.ReadFile
+        - pattern: ioutil.TempDir # use os.MkdirTemp
+        - pattern: ioutil.TempFile # use os.CreateTemp
+        - pattern: ioutil.WriteFile # use os.WriteFile
+        - pattern: time.Now # do not use outside of 'clock' and 'timetrack' packages use clock.Now or timetrack.StartTimer
+        - pattern: time.Since # use timetrack.Timer.Elapsed()
+        - pattern: time.Until # never use this
+        - pattern: Envar\(\" # do not use envar literals, always wrap with EnvName()
+    funlen:
+      lines: 100
+      statements: 60
+    gocognit:
+      min-complexity: 40
+    goconst:
+      min-len: 5
+      min-occurrences: 3
+    gocritic:
+      settings:
+        hugeParam:
+          # size in bytes that makes the warning trigger (default 80)
+          sizeThreshold: 160
+      enabled-tags:
+        - diagnostic
+        - experimental
+        - opinionated
+        - performance
+        - style
+      disabled-checks:
+        - whyNoLint
+        - wrapperFunc
+    gocyclo:
+      min-complexity: 15
+    govet:
+      enable-all: true
+      settings:
+        printf:
+          funcs:
+            - (*go.uber.org/zap.SugaredLogger).Debugf
+            - (*go.uber.org/zap.SugaredLogger).Infof
+            - (*go.uber.org/zap.SugaredLogger).Warnf
+            - (*go.uber.org/zap.SugaredLogger).Errorf
+    lll:
+      line-length: 256
+    loggercheck:
+      zap: true
+      no-printf-like: true
+      rules:
+        - (*go.uber.org/zap.SugaredLogger).With
+    misspell:
+      locale: US
+
+  default: all
   disable:
+    - gochecknoglobals
     - depguard
     - exhaustruct
-    - exportloopref
+    - exptostd
+    - forcetypeassert
+    - funcorder
     - gochecknoinits
-    - gci
+    - importas
     - ireturn # this one may be interesting to control allocations
     - gosmopolitan
     - musttag
@@ -90,90 +84,119 @@ linters:
     - nonamedreturns
     - paralleltest
     - prealloc
+    - protogetter
     - rowserrcheck
     - sqlclosecheck
     - tagalign
     - tagliatelle
     - testpackage
     - tparallel
+    - usetesting
     - varnamelen # this one may be interesting, but too much churn
     - wastedassign
     - whitespace
 
-issues:
-  exclude-use-default: false
-  exclude-rules:
-    - path: \.pb\.go
-      linters:
-      - gofmt
-    - path: reporter.go
-      linters:
-      - musttag
-    - path: _test\.go|testing|tests|test_env|fshasher|fault
-      linters:
-      - contextcheck
-      - errcheck
-      - errchkjson
-      - forcetypeassert
-      - funlen
-      - gochecknoglobals
-      - gocognit
-      - goconst
-      - mnd
-      - gosec
-      - musttag
-      - nestif
-      - perfsprint
-      - revive
-      - nolintlint
-      - wrapcheck
-      - wsl
-    - text: "log is a global variable"
-      linters:
-      - gochecknoglobals
-    - text: "tracer is a global variable"
-      linters:
-      - gochecknoglobals
-    # always allow ctx even when unused
-    - text: "unused-parameter: parameter 'ctx' seems to be unused"
-      linters:
-      - revive
-    - text: "Magic number: 1e"
-      linters:
-      - mnd
-    - text: "unnecessaryDefer"
-      linters:
-      - gocritic
-    - text: "filepathJoin"
-      linters:
-      - gocritic
-    - text: "weak cryptographic primitive"
-      linters:
-        - gosec
-    - text: "lines are duplicate of"
-      linters:
-        - dupl
-    - text: "Line contains TODO"
-      linters:
-        - godox
-    - text: "error returned from external package is unwrapped: sig: func github.com/kopia/kopia/internal/retry"
-      linters:
-        - wrapcheck
-    - text: "error returned from external package is unwrapped: sig: func github.com/kopia/kopia/internal/connection.UsingConnection"
-      linters:
-        - wrapcheck
-    - text: timeCmpSimplify
-      linters:
-        - gocritic
-    - text: ".*Magic number\\: [01],"
-      linters:
-        - mnd
-    - text: "Errors unhandled"
-      linters:
-        - gosec
-    - text: "unwrapped: sig: func github.com/kopia/kopia/fs.GetAllEntries"
-      linters:
-        - wrapcheck
-    - text: "float-compare: use require.InEpsilon"
-      linters:
-        - testifylint
+  exclusions:
+    generated: lax
+    rules:
+      - path: reporter.go
+        linters:
+          - musttag        
+      - path: _test\.go|testing|tests|test_env|fshasher|fault 
+        linters:
+          - contextcheck
+          - errcheck
+          - errchkjson
+          - forcetypeassert
+          - funlen
+          - gochecknoglobals
+          - gocognit
+          - goconst
+          - gosec
+          - mnd
+          - musttag
+          - nestif
+          - nolintlint
+          - perfsprint
+          - revive
+          - wrapcheck
+          - wsl
+      - text: "log is a global variable"
+        linters:
+          - gochecknoglobals
+      - text: "tracer is a global variable"
+        linters:
+          - gochecknoglobals
+        # always allow ctx even when unused
+      - text: "unused-parameter: parameter 'ctx' seems to be unused"
+        linters:
+          - revive
+      - text: "Magic number: 1e"
+        linters:
+          - mnd
+      - text: "unnecessaryDefer"
+        linters:
+          - gocritic
+      - text: "filepathJoin"
+        linters:
+          - gocritic
+      - text: "weak cryptographic primitive"
+        linters:
+          - gosec
+      - text: "lines are duplicate of"
+        linters:
+          - dupl
+      - text: "Line contains TODO"
+        linters:
+          - godox
+      - text: "error returned from external package is unwrapped: sig: func github.com/kopia/kopia/internal/retry"
+        linters:
+          - wrapcheck
+      - text: "error returned from external package is unwrapped: sig: func github.com/kopia/kopia/internal/connection.UsingConnection"
+        linters:
+          - wrapcheck
+      - text: timeCmpSimplify
+        linters:
+          - gocritic
+      - text: ".*Magic number\\: [01],"
+        linters:
+          - mnd
+      - text: "Errors unhandled"
+        linters:
+          - gosec
+      - text: "unwrapped: sig: func github.com/kopia/kopia/fs.GetAllEntries"
+        linters:
+          - wrapcheck
+      - text: "float-compare: use require.InEpsilon"
+        linters:
+          - testifylint
+      - text: "fieldalignment:"
+        linters:
+          - govet
+      - text: "shadow:"
+        linters:
+          - govet
+      - text: "unnamedResult:"
+        linters:
+          - gocritic
+      - linters:
+          - cyclop
+        path: (.+)_test\.go
+    warn-unused: true
+
+formatters:
+  enable:
+    - gci
+    - gofumpt
+  settings:
+    gci:
+      sections:
+        - standard
+        - default
+        - prefix(github.com/kopia/kopia)
+
+output:
+  show-stats: false
+
+run:
+  timeout: 20m
diff --git a/cli/command_notification_configure_common.go b/cli/command_notification_configure_common.go
index 75a1714c..ab7ba48a 100644
--- a/cli/command_notification_configure_common.go
+++ b/cli/command_notification_configure_common.go
@@ -3,10 +3,9 @@
 import (
 	"context"
 
-	"golang.org/x/exp/maps"
-
 	"github.com/alecthomas/kingpin/v2"
 	"github.com/pkg/errors"
+	"golang.org/x/exp/maps"
 
 	"github.com/kopia/kopia/notification"
 	"github.com/kopia/kopia/notification/notifyprofile"
diff --git a/cli/command_policy_export.go b/cli/command_policy_export.go
index a44f6a43..1bfa92e1 100644
--- a/cli/command_policy_export.go
+++ b/cli/command_policy_export.go
@@ -53,7 +53,7 @@ func (c *commandPolicyExport) run(ctx context.Context, rep repo.Repository) erro
 
 	policies := make(map[string]*policy.Policy)
 
-	if c.policyTargetFlags.global || len(c.policyTargetFlags.targets) > 0 {
+	if c.global || len(c.targets) > 0 {
 		targets, err := c.policyTargets(ctx, rep)
 		if err != nil {
 			return err
diff --git a/cli/command_policy_import.go b/cli/command_policy_import.go
index f9012d39..df55156c 100644
--- a/cli/command_policy_import.go
+++ b/cli/command_policy_import.go
@@ -67,7 +67,7 @@ func (c *commandPolicyImport) run(ctx context.Context, rep repo.RepositoryWriter
 
 	var targetLimit []snapshot.SourceInfo
 
-	if c.policyTargetFlags.global || len(c.policyTargetFlags.targets) > 0 {
+	if c.global || len(c.targets) > 0 {
 		targetLimit, err = c.policyTargets(ctx, rep)
 		if err != nil {
 			return err
diff --git a/cli/command_server_tls.go b/cli/command_server_tls.go
index 019af0c0..a69d62ad 100644
--- a/cli/command_server_tls.go
+++ b/cli/command_server_tls.go
@@ -17,9 +17,8 @@
 	"strings"
 	"time"
 
-	"github.com/pkg/errors"
-
 	"github.com/coreos/go-systemd/v22/activation"
+	"github.com/pkg/errors"
 
 	"github.com/kopia/kopia/internal/tlsutil"
 )
diff --git a/internal/jsonencoding/jsonencoding.go b/internal/jsonencoding/jsonencoding.go
index 3c369c06..dec9103e 100644
--- a/internal/jsonencoding/jsonencoding.go
+++ b/internal/jsonencoding/jsonencoding.go
@@ -9,7 +9,7 @@
 )
 
 // Duration adds text/json (un)marshaling functions to time.Duration.
-type Duration struct { //nolint: recvcheck
+type Duration struct {
 	time.Duration
 }
 
diff --git a/internal/ownwrites/ownwrites.go b/internal/ownwrites/ownwrites.go
index a2a0e2f9..3e5312d2 100644
--- a/internal/ownwrites/ownwrites.go
+++ b/internal/ownwrites/ownwrites.go
@@ -105,7 +105,7 @@ func (s *CacheStorage) ListBlobs(ctx context.Context, prefix blob.ID, cb func(bl
 	// This will be slower than ListBlobs() but we're expecting this set to be empty
 	// most of the time because eventual consistency effects don't show up too often.
 	for blobID := range cachedCreatedSet {
-		bm, err := s.Storage.GetMetadata(ctx, blobID)
+		bm, err := s.GetMetadata(ctx, blobID)
 		if errors.Is(err, blob.ErrBlobNotFound) {
 			// blob did not exist in storage, but we had the marker in cache, ignore.
 			continue
diff --git a/internal/providervalidation/providervalidation.go b/internal/providervalidation/providervalidation.go
index 37d2ebb8..8106efe8 100644
--- a/internal/providervalidation/providervalidation.go
+++ b/internal/providervalidation/providervalidation.go
@@ -19,9 +19,8 @@
 	"github.com/kopia/kopia/internal/clock"
 	"github.com/kopia/kopia/internal/gather"
 	"github.com/kopia/kopia/repo/blob"
-	"github.com/kopia/kopia/repo/logging"
-
 	loggingwrapper "github.com/kopia/kopia/repo/blob/logging"
+	"github.com/kopia/kopia/repo/logging"
 )
 
 // Options provides options for provider validation.
@@ -447,7 +446,7 @@ func (c *concurrencyTest) getMetadataWorker(ctx context.Context, worker int) fun
 	}
 }
 
-func (c *concurrencyTest) listBlobWorker(ctx context.Context, worker int) func() error {
+func (c *concurrencyTest) listBlobWorker(_ context.Context, worker int) func() error {
 	// TODO: implement me
 	_ = worker
 
diff --git a/internal/server/api_repo.go b/internal/server/api_repo.go
index 46b72114..04b38de0 100644
--- a/internal/server/api_repo.go
+++ b/internal/server/api_repo.go
@@ -328,7 +328,7 @@ func handleRepoSetThrottle(ctx context.Context, rc requestContext) (interface{},
 
 func (s *Server) getConnectOptions(cliOpts repo.ClientOptions) *repo.ConnectOptions {
 	o := *s.options.ConnectOptions
-	o.ClientOptions = o.ClientOptions.Override(cliOpts)
+	o.ClientOptions = o.Override(cliOpts)
 
 	return &o
 }
diff --git a/internal/server/grpc_session.go b/internal/server/grpc_session.go
index e532b288..0612fb22 100644
--- a/internal/server/grpc_session.go
+++ b/internal/server/grpc_session.go
@@ -41,8 +41,8 @@ type grpcServerState struct {
 
 // send sends the provided session response with the provided request ID.
 func (s *Server) send(srv grpcapi.KopiaRepository_SessionServer, requestID int64, resp *grpcapi.SessionResponse) error {
-	s.grpcServerState.sendMutex.Lock()
-	defer s.grpcServerState.sendMutex.Unlock()
+	s.sendMutex.Lock()
+	defer s.sendMutex.Unlock()
 
 	resp.RequestId = requestID
 
@@ -125,12 +125,12 @@ func (s *Server) Session(srv grpcapi.KopiaRepository_SessionServer) error {
 			}
 
 			// enforce limit on concurrent handling
-			if err := s.grpcServerState.sem.Acquire(ctx, 1); err != nil {
+			if err := s.sem.Acquire(ctx, 1); err != nil {
 				return errors.Wrap(err, "unable to acquire semaphore")
 			}
 
 			go func() {
-				defer s.grpcServerState.sem.Release(1)
+				defer s.sem.Release(1)
 
 				s.handleSessionRequest(ctx, dw, authz, usernameAtHostname, req, func(resp *grpcapi.SessionResponse) {
 					if err := s.send(srv, req.GetRequestId(), resp); err != nil {
diff --git a/internal/tempfile/tempfile_linux.go b/internal/tempfile/tempfile_linux.go
index 5481b127..a0694f92 100644
--- a/internal/tempfile/tempfile_linux.go
+++ b/internal/tempfile/tempfile_linux.go
@@ -10,6 +10,8 @@
 
 // Create creates a temporary file that will be automatically deleted on close.
 func Create(dir string) (*os.File, error) {
+	dir = tempDirOr(dir)
+
 	// on reasonably modern Linux (3.11 and above) O_TMPFILE is supported,
 	// which creates invisible, unlinked file in a given directory.
 	fd, err := unix.Open(dir, unix.O_RDWR|unix.O_TMPFILE|unix.O_CLOEXEC, permissions)
diff --git a/internal/wcmatch/wcmatch.go b/internal/wcmatch/wcmatch.go
index 4197985f..aca5b788 100644
--- a/internal/wcmatch/wcmatch.go
+++ b/internal/wcmatch/wcmatch.go
@@ -389,11 +389,7 @@ func doMatch(tokens []token, text []rune, ignoreCase bool) matchResult {
 				break
 			}
 
-			for {
-				if t.eos() {
-					break
-				}
-
+			for !t.eos() {
 				matchResult := doMatch(tokens[pi+1:], text[t.pos:], ignoreCase)
 				if matchResult != wcNoMatch {
 					if !token.doubleStar || matchResult != wcAbortToDoubleStar {
diff --git a/internal/webdavmount/webdavmount.go b/internal/webdavmount/webdavmount.go
index 57fa0e63..9bbc1704 100644
--- a/internal/webdavmount/webdavmount.go
+++ b/internal/webdavmount/webdavmount.go
@@ -26,7 +26,7 @@
 type webdavFile struct {
 	// webdavFile implements webdav.File but needs context
 	// +checklocks:mu
-	ctx context.Context //nolint:containedctx
+	ctx context.Context
 
 	entry fs.File
 
@@ -100,7 +100,7 @@ func (f *webdavFile) Close() error {
 
 type webdavDir struct {
 	// webdavDir implements webdav.File but needs context
-	ctx context.Context //nolint:containedctx
+	ctx context.Context
 
 	w    *webdavFS
 	info os.FileInfo
diff --git a/repo/api_server_repository.go b/repo/api_server_repository.go
index 11cc4048..b1031ebd 100644
--- a/repo/api_server_repository.go
+++ b/repo/api_server_repository.go
@@ -22,7 +22,7 @@ type APIServerInfo struct {
 func ConnectAPIServer(ctx context.Context, configFile string, si *APIServerInfo, password string, opt *ConnectOptions) error {
 	lc := LocalConfig{
 		APIServer:     si,
-		ClientOptions: opt.ClientOptions.ApplyDefaults(ctx, "API Server: "+si.BaseURL),
+		ClientOptions: opt.ApplyDefaults(ctx, "API Server: "+si.BaseURL),
 	}
 
 	if err := setupCachingOptionsWithDefaults(ctx, configFile, &lc, &opt.CachingOptions, []byte(si.BaseURL)); err != nil {
diff --git a/repo/blob/azure/azure_pit.go b/repo/blob/azure/azure_pit.go
index a5f15c86..6207f7f6 100644
--- a/repo/blob/azure/azure_pit.go
+++ b/repo/blob/azure/azure_pit.go
@@ -180,7 +180,7 @@ func (az *azPointInTimeStorage) isAzureDeleteMarker(it *azblobmodels.BlobItem) b
 // maybePointInTimeStore wraps s with a point-in-time store when s is versioned
 // and a point-in-time value is specified. Otherwise, s is returned.
 func maybePointInTimeStore(ctx context.Context, s *azStorage, pointInTime *time.Time) (blob.Storage, error) {
-	if pit := s.Options.PointInTime; pit == nil || pit.IsZero() {
+	if pit := s.PointInTime; pit == nil || pit.IsZero() {
 		return s, nil
 	}
 
diff --git a/repo/blob/azure/azure_storage.go b/repo/blob/azure/azure_storage.go
index 42777975..4c4379ac 100644
--- a/repo/blob/azure/azure_storage.go
+++ b/repo/blob/azure/azure_storage.go
@@ -239,7 +239,7 @@ func (az *azStorage) ConnectionInfo() blob.ConnectionInfo {
 }
 
 func (az *azStorage) DisplayName() string {
-	return fmt.Sprintf("Azure: %v", az.Options.Container)
+	return fmt.Sprintf("Azure: %v", az.Container)
 }
 
 func (az *azStorage) getBlobName(it *azblobmodels.BlobItem) blob.ID {
diff --git a/repo/blob/config.go b/repo/blob/config.go
index 4ec6da65..215fe128 100644
--- a/repo/blob/config.go
+++ b/repo/blob/config.go
@@ -7,8 +7,6 @@
 )
 
 // ConnectionInfo represents JSON-serializable configuration of a blob storage.
-//
-//nolint:recvcheck
 type ConnectionInfo struct {
 	Type   string
 	Config interface{}
diff --git a/repo/blob/filesystem/filesystem_storage.go b/repo/blob/filesystem/filesystem_storage.go
index ed256157..4e9a1e85 100644
--- a/repo/blob/filesystem/filesystem_storage.go
+++ b/repo/blob/filesystem/filesystem_storage.go
@@ -292,7 +292,7 @@ func (fs *fsStorage) TouchBlob(ctx context.Context, blobID blob.ID, threshold ti
 
 	//nolint:wrapcheck,forcetypeassert
 	err := retry.WithExponentialBackoffNoValue(ctx, "TouchBlob", func() error {
-		_, path, err := fs.Storage.GetShardedPathAndFilePath(ctx, blobID)
+		_, path, err := fs.GetShardedPathAndFilePath(ctx, blobID)
 		if err != nil {
 			return errors.Wrap(err, "error getting sharded path")
 		}
diff --git a/repo/blob/gcs/gcs_pit.go b/repo/blob/gcs/gcs_pit.go
index 6a552744..4b75435f 100644
--- a/repo/blob/gcs/gcs_pit.go
+++ b/repo/blob/gcs/gcs_pit.go
@@ -120,7 +120,7 @@ func getOlderThan(vs []versionMetadata, t time.Time) []versionMetadata {
 // maybePointInTimeStore wraps s with a point-in-time store when s is versioned
 // and a point-in-time value is specified. Otherwise s is returned.
 func maybePointInTimeStore(ctx context.Context, gcs *gcsStorage, pointInTime *time.Time) (blob.Storage, error) {
-	if pit := gcs.Options.PointInTime; pit == nil || pit.IsZero() {
+	if pit := gcs.PointInTime; pit == nil || pit.IsZero() {
 		return gcs, nil
 	}
 
diff --git a/repo/blob/gcs/gcs_storage.go b/repo/blob/gcs/gcs_storage.go
index 6b8ed3b3..9ad8faa7 100644
--- a/repo/blob/gcs/gcs_storage.go
+++ b/repo/blob/gcs/gcs_storage.go
@@ -137,11 +137,11 @@ func (gcs *gcsStorage) PutBlob(ctx context.Context, b blob.ID, data blob.Bytes,
 	writer := obj.NewWriter(ctx)
 	writer.ChunkSize = writerChunkSize
 	writer.ContentType = "application/x-kopia"
-	writer.ObjectAttrs.Metadata = timestampmeta.ToMap(opts.SetModTime, timeMapKey)
+	writer.Metadata = timestampmeta.ToMap(opts.SetModTime, timeMapKey)
 
 	if opts.RetentionPeriod != 0 {
 		retainUntilDate := clock.Now().Add(opts.RetentionPeriod).UTC()
-		writer.ObjectAttrs.Retention = &gcsclient.ObjectRetention{
+		writer.Retention = &gcsclient.ObjectRetention{
 			Mode:        string(blob.Locked),
 			RetainUntil: retainUntilDate,
 		}
diff --git a/repo/blob/gdrive/gdrive_storage.go b/repo/blob/gdrive/gdrive_storage.go
index d37881a7..7f4e0e1d 100644
--- a/repo/blob/gdrive/gdrive_storage.go
+++ b/repo/blob/gdrive/gdrive_storage.go
@@ -473,12 +473,11 @@ func translateError(err error) error {
 		}
 	}
 
-	switch {
-	case err == nil:
-		return nil
-	default:
+	if err != nil {
 		return errors.Wrap(err, "unexpected Google Drive error")
 	}
+
+	return nil
 }
 
 func tokenSourceFromCredentialsFile(ctx context.Context, fn string, scopes ...string) (oauth2.TokenSource, error) {
diff --git a/repo/blob/rclone/rclone_storage.go b/repo/blob/rclone/rclone_storage.go
index 72478f7f..5d96ebbe 100644
--- a/repo/blob/rclone/rclone_storage.go
+++ b/repo/blob/rclone/rclone_storage.go
@@ -120,7 +120,7 @@ func (r *rcloneStorage) Close(ctx context.Context) error {
 }
 
 func (r *rcloneStorage) DisplayName() string {
-	return "RClone " + r.Options.RemotePath
+	return "RClone " + r.RemotePath
 }
 
 func (r *rcloneStorage) processStderrStatus(ctx context.Context, s *bufio.Scanner) {
diff --git a/repo/blob/s3/s3_pit.go b/repo/blob/s3/s3_pit.go
index e6ca0c56..1e87bd15 100644
--- a/repo/blob/s3/s3_pit.go
+++ b/repo/blob/s3/s3_pit.go
@@ -118,7 +118,7 @@ func getOlderThan(vs []versionMetadata, t time.Time) []versionMetadata {
 // maybePointInTimeStore wraps s with a point-in-time store when s is versioned
 // and a point-in-time value is specified. Otherwise s is returned.
 func maybePointInTimeStore(ctx context.Context, s *s3Storage, pointInTime *time.Time) (blob.Storage, error) {
-	if pit := s.Options.PointInTime; pit == nil || pit.IsZero() {
+	if pit := s.PointInTime; pit == nil || pit.IsZero() {
 		return s, nil
 	}
 
diff --git a/repo/blob/webdav/webdav_storage.go b/repo/blob/webdav/webdav_storage.go
index c84044e2..94009fc6 100644
--- a/repo/blob/webdav/webdav_storage.go
+++ b/repo/blob/webdav/webdav_storage.go
@@ -152,7 +152,7 @@ func (d *davStorageImpl) PutBlobInPath(ctx context.Context, dirPath, filePath st
 
 	var writePath string
 
-	if d.Options.AtomicWrites {
+	if d.AtomicWrites {
 		writePath = filePath
 	} else {
 		writePath = fmt.Sprintf("%v-%v", filePath, rand.Int63()) //nolint:gosec
@@ -171,7 +171,7 @@ func (d *davStorageImpl) PutBlobInPath(ctx context.Context, dirPath, filePath st
 
 			err := d.translateError(d.cli.Write(writePath, b, defaultFilePerm))
 			if err == nil {
-				if d.Options.AtomicWrites {
+				if d.AtomicWrites {
 					return nil
 				}
 
diff --git a/repo/connect.go b/repo/connect.go
index b1cef53f..48975c95 100644
--- a/repo/connect.go
+++ b/repo/connect.go
@@ -51,7 +51,7 @@ func Connect(ctx context.Context, configFile string, st blob.Storage, password s
 
 	ci := st.ConnectionInfo()
 	lc.Storage = &ci
-	lc.ClientOptions = opt.ClientOptions.ApplyDefaults(ctx, "Repository in "+st.DisplayName())
+	lc.ClientOptions = opt.ApplyDefaults(ctx, "Repository in "+st.DisplayName())
 
 	if err = setupCachingOptionsWithDefaults(ctx, configFile, &lc, &opt.CachingOptions, f.UniqueID); err != nil {
 		return errors.Wrap(err, "unable to set up caching")
diff --git a/repo/content/index/one_use_index_builder.go b/repo/content/index/one_use_index_builder.go
index 56cdee1c..c7255321 100644
--- a/repo/content/index/one_use_index_builder.go
+++ b/repo/content/index/one_use_index_builder.go
@@ -5,9 +5,8 @@
 	"hash/fnv"
 	"io"
 
-	"github.com/pkg/errors"
-
 	"github.com/petar/GoLLRB/llrb"
+	"github.com/pkg/errors"
 
 	"github.com/kopia/kopia/internal/gather"
 )
diff --git a/repo/ecc/ecc_rs_crc.go b/repo/ecc/ecc_rs_crc.go
index 1fd1e456..ce85da86 100644
--- a/repo/ecc/ecc_rs_crc.go
+++ b/repo/ecc/ecc_rs_crc.go
@@ -312,7 +312,7 @@ func (r *ReedSolomonCrcECC) Decrypt(input gather.Bytes, _ []byte, output *gather
 			}
 		}
 
-		if r.Options.DeleteFirstShardForTests {
+		if r.DeleteFirstShardForTests {
 			shards[0] = nil
 		}
 
diff --git a/repo/format/format_manager.go b/repo/format/format_manager.go
index 1f24d6e0..1de22f61 100644
--- a/repo/format/format_manager.go
+++ b/repo/format/format_manager.go
@@ -465,7 +465,7 @@ func Initialize(ctx context.Context, st blob.Storage, formatBlob *KopiaRepositor
 		return errors.Wrap(err, "unable to derive format encryption key")
 	}
 
-	if err = repoConfig.MutableParameters.Validate(); err != nil {
+	if err = repoConfig.Validate(); err != nil {
 		return errors.Wrap(err, "invalid parameters")
 	}
 
diff --git a/repo/format/format_set_parameters.go b/repo/format/format_set_parameters.go
index b91688a9..4fd0108d 100644
--- a/repo/format/format_set_parameters.go
+++ b/repo/format/format_set_parameters.go
@@ -27,7 +27,7 @@ func (m *Manager) SetParameters(
 		return errors.Wrap(err, "invalid blob-config options")
 	}
 
-	m.repoConfig.ContentFormat.MutableParameters = mp
+	m.repoConfig.MutableParameters = mp
 	m.repoConfig.RequiredFeatures = requiredFeatures
 
 	if err := m.j.EncryptRepositoryConfig(m.repoConfig, m.formatEncryptionKey); err != nil {
diff --git a/repo/format/upgrade_lock.go b/repo/format/upgrade_lock.go
index 48680e59..a959037d 100644
--- a/repo/format/upgrade_lock.go
+++ b/repo/format/upgrade_lock.go
@@ -51,9 +51,9 @@ func (m *Manager) SetUpgradeLockIntent(ctx context.Context, l UpgradeLockIntent)
 	if m.repoConfig.UpgradeLock == nil {
 		// when we are putting a new lock then ensure that we can upgrade
 		// to that version
-		if m.repoConfig.ContentFormat.Version >= MaxFormatVersion {
+		if m.repoConfig.Version >= MaxFormatVersion {
 			return nil, errors.WithMessagef(ErrFormatUptoDate, "repository is using version %d, and version %d is the maximum",
-				m.repoConfig.ContentFormat.Version, MaxFormatVersion)
+				m.repoConfig.Version, MaxFormatVersion)
 		}
 
 		// backup the current repository config from local cache to the
@@ -66,7 +66,7 @@ func (m *Manager) SetUpgradeLockIntent(ctx context.Context, l UpgradeLockIntent)
 		m.repoConfig.UpgradeLock = &l
 		// mark the upgrade to the new format version, this will ensure that older
 		// clients won't be able to parse the new version
-		m.repoConfig.ContentFormat.Version = MaxFormatVersion
+		m.repoConfig.Version = MaxFormatVersion
 	} else if newL, err := m.repoConfig.UpgradeLock.Update(&l); err == nil {
 		m.repoConfig.UpgradeLock = newL
 	} else {
diff --git a/repo/grpc_repository_client.go b/repo/grpc_repository_client.go
index 85d260af..0232abcf 100644
--- a/repo/grpc_repository_client.go
+++ b/repo/grpc_repository_client.go
@@ -878,7 +878,7 @@ func openGRPCAPIRepository(ctx context.Context, si *APIServerInfo, password stri
 		return nil, errors.Wrap(err, "gRPC client creation error")
 	}
 
-	par.refCountedCloser.registerEarlyCloseFunc(
+	par.registerEarlyCloseFunc(
 		func(ctx context.Context) error {
 			return errors.Wrap(conn.Close(), "error closing GRPC connection")
 		})
diff --git a/repo/initialize.go b/repo/initialize.go
index ce1c931d..fd10b1e0 100644
--- a/repo/initialize.go
+++ b/repo/initialize.go
@@ -111,12 +111,12 @@ func repositoryObjectFormatFromOptions(opt *NewRepositoryOptions) (*format.Repos
 		f.HMACSecret = nil
 	}
 
-	if fv == format.FormatVersion1 || f.ContentFormat.ECCOverheadPercent == 0 {
-		f.ContentFormat.ECC = ""
-		f.ContentFormat.ECCOverheadPercent = 0
+	if fv == format.FormatVersion1 || f.ECCOverheadPercent == 0 {
+		f.ECC = ""
+		f.ECCOverheadPercent = 0
 	}
 
-	if err := f.ContentFormat.ResolveFormatVersion(); err != nil {
+	if err := f.ResolveFormatVersion(); err != nil {
 		return nil, errors.Wrap(err, "error resolving format version")
 	}
 
diff --git a/repo/splitter/splitter_pool.go b/repo/splitter/splitter_pool.go
index c7a01528..6ea7e2e6 100644
--- a/repo/splitter/splitter_pool.go
+++ b/repo/splitter/splitter_pool.go
@@ -10,7 +10,7 @@ type recyclableSplitter struct {
 }
 
 func (s recyclableSplitter) Close() {
-	s.Splitter.Reset()
+	s.Reset()
 	s.Splitter.Close()
 	s.pool.Put(s.Splitter)
 }
diff --git a/snapshot/manifest.go b/snapshot/manifest.go
index 5165894b..5a8d44ba 100644
--- a/snapshot/manifest.go
+++ b/snapshot/manifest.go
@@ -84,8 +84,6 @@ func (m *Manifest) UpdatePins(add, remove []string) bool {
 )
 
 // Permissions encapsulates UNIX permissions for a filesystem entry.
-//
-//nolint:recvcheck
 type Permissions int
 
 // MarshalJSON emits permissions as octal string.
diff --git a/snapshot/upload/upload_estimator.go b/snapshot/upload/upload_estimator.go
index f3b7c9e8..1ebcfd6d 100644
--- a/snapshot/upload/upload_estimator.go
+++ b/snapshot/upload/upload_estimator.go
@@ -4,12 +4,12 @@
 	"context"
 	"sync"
 
+	"github.com/pkg/errors"
+
 	"github.com/kopia/kopia/fs"
 	vsi "github.com/kopia/kopia/internal/volumesizeinfo"
 	"github.com/kopia/kopia/repo/logging"
 	"github.com/kopia/kopia/snapshot/policy"
-
-	"github.com/pkg/errors"
 )
 
 // EstimationDoneFn represents the signature of the callback function which will be invoked when an estimation is done.
diff --git a/tools/cli2md/cli2md.go b/tools/cli2md/cli2md.go
index c10da704..ab7237bd 100644
--- a/tools/cli2md/cli2md.go
+++ b/tools/cli2md/cli2md.go
@@ -251,7 +251,7 @@ func generateSubcommands(w io.Writer, dir, sectionTitle string, cmds []*kingpin.
 			first = false
 		}
 
-		subcommandSlug := strings.Replace(c.FullCommand, " ", "-", -1)
+		subcommandSlug := strings.ReplaceAll(c.FullCommand, " ", "-")
 		helpSummary := strings.SplitN(c.Help, "\n", 2)[0] //nolint:mnd
 		helpSummary = strings.TrimSuffix(helpSummary, ".")
 		fmt.Fprintf(w, "* [`%v`](%v) - %v\n", c.FullCommand, subcommandSlug+"/", helpSummary) //nolint:errcheck
diff --git a/tools/gettool/checksums.txt b/tools/gettool/checksums.txt
index 7b7983bc..ee18b682 100644
--- a/tools/gettool/checksums.txt
+++ b/tools/gettool/checksums.txt
@@ -7,12 +7,12 @@ https://github.com/git-chglog/git-chglog/releases/download/v0.15.1/git-chglog_0.
 https://github.com/gohugoio/hugo/releases/download/v0.113.0/hugo_extended_0.113.0_darwin-universal.tar.gz: 1557f896f34743d241e1aecab588be273dde59692b362a9f4488231a2595b2ae
 https://github.com/gohugoio/hugo/releases/download/v0.113.0/hugo_extended_0.113.0_linux-amd64.tar.gz: e04bccfa81df6c727f1c03bc858eb21d6f95123d311cafe245f4485d289123f3
 https://github.com/gohugoio/hugo/releases/download/v0.113.0/hugo_extended_0.113.0_windows-amd64.zip: 3eabfbfad1431939058e6f7e76573c6bac1fee92f3a7b1ac5739c555940f0e0e
-https://github.com/golangci/golangci-lint/releases/download/v1.62.0/golangci-lint-1.62.0-darwin-amd64.tar.gz: 0ed6f1a216ddb62e293858196799608d63894bd2ec178114484363ca45cde84b
-https://github.com/golangci/golangci-lint/releases/download/v1.62.0/golangci-lint-1.62.0-darwin-arm64.tar.gz: dde51958f0f24d442062b5709b6912d91e235115dfe5887e80b3e5602c9cc09b
-https://github.com/golangci/golangci-lint/releases/download/v1.62.0/golangci-lint-1.62.0-linux-amd64.tar.gz: 53695531eeb824b6883c703335cef6f07882f8ba6fedc00ed43853ea07fa1fbd
-https://github.com/golangci/golangci-lint/releases/download/v1.62.0/golangci-lint-1.62.0-linux-arm64.tar.gz: e1e47209d7bdd288fd8cfe88548b477df2f7eca81b0e9ec1f9d45604f79185eb
-https://github.com/golangci/golangci-lint/releases/download/v1.62.0/golangci-lint-1.62.0-linux-armv6.tar.gz: 0a6565ed98da60b470f5652eb1bf434ae84f39ab0632749398176e1a9c477798
-https://github.com/golangci/golangci-lint/releases/download/v1.62.0/golangci-lint-1.62.0-windows-amd64.zip: 34e980afe44655c395aa65f96953fc4b6a2e58206f1a7370ab88407b187184c8
+https://github.com/golangci/golangci-lint/releases/download/v2.1.2/golangci-lint-2.1.2-darwin-amd64.tar.gz: ed02ba3ad28466d61d2ae2b80cc95671713fa842c353da37842b1b89e36cb3ce
+https://github.com/golangci/golangci-lint/releases/download/v2.1.2/golangci-lint-2.1.2-darwin-arm64.tar.gz: 1cff60651d7c95a4248fa72f0dd020bffed1d2dc4dd8c2c77aee89a0731fa615
+https://github.com/golangci/golangci-lint/releases/download/v2.1.2/golangci-lint-2.1.2-linux-amd64.tar.gz: bc16fd1ef25bce2c600de0122600100ab26d6d75388cc5369c5bb916cb2b82e3
+https://github.com/golangci/golangci-lint/releases/download/v2.1.2/golangci-lint-2.1.2-linux-arm64.tar.gz: 46e86f1c4a94236e4d0bb35252c72939bed9f749897aaad54b576d430b1bb6d4
+https://github.com/golangci/golangci-lint/releases/download/v2.1.2/golangci-lint-2.1.2-linux-armv6.tar.gz: a0ddb93965d25d11f973ef3ff226bae3486bb12000a6b1a5ed18657aa13ef8a9
+https://github.com/golangci/golangci-lint/releases/download/v2.1.2/golangci-lint-2.1.2-windows-amd64.zip: 1f920e8af6d596deeb9295153270ad2ced9cfa2085c79e68e09a5d96721b48dd
 https://github.com/goreleaser/goreleaser/releases/download/v0.176.0/goreleaser_Darwin_arm64.tar.gz: 1f95e6561974f4766d8833438b646b06930563ca9867447ea03edb623d876c75
 https://github.com/goreleaser/goreleaser/releases/download/v0.176.0/goreleaser_Darwin_x86_64.tar.gz: 17ecad881a50e32f033da5a200c8417d37cae70f09e925645452937998aca506
 https://github.com/goreleaser/goreleaser/releases/download/v0.176.0/goreleaser_Linux_arm64.tar.gz: 8bf2a9b9e84498bfa239f2fe91b2d555642c87ab9d3f5d37f29e6e97116910a3
diff --git a/tools/tools.mk b/tools/tools.mk
index e5845149..c7e6cc51 100644
--- a/tools/tools.mk
+++ b/tools/tools.mk
@@ -102,7 +102,7 @@ retry:=
 endif
 
 # tool versions
-GOLANGCI_LINT_VERSION=1.62.0
+GOLANGCI_LINT_VERSION=2.1.2
 CHECKLOCKS_VERSION=release-20241104.0
 NODE_VERSION=20.15.1
 HUGO_VERSION=0.113.0


Test patch:
diff --git a/cli/command_repository_create_test.go b/cli/command_repository_create_test.go
index f30670a6..9fd128b7 100644
--- a/cli/command_repository_create_test.go
+++ b/cli/command_repository_create_test.go
@@ -6,12 +6,12 @@
 	"strings"
 	"testing"
 
+	"github.com/stretchr/testify/require"
+
 	"github.com/kopia/kopia/repo"
 	"github.com/kopia/kopia/repo/blob"
 	"github.com/kopia/kopia/repo/blob/filesystem"
 	"github.com/kopia/kopia/tests/testenv"
-
-	"github.com/stretchr/testify/require"
 )
 
 func TestRepositoryCreateWithConfigFile(t *testing.T) {
diff --git a/cli/command_repository_upgrade_test.go b/cli/command_repository_upgrade_test.go
index 7feccca0..51000996 100644
--- a/cli/command_repository_upgrade_test.go
+++ b/cli/command_repository_upgrade_test.go
@@ -420,7 +420,7 @@ func TestRepositoryUpgrade_checkIndexInfo(t *testing.T) {
 	for i, tc := range tcs {
 		t.Run(strconv.Itoa(i), func(t *testing.T) {
 			report := cli.CheckIndexInfo(tc.indexInfo0, tc.indexInfo1)
-			require.Equal(t, len(report), len(tc.expectRegexs), "unexpected report length")
+			require.Len(t, tc.expectRegexs, len(report), "unexpected report length")
 			for i := range tc.expectRegexs {
 				require.Regexp(t, tc.expectRegexs[i], report[i], "report does not match regular expression")
 			}
diff --git a/internal/epoch/epoch_manager_test.go b/internal/epoch/epoch_manager_test.go
index abc4ed76..af2edf15 100644
--- a/internal/epoch/epoch_manager_test.go
+++ b/internal/epoch/epoch_manager_test.go
@@ -862,7 +862,7 @@ func verifySequentialWrites(t *testing.T, te *epochManagerTestEnv) {
 func TestIndexEpochManager_Disabled(t *testing.T) {
 	te := newTestEnv(t)
 
-	te.mgr.paramProvider.(parameterProvider).Parameters.Enabled = false
+	te.mgr.paramProvider.(parameterProvider).Enabled = false
 
 	_, err := te.mgr.Current(testlogging.Context(t))
 	require.Error(t, err)
diff --git a/internal/iocopy/iocopy_test.go b/internal/iocopy/iocopy_test.go
index 2644508a..c880e98a 100644
--- a/internal/iocopy/iocopy_test.go
+++ b/internal/iocopy/iocopy_test.go
@@ -20,7 +20,7 @@
 type errorWriter struct{}
 
 func (errorWriter) Write(p []byte) (n int, err error) {
-	return 0, errors.New("write error") //nolint:goerr113
+	return 0, errors.New("write error") //nolint:err113
 }
 
 func TestGetBuffer(t *testing.T) {
diff --git a/internal/parallelwork/parallel_work_queue_test.go b/internal/parallelwork/parallel_work_queue_test.go
index 38e95244..b0359263 100644
--- a/internal/parallelwork/parallel_work_queue_test.go
+++ b/internal/parallelwork/parallel_work_queue_test.go
@@ -80,7 +80,7 @@ func TestEnqueueBackAndProcess(t *testing.T) {
 func TestProcessWithError(t *testing.T) {
 	queue := parallelwork.NewQueue()
 
-	testError := errors.New("test error") //nolint:goerr113
+	testError := errors.New("test error") //nolint:err113
 
 	// Enqueue work items, one of them returns an error
 	queue.EnqueueBack(context.Background(), func() error {
@@ -165,7 +165,7 @@ func TestOnNthCompletion(t *testing.T) {
 	t.Run("callback is only called on n-th invocation", func(t *testing.T) {
 		var (
 			n               = 5                    // expect invocation on 5th attempt
-			errCalled       = errors.New("called") //nolint:goerr113
+			errCalled       = errors.New("called") //nolint:err113
 			callbackInvoked int
 			callback        = func() error {
 				callbackInvoked++
@@ -198,7 +198,7 @@ func TestOnNthCompletion(t *testing.T) {
 		var (
 			n               = 5                     // expect invocation on 5th attempt
 			results         = make(chan error, n+1) // we will have n+1, i.e. 6 attempts in total
-			errCalled       = errors.New("called")  //nolint:goerr113
+			errCalled       = errors.New("called")  //nolint:err113
 			callbackInvoked atomic.Int32
 			wg              sync.WaitGroup
 			callback        = func() error {
diff --git a/internal/server/api_snapshots_test.go b/internal/server/api_snapshots_test.go
index 00977a12..3116e6fe 100644
--- a/internal/server/api_snapshots_test.go
+++ b/internal/server/api_snapshots_test.go
@@ -233,8 +233,8 @@ func TestEditSnapshots(t *testing.T) {
 	}, &updated))
 
 	require.Len(t, updated, 1)
-	require.EqualValues(t, []string{"pin1", "pin2"}, updated[0].Pins)
-	require.EqualValues(t, newDesc1, updated[0].Description)
+	require.Equal(t, []string{"pin1", "pin2"}, updated[0].Pins)
+	require.Equal(t, newDesc1, updated[0].Description)
 
 	require.NoError(t, cli.Post(ctx, "snapshots/edit", &serverapi.EditSnapshotsRequest{
 		Snapshots:      []manifest.ID{updated[0].ID},
@@ -244,8 +244,8 @@ func TestEditSnapshots(t *testing.T) {
 	}, &updated))
 
 	require.Len(t, updated, 1)
-	require.EqualValues(t, []string{"pin2", "pin3"}, updated[0].Pins)
-	require.EqualValues(t, newDesc2, updated[0].Description)
+	require.Equal(t, []string{"pin2", "pin3"}, updated[0].Pins)
+	require.Equal(t, newDesc2, updated[0].Description)
 
 	require.NoError(t, cli.Post(ctx, "snapshots/edit", &serverapi.EditSnapshotsRequest{
 		Snapshots:  []manifest.ID{updated[0].ID},
@@ -253,6 +253,6 @@ func TestEditSnapshots(t *testing.T) {
 	}, &updated))
 
 	require.Len(t, updated, 1)
-	require.EqualValues(t, []string{"pin2"}, updated[0].Pins)
-	require.EqualValues(t, newDesc2, updated[0].Description)
+	require.Equal(t, []string{"pin2"}, updated[0].Pins)
+	require.Equal(t, newDesc2, updated[0].Description)
 }
diff --git a/internal/server/api_ui_pref_test.go b/internal/server/api_ui_pref_test.go
index 02b3d0b2..7e512be2 100644
--- a/internal/server/api_ui_pref_test.go
+++ b/internal/server/api_ui_pref_test.go
@@ -29,7 +29,7 @@ func TestUIPreferences(t *testing.T) {
 	var p, p2 serverapi.UIPreferences
 
 	require.NoError(t, cli.Get(ctx, "ui-preferences", nil, &p))
-	require.Equal(t, "", p.Theme)
+	require.Empty(t, p.Theme)
 	p.Theme = "dark"
 
 	require.NoError(t, cli.Put(ctx, "ui-preferences", &p, &serverapi.Empty{}))
diff --git a/internal/tempfile/tempfile_test.go b/internal/tempfile/tempfile_test.go
index c2a2f645..8c2dd08f 100644
--- a/internal/tempfile/tempfile_test.go
+++ b/internal/tempfile/tempfile_test.go
@@ -36,3 +36,12 @@ func TestTempFile(t *testing.T) {
 	require.NoError(t, err)
 	require.Empty(t, files)
 }
+
+func TestCreateSucceedsWhenDirIsNotSpecified(t *testing.T) {
+	f, err := tempfile.Create("")
+
+	require.NoError(t, err)
+
+	err = f.Close()
+	require.NoError(t, err)
+}
diff --git a/internal/uitask/uitask_test.go b/internal/uitask/uitask_test.go
index c6edbdef..033ff2dd 100644
--- a/internal/uitask/uitask_test.go
+++ b/internal/uitask/uitask_test.go
@@ -30,7 +30,7 @@ func TestUITask_withoutPersistentLogging(t *testing.T) {
 
 	m := uitask.NewManager(false)
 	testUITaskInternal(t, ctx, m)
-	require.Equal(t, "", logBuf.String())
+	require.Empty(t, logBuf.String())
 }
 
 func TestUITask_withPersistentLogging(t *testing.T) {
diff --git a/repo/blob/s3/s3_storage_test.go b/repo/blob/s3/s3_storage_test.go
index 2a9c7361..577013cd 100644
--- a/repo/blob/s3/s3_storage_test.go
+++ b/repo/blob/s3/s3_storage_test.go
@@ -545,7 +545,7 @@ func TestNeedMD5AWS(t *testing.T) {
 func testStorage(t *testing.T, options *Options, runValidationTest bool, opts blob.PutOptions) {
 	ctx := testlogging.Context(t)
 
-	require.Equal(t, "", options.Prefix)
+	require.Empty(t, options.Prefix)
 
 	st0, err := New(ctx, options, false)
 
@@ -579,7 +579,7 @@ func testStorage(t *testing.T, options *Options, runValidationTest bool, opts bl
 func testPutBlobWithInvalidRetention(t *testing.T, options Options, opts blob.PutOptions) {
 	ctx := testlogging.Context(t)
 
-	require.Equal(t, "", options.Prefix)
+	require.Empty(t, options.Prefix)
 	options.Prefix = uuid.NewString()
 
 	// non-retrying storage
diff --git a/repo/blob/s3/s3_versioned_test.go b/repo/blob/s3/s3_versioned_test.go
index 4dfd017a..b337f630 100644
--- a/repo/blob/s3/s3_versioned_test.go
+++ b/repo/blob/s3/s3_versioned_test.go
@@ -765,7 +765,7 @@ func compareVersionSlices(tb testing.TB, a, b []versionMetadata) {
 		compareMetadata(tb, a[i], b[i])
 	}
 
-	require.Equal(tb, len(a), len(b), "the number of the blob versions to compare does not match", a, b)
+	require.Len(tb, b, len(a), "the number of the blob versions to compare does not match", a, b)
 }
 
 func reverseVersionSlice(m []versionMetadata) []versionMetadata {
diff --git a/repo/blob/storagemetrics/storage_metrics_test.go b/repo/blob/storagemetrics/storage_metrics_test.go
index 05141b93..04f2831d 100644
--- a/repo/blob/storagemetrics/storage_metrics_test.go
+++ b/repo/blob/storagemetrics/storage_metrics_test.go
@@ -284,5 +284,5 @@ func requireCounterValue(t *testing.T, snap metrics.Snapshot, key string, want i
 
 	v, ok := snap.Counters[key]
 	require.True(t, ok)
-	require.EqualValues(t, want, v)
+	require.Equal(t, want, v)
 }
diff --git a/repo/format/upgrade_lock_intent_test.go b/repo/format/upgrade_lock_intent_test.go
index a5ffcc09..520c91aa 100644
--- a/repo/format/upgrade_lock_intent_test.go
+++ b/repo/format/upgrade_lock_intent_test.go
@@ -337,5 +337,5 @@ func TestUpgradeLockIntentClone(t *testing.T) {
 		Message:                "upgrading from format version 2 -> 3",
 		MaxPermittedClockDrift: 5 * time.Second,
 	}
-	require.EqualValues(t, l, l.Clone())
+	require.Equal(t, l, l.Clone())
 }
diff --git a/repo/logging/logging_buf_test.go b/repo/logging/logging_buf_test.go
index 147a6b36..2e5dc31a 100644
--- a/repo/logging/logging_buf_test.go
+++ b/repo/logging/logging_buf_test.go
@@ -50,7 +50,7 @@ func TestLoggingBuffer_Append(t *testing.T) {
 	b := logging.GetBuffer()
 	defer b.Release()
 
-	require.Equal(t, "", b.String())
+	require.Empty(t, b.String())
 
 	require.Equal(t,
 		"xx ABC D -42 -23 true 42 false 23 2000-01-02T03:04:05Z",
diff --git a/snapshot/upload/upload_estimator_test.go b/snapshot/upload/upload_estimator_test.go
index 737af4bb..8125a32b 100644
--- a/snapshot/upload/upload_estimator_test.go
+++ b/snapshot/upload/upload_estimator_test.go
@@ -6,15 +6,15 @@
 	"testing"
 	"time"
 
+	"github.com/stretchr/testify/require"
+	"go.uber.org/zap"
+	"go.uber.org/zap/zapcore"
+
 	"github.com/kopia/kopia/internal/mockfs"
 	vsi "github.com/kopia/kopia/internal/volumesizeinfo"
 	"github.com/kopia/kopia/repo/logging"
 	"github.com/kopia/kopia/snapshot/policy"
 	"github.com/kopia/kopia/snapshot/upload"
-
-	"github.com/stretchr/testify/require"
-	"go.uber.org/zap"
-	"go.uber.org/zap/zapcore"
 )
 
 var errSimulated = errors.New("simulated error")
diff --git a/snapshot/upload/upload_test.go b/snapshot/upload/upload_test.go
index 0b3871a9..849deff2 100644
--- a/snapshot/upload/upload_test.go
+++ b/snapshot/upload/upload_test.go
@@ -409,7 +409,7 @@ func TestUpload_SubDirectoryReadFailureFailFast(t *testing.T) {
 	man, err := u.Upload(ctx, th.sourceDir, policyTree, snapshot.SourceInfo{})
 	require.NoError(t, err)
 
-	require.NotEqual(t, "", man.IncompleteReason, "snapshot not marked as incomplete")
+	require.NotEmpty(t, man.IncompleteReason, "snapshot not marked as incomplete")
 
 	// will have one error because we're canceling early.
 	verifyErrors(t, man, 1, 0,
diff --git a/tests/end_to_end_test/server_start_test.go b/tests/end_to_end_test/server_start_test.go
index ca24410a..0e43073e 100644
--- a/tests/end_to_end_test/server_start_test.go
+++ b/tests/end_to_end_test/server_start_test.go
@@ -490,7 +490,7 @@ func TestServerScheduling(t *testing.T) {
 	testutil.MustParseJSONLines(t, e.RunAndExpectSuccess(t, "maintenance", "info", "--json"), &miAfter)
 
 	// make sure we got some maintenance runs
-	numRuns := len(miAfter.Schedule.Runs["cleanup-logs"]) - len(miBefore.Schedule.Runs["cleanup-logs"])
+	numRuns := len(miAfter.Runs["cleanup-logs"]) - len(miBefore.Runs["cleanup-logs"])
 	require.Greater(t, numRuns, 2)
 	require.Less(t, numRuns, 5)
 }
diff --git a/tests/end_to_end_test/shallowrestore_test.go b/tests/end_to_end_test/shallowrestore_test.go
index ce3114ee..4fbca7f9 100644
--- a/tests/end_to_end_test/shallowrestore_test.go
+++ b/tests/end_to_end_test/shallowrestore_test.go
@@ -760,7 +760,7 @@ func findFileDir(t *testing.T, shallow string) (dirinshallow, fileinshallow stri
 		fi, err := os.Lstat(f)
 		require.NoError(t, err)
 
-		if !(fi.Mode().IsDir() || fi.Mode().IsRegular()) {
+		if !fi.Mode().IsDir() && !fi.Mode().IsRegular() {
 			continue
 		}
 
diff --git a/tests/end_to_end_test/snapshot_create_test.go b/tests/end_to_end_test/snapshot_create_test.go
index 16a13b3d..15057991 100644
--- a/tests/end_to_end_test/snapshot_create_test.go
+++ b/tests/end_to_end_test/snapshot_create_test.go
@@ -755,7 +755,7 @@ func TestSnapshotCreateAllSnapshotPath(t *testing.T) {
 	// all non-global policies should be manual
 	for _, p := range plist {
 		if (p.Target != snapshot.SourceInfo{}) {
-			require.True(t, p.Policy.SchedulingPolicy.Manual)
+			require.True(t, p.SchedulingPolicy.Manual)
 		}
 	}
 
diff --git a/tests/robustness/engine/engine_test.go b/tests/robustness/engine/engine_test.go
index e3d490fe..f6c39a39 100644
--- a/tests/robustness/engine/engine_test.go
+++ b/tests/robustness/engine/engine_test.go
@@ -541,7 +541,7 @@ func TestActionsFilesystem(t *testing.T) {
 	numActions := 10
 	for range numActions {
 		err := eng.RandomAction(ctx, actionOpts)
-		if !(err == nil || errors.Is(err, robustness.ErrNoOp)) {
+		if err != nil && !errors.Is(err, robustness.ErrNoOp) {
 			t.Error("Hit error", err)
 		}
 	}
@@ -588,7 +588,7 @@ func TestActionsS3(t *testing.T) {
 	numActions := 10
 	for range numActions {
 		err := eng.RandomAction(ctx, actionOpts)
-		if !(err == nil || errors.Is(err, robustness.ErrNoOp)) {
+		if err != nil && !errors.Is(err, robustness.ErrNoOp) {
 			t.Error("Hit error", err)
 		}
 	}
diff --git a/tests/robustness/pathlock/path_lock_test.go b/tests/robustness/pathlock/path_lock_test.go
index 5db08f87..214c2b50 100644
--- a/tests/robustness/pathlock/path_lock_test.go
+++ b/tests/robustness/pathlock/path_lock_test.go
@@ -133,11 +133,7 @@ func TestPathLockBasic(t *testing.T) {
 		// Wait until the internal atomic counter increments.
 		// That will only happen once the Lock call to path2 executes
 		// and blocks on the prior Lock to path1.
-		for {
-			if busyCounter.Load() > currBusyCounter {
-				break
-			}
-
+		for busyCounter.Load() <= currBusyCounter {
 			time.Sleep(1 * time.Millisecond)
 		}
 
diff --git a/tests/robustness/robustness_test/main_test.go b/tests/robustness/robustness_test/main_test.go
index 320d809c..3a799245 100644
--- a/tests/robustness/robustness_test/main_test.go
+++ b/tests/robustness/robustness_test/main_test.go
@@ -60,7 +60,7 @@ func TestMain(m *testing.M) {
 		rs, err := th.snapshotter.GetRepositoryStatus()
 		exitOnError("failed to get repository status before upgrade", err)
 
-		prev := rs.ContentFormat.MutableParameters.Version
+		prev := rs.ContentFormat.Version
 
 		log.Println("Old repository format:", prev)
 		th.snapshotter.UpgradeRepository()
@@ -68,7 +68,7 @@ func TestMain(m *testing.M) {
 		rs, err = th.snapshotter.GetRepositoryStatus()
 		exitOnError("failed to get repository status after upgrade", err)
 
-		curr := rs.ContentFormat.MutableParameters.Version
+		curr := rs.ContentFormat.Version
 		log.Println("Upgraded repository format:", curr)
 
 		// Reset the env variable.
diff --git a/tests/robustness/snapmeta/kopia_snapshotter_upgrade_test.go b/tests/robustness/snapmeta/kopia_snapshotter_upgrade_test.go
index aaa61119..f068a412 100644
--- a/tests/robustness/snapmeta/kopia_snapshotter_upgrade_test.go
+++ b/tests/robustness/snapmeta/kopia_snapshotter_upgrade_test.go
@@ -27,6 +27,6 @@ func TestGetRepositoryStatus(t *testing.T) {
 	rs, err := ks.GetRepositoryStatus()
 	require.NoError(t, err)
 
-	prev := rs.ContentFormat.MutableParameters.Version
+	prev := rs.ContentFormat.Version
 	require.Equal(t, prev, format.Version(3), "The format version should be 3.")
 }
diff --git a/tests/tools/fio/workload_test.go b/tests/tools/fio/workload_test.go
index cf02ffee..19d7fa8b 100644
--- a/tests/tools/fio/workload_test.go
+++ b/tests/tools/fio/workload_test.go
@@ -276,7 +276,7 @@ func TestDeleteContentsAtDepth(t *testing.T) {
 				// Broad check: just make sure a 50% probability deleted something.
 				// Extremely improbable that this causes a false failure;
 				// akin to 100 coin flips all landing on the same side.
-				if !(fileCount > 0 && fileCount < 100) {
+				if fileCount <= 0 || fileCount >= 100 {
 					t.Error("expected some but not all files to be deleted")
 				}
 			},
diff --git a/tests/tools/fswalker/fswalker.go b/tests/tools/fswalker/fswalker.go
index 2919ace4..a0a415af 100644
--- a/tests/tools/fswalker/fswalker.go
+++ b/tests/tools/fswalker/fswalker.go
@@ -13,8 +13,6 @@
 	"path/filepath"
 	"strings"
 
-	//nolint:staticcheck
-
 	"github.com/google/fswalker"
 	fspb "github.com/google/fswalker/proto/fswalker"
 	"github.com/pkg/errors"