Repository: kopia/kopia
Pull request #4479 (2025-03-31 10:18:27)

fix(site): escape flags with backticks
In the generated markdown docs, flags like `--foo` inside help texts currently get pretty-printed as `–foo` with an em-dash.

This PR applies backticks via a regex replacement (run by `cli2md`), so that they appear as `--foo` in the docs but remain as --foo (without backticks) in the CLI output.

See also #4341 


Patch:
diff --git a/.github/workflows/code-coverage.yml b/.github/workflows/code-coverage.yml
index bc68672f..0583d74d 100644
--- a/.github/workflows/code-coverage.yml
+++ b/.github/workflows/code-coverage.yml
@@ -16,7 +16,7 @@ jobs:
       with:
         fetch-depth: 0
     - name: Set up Go
-      uses: actions/setup-go@f111f3307d8850f501ac008e886eec1fd1932a34 # v5.3.0
+      uses: actions/setup-go@0aaccfd150d50ccaeb58ebd88d36e91967a5f35b # v5.4.0
       with:
         go-version-file: 'go.mod'
         check-latest: true
@@ -28,7 +28,7 @@ jobs:
       with:
         files: coverage.txt
     - name: Upload Logs
-      uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4.6.1
+      uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
       with:
         name: logs
         path: .logs/**/*.log
diff --git a/.github/workflows/license-check.yml b/.github/workflows/license-check.yml
index 978ebafb..a86a02e7 100644
--- a/.github/workflows/license-check.yml
+++ b/.github/workflows/license-check.yml
@@ -16,7 +16,7 @@ jobs:
       with:
         fetch-depth: 0
     - name: Set up Go
-      uses: actions/setup-go@f111f3307d8850f501ac008e886eec1fd1932a34 # v5.3.0
+      uses: actions/setup-go@0aaccfd150d50ccaeb58ebd88d36e91967a5f35b # v5.4.0
       with:
         go-version-file: 'go.mod'
         check-latest: true
diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml
index dfefa94d..bd29929c 100644
--- a/.github/workflows/lint.yml
+++ b/.github/workflows/lint.yml
@@ -30,7 +30,7 @@ jobs:
       with:
         fetch-depth: 0
     - name: Set up Go
-      uses: actions/setup-go@f111f3307d8850f501ac008e886eec1fd1932a34 # v5.3.0
+      uses: actions/setup-go@0aaccfd150d50ccaeb58ebd88d36e91967a5f35b # v5.4.0
       with:
         go-version-file: 'go.mod'
         check-latest: true
diff --git a/.github/workflows/make.yml b/.github/workflows/make.yml
index 23b2a117..d148da17 100644
--- a/.github/workflows/make.yml
+++ b/.github/workflows/make.yml
@@ -40,7 +40,7 @@ jobs:
       with:
         fetch-depth: 0
     - name: Set up Go
-      uses: actions/setup-go@f111f3307d8850f501ac008e886eec1fd1932a34 # v5.3.0
+      uses: actions/setup-go@0aaccfd150d50ccaeb58ebd88d36e91967a5f35b # v5.4.0
       with:
         go-version-file: 'go.mod'
         check-latest: true
@@ -93,7 +93,7 @@ jobs:
         # macOS signing certificate (base64-encoded), used by Electron Builder
         MACOS_SIGNING_IDENTITY: ${{ secrets.MACOS_SIGNING_IDENTITY }}
     - name: Upload Kopia Artifacts
-      uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4.6.1
+      uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
       with:
         name: kopia-${{ matrix.os }}
         path: |
@@ -114,7 +114,7 @@ jobs:
           dist/kopia-ui/*.yml
         if-no-files-found: ignore
     - name: Upload Kopia Binary
-      uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4.6.1
+      uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
       with:
         name: kopia_binaries-${{ matrix.os }}
         path: |
@@ -137,13 +137,13 @@ jobs:
     - name: Install Linux-specific packages
       run: "sudo apt-get install -y createrepo-c"
     - name: Download Artifacts
-      uses: actions/download-artifact@cc203385981b70ca67e1cc392babf9cc229d5806 # v4.1.9
+      uses: actions/download-artifact@95815c38cf2ff2164869cbab79da8d1f422bc89e # v4.2.1
       with:
         pattern: kopia-*
         merge-multiple: true
         path: dist
     - name: Download Kopia Binaries
-      uses: actions/download-artifact@cc203385981b70ca67e1cc392babf9cc229d5806 # v4.1.9
+      uses: actions/download-artifact@95815c38cf2ff2164869cbab79da8d1f422bc89e # v4.2.1
       with:
         pattern: kopia_binaries-*
         merge-multiple: true
diff --git a/.github/workflows/ossf-scorecard.yml b/.github/workflows/ossf-scorecard.yml
index fb1a20ff..ac16ae3a 100644
--- a/.github/workflows/ossf-scorecard.yml
+++ b/.github/workflows/ossf-scorecard.yml
@@ -39,12 +39,12 @@ jobs:
       -
         # Upload the results to GitHub's code scanning dashboard.
         name: "Upload to results to dashboard"
-        uses: github/codeql-action/upload-sarif@b56ba49b26e50535fa1e7f7db0f4f7b4bf65d80d # v3.28.10
+        uses: github/codeql-action/upload-sarif@1b549b9259bda1cb5ddde3b41741a82a2d15a841 # v3.28.13
         with:
           sarif_file: results.sarif
       -
         name: "Upload analysis results as 'Job Artifact'"
-        uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4.6.1
+        uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
         with:
           name: SARIF file
           path: results.sarif
diff --git a/.github/workflows/providers-core.yml b/.github/workflows/providers-core.yml
index b2e4ffcd..eb56a09c 100644
--- a/.github/workflows/providers-core.yml
+++ b/.github/workflows/providers-core.yml
@@ -29,7 +29,7 @@ jobs:
         fetch-depth: 0
         ref: ${{ github.event.inputs.ref_name || github.ref }}
     - name: Set up Go
-      uses: actions/setup-go@f111f3307d8850f501ac008e886eec1fd1932a34 # v5.3.0
+      uses: actions/setup-go@0aaccfd150d50ccaeb58ebd88d36e91967a5f35b # v5.4.0
       with:
         go-version-file: 'go.mod'
         check-latest: true
diff --git a/.github/workflows/providers-extra.yml b/.github/workflows/providers-extra.yml
index 8f865ae7..e9d852bd 100644
--- a/.github/workflows/providers-extra.yml
+++ b/.github/workflows/providers-extra.yml
@@ -29,7 +29,7 @@ jobs:
         fetch-depth: 0
         ref: ${{ github.event.inputs.ref_name || github.ref }}
     - name: Set up Go
-      uses: actions/setup-go@f111f3307d8850f501ac008e886eec1fd1932a34 # v5.3.0
+      uses: actions/setup-go@0aaccfd150d50ccaeb58ebd88d36e91967a5f35b # v5.4.0
       with:
         go-version-file: 'go.mod'
         check-latest: true
diff --git a/.github/workflows/race-detector.yml b/.github/workflows/race-detector.yml
index 75e7bd1e..05dc7bb6 100644
--- a/.github/workflows/race-detector.yml
+++ b/.github/workflows/race-detector.yml
@@ -16,7 +16,7 @@ jobs:
       with:
         fetch-depth: 0
     - name: Set up Go
-      uses: actions/setup-go@f111f3307d8850f501ac008e886eec1fd1932a34 # v5.3.0
+      uses: actions/setup-go@0aaccfd150d50ccaeb58ebd88d36e91967a5f35b # v5.4.0
       with:
         go-version-file: 'go.mod'
         check-latest: true
diff --git a/.golangci.yml b/.golangci.yml
index 92be8a1e..ac164fec 100644
--- a/.golangci.yml
+++ b/.golangci.yml
@@ -1,87 +1,80 @@
-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
+    - forcetypeassert
+    - funcorder
     - gochecknoinits
-    - gci
+    - importas
     - ireturn # this one may be interesting to control allocations
     - gosmopolitan
     - musttag
@@ -90,90 +83,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..69ea7602 100644
--- a/cli/command_notification_configure_common.go
+++ b/cli/command_notification_configure_common.go
@@ -2,8 +2,8 @@
 
 import (
 	"context"
-
-	"golang.org/x/exp/maps"
+	"maps"
+	"slices"
 
 	"github.com/alecthomas/kingpin/v2"
 	"github.com/pkg/errors"
@@ -24,7 +24,7 @@ type commonNotificationOptions struct {
 func (c *commonNotificationOptions) setup(svc appServices, cmd *kingpin.CmdClause) {
 	c.notificationProfileFlag.setup(svc, cmd)
 	cmd.Flag("send-test-notification", "Test the notification").BoolVar(&c.sendTestNotification)
-	cmd.Flag("min-severity", "Minimum severity").EnumVar(&c.minSeverity, maps.Keys(notification.SeverityToNumber)...)
+	cmd.Flag("min-severity", "Minimum severity").EnumVar(&c.minSeverity, mapKeys(notification.SeverityToNumber)...)
 }
 
 // configureNotificationAction is a helper function that creates a Kingpin action that
@@ -102,3 +102,7 @@ func configureNotificationAction[T any](
 		})
 	})
 }
+
+func mapKeys[Map ~map[K]V, K comparable, V any](m Map) []K {
+	return slices.AppendSeq(make([]K, 0, len(m)), maps.Keys(m))
+}
diff --git a/cli/command_policy.go b/cli/command_policy.go
index 3e31b564..5f112399 100644
--- a/cli/command_policy.go
+++ b/cli/command_policy.go
@@ -40,7 +40,7 @@ type policyTargetFlags struct {
 }
 
 func (c *policyTargetFlags) setup(cmd *kingpin.CmdClause) {
-	cmd.Arg("target", "Select a particular policy ('user@host','@host','user@host:path' or a local path). Use --global to target the global policy.").StringsVar(&c.targets)
+	cmd.Arg("target", "Select a particular policy (a per-host policy `@host`, a per-user policy `user@host`, a per-path policy `user@host:path` or a local path). Use --global to target the global policy.").StringsVar(&c.targets)
 	cmd.Flag("global", "Select the global policy.").BoolVar(&c.global)
 }
 
diff --git a/cli/command_policy_edit.go b/cli/command_policy_edit.go
index 20562156..61b04f1b 100644
--- a/cli/command_policy_edit.go
+++ b/cli/command_policy_edit.go
@@ -57,7 +57,7 @@ type commandPolicyEdit struct {
 }
 
 func (c *commandPolicyEdit) setup(svc appServices, parent commandParent) {
-	cmd := parent.Command("edit", "Set snapshot policy for a single directory, user@host or a global policy.")
+	cmd := parent.Command("edit", "Edit policy.")
 	c.policyTargetFlags.setup(cmd)
 	cmd.Action(svc.repositoryWriterAction(c.run))
 	c.out.setup(svc)
diff --git a/cli/command_policy_export.go b/cli/command_policy_export.go
index a44f6a43..cf243ddd 100644
--- a/cli/command_policy_export.go
+++ b/cli/command_policy_export.go
@@ -27,7 +27,7 @@ type commandPolicyExport struct {
 const exportFilePerms = 0o600
 
 func (c *commandPolicyExport) setup(svc appServices, parent commandParent) {
-	cmd := parent.Command("export", "Exports the policy to the specified file, or to stdout if none is specified.")
+	cmd := parent.Command("export", "Exports the policies to the specified file, or to stdout if none is specified.")
 	cmd.Flag("to-file", "File path to export to").StringVar(&c.filePath)
 	cmd.Flag("overwrite", "Overwrite the file if it exists").BoolVar(&c.overwrite)
 
@@ -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_policy_remove.go b/cli/command_policy_remove.go
index 15052ecf..3a86df21 100644
--- a/cli/command_policy_remove.go
+++ b/cli/command_policy_remove.go
@@ -15,7 +15,7 @@ type commandPolicyDelete struct {
 }
 
 func (c *commandPolicyDelete) setup(svc appServices, parent commandParent) {
-	cmd := parent.Command("delete", "Remove snapshot policy for a single directory, user@host or a global policy.").Alias("remove").Alias("rm")
+	cmd := parent.Command("delete", "Remove policy.").Alias("remove").Alias("rm")
 	c.policyTargetFlags.setup(cmd)
 	cmd.Flag("dry-run", "Do not remove").Short('n').BoolVar(&c.dryRun)
 	cmd.Action(svc.repositoryWriterAction(c.run))
diff --git a/cli/command_policy_set.go b/cli/command_policy_set.go
index 46fd744a..27868ad5 100644
--- a/cli/command_policy_set.go
+++ b/cli/command_policy_set.go
@@ -31,7 +31,7 @@ type commandPolicySet struct {
 }
 
 func (c *commandPolicySet) setup(svc appServices, parent commandParent) {
-	cmd := parent.Command("set", "Set snapshot policy for a single directory, user@host or a global policy.")
+	cmd := parent.Command("set", "Set policy.")
 	c.policyTargetFlags.setup(cmd)
 	cmd.Flag(inheritPolicyString, "Enable or disable inheriting policies from the parent").BoolListVar(&c.inherit)
 
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/cli/command_snapshot_create.go b/cli/command_snapshot_create.go
index 3fb87f53..bd2d61af 100644
--- a/cli/command_snapshot_create.go
+++ b/cli/command_snapshot_create.go
@@ -296,9 +296,9 @@ func (c *commandSnapshotCreate) snapshotSingleSource(
 
 	var previous []*snapshot.Manifest
 
-	previous, finalErr = findPreviousSnapshotManifest(ctx, rep, sourceInfo, nil)
+	previous, finalErr = snapshot.FindPreviousManifests(ctx, rep, sourceInfo, nil)
 	if finalErr != nil {
-		return finalErr
+		return errors.Wrap(finalErr, "unable to find previous manifests")
 	}
 
 	if len(previous) > 0 {
@@ -407,50 +407,6 @@ func (c *commandSnapshotCreate) reportSnapshotStatus(ctx context.Context, manife
 	return nil
 }
 
-// findPreviousSnapshotManifest returns the list of previous snapshots for a given source, including
-// last complete snapshot and possibly some number of incomplete snapshots following it.
-func findPreviousSnapshotManifest(ctx context.Context, rep repo.Repository, sourceInfo snapshot.SourceInfo, noLaterThan *fs.UTCTimestamp) ([]*snapshot.Manifest, error) {
-	man, err := snapshot.ListSnapshots(ctx, rep, sourceInfo)
-	if err != nil {
-		return nil, errors.Wrap(err, "error listing previous snapshots")
-	}
-
-	// phase 1 - find latest complete snapshot.
-	var previousComplete *snapshot.Manifest
-
-	var previousCompleteStartTime fs.UTCTimestamp
-
-	var result []*snapshot.Manifest
-
-	for _, p := range man {
-		if noLaterThan != nil && p.StartTime.After(*noLaterThan) {
-			continue
-		}
-
-		if p.IncompleteReason == "" && (previousComplete == nil || p.StartTime.After(previousComplete.StartTime)) {
-			previousComplete = p
-			previousCompleteStartTime = p.StartTime
-		}
-	}
-
-	if previousComplete != nil {
-		result = append(result, previousComplete)
-	}
-
-	// add all incomplete snapshots after that
-	for _, p := range man {
-		if noLaterThan != nil && p.StartTime.After(*noLaterThan) {
-			continue
-		}
-
-		if p.IncompleteReason != "" && p.StartTime.After(previousCompleteStartTime) {
-			result = append(result, p)
-		}
-	}
-
-	return result, nil
-}
-
 func getLocalBackupPaths(ctx context.Context, rep repo.Repository) ([]string, error) {
 	log(ctx).Debugf("Looking for previous backups of '%v@%v'...", rep.ClientOptions().Hostname, rep.ClientOptions().Username)
 
diff --git a/cli/command_snapshot_migrate.go b/cli/command_snapshot_migrate.go
index eb04586b..329d969c 100644
--- a/cli/command_snapshot_migrate.go
+++ b/cli/command_snapshot_migrate.go
@@ -272,9 +272,9 @@ func (c *commandSnapshotMigrate) migrateSingleSourceSnapshot(ctx context.Context
 
 	log(ctx).Infof("migrating snapshot of %v at %v", s, formatTimestamp(m.StartTime.ToTime()))
 
-	previous, err := findPreviousSnapshotManifest(ctx, destRepo, m.Source, &m.StartTime)
+	previous, err := snapshot.FindPreviousManifests(ctx, destRepo, m.Source, &m.StartTime)
 	if err != nil {
-		return err
+		return errors.Wrap(err, "unable to find previous manifests")
 	}
 
 	policyTree, err := policy.TreeForSource(ctx, destRepo, m.Source)
diff --git a/go.mod b/go.mod
index 97300a21..b524fe33 100644
--- a/go.mod
+++ b/go.mod
@@ -2,18 +2,18 @@ module github.com/kopia/kopia
 
 go 1.23.0
 
-toolchain go1.23.7
+toolchain go1.23.8
 
 require (
 	cloud.google.com/go/storage v1.51.0
-	github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.1
-	github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.2
+	github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.0
+	github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.9.0
 	github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.6.0
 	github.com/alecthomas/kingpin/v2 v2.4.0
 	github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137
 	github.com/chmduquesne/rollinghash v4.0.0+incompatible
-	github.com/chromedp/cdproto v0.0.0-20250319231242-a755498943c8
-	github.com/chromedp/chromedp v0.13.3
+	github.com/chromedp/cdproto v0.0.0-20250403032234-65de8f5d025b
+	github.com/chromedp/chromedp v0.13.6
 	github.com/coreos/go-systemd/v22 v22.5.0
 	github.com/dustinkirkland/golang-petname v0.0.0-20191129215211-8e5a1ed0cff0
 	github.com/edsrzf/mmap-go v1.2.0
@@ -30,11 +30,11 @@ require (
 	github.com/klauspost/compress v1.18.0
 	github.com/klauspost/pgzip v1.2.6
 	github.com/klauspost/reedsolomon v1.12.4
-	github.com/kopia/htmluibuild v0.0.1-0.20250303064157-676b1b5bc735
+	github.com/kopia/htmluibuild v0.0.1-0.20250416164135-16793194ee70
 	github.com/kylelemons/godebug v1.1.0
 	github.com/mattn/go-colorable v0.1.14
 	github.com/mattn/go-isatty v0.0.20
-	github.com/minio/minio-go/v7 v7.0.89
+	github.com/minio/minio-go/v7 v7.0.90
 	github.com/mocktools/go-smtp-mock/v2 v2.4.0
 	github.com/mxk/go-vss v1.2.0
 	github.com/natefinch/atomic v1.0.1
@@ -43,14 +43,14 @@ require (
 	github.com/pkg/errors v0.9.1
 	github.com/pkg/profile v1.7.0
 	github.com/pkg/sftp v1.13.9
-	github.com/prometheus/client_golang v1.21.1
-	github.com/prometheus/client_model v0.6.1
+	github.com/prometheus/client_golang v1.22.0
+	github.com/prometheus/client_model v0.6.2
 	github.com/prometheus/common v0.63.0
 	github.com/sanity-io/litter v1.5.8
 	github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966
 	github.com/stretchr/testify v1.10.0
 	github.com/studio-b12/gowebdav v0.10.0
-	github.com/tg123/go-htpasswd v1.2.3
+	github.com/tg123/go-htpasswd v1.2.4
 	github.com/zalando/go-keyring v0.2.6
 	github.com/zeebo/blake3 v0.2.4
 	go.opentelemetry.io/otel v1.35.0
@@ -59,34 +59,34 @@ require (
 	go.opentelemetry.io/otel/trace v1.35.0
 	go.uber.org/multierr v1.11.0
 	go.uber.org/zap v1.27.0
-	golang.org/x/crypto v0.36.0
+	golang.org/x/crypto v0.37.0
 	golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1
 	golang.org/x/mod v0.24.0
-	golang.org/x/net v0.38.0
-	golang.org/x/oauth2 v0.28.0
-	golang.org/x/sync v0.12.0
-	golang.org/x/sys v0.31.0
-	golang.org/x/term v0.30.0
-	golang.org/x/text v0.23.0
-	google.golang.org/api v0.228.0
-	google.golang.org/grpc v1.71.0
+	golang.org/x/net v0.39.0
+	golang.org/x/oauth2 v0.29.0
+	golang.org/x/sync v0.13.0
+	golang.org/x/sys v0.32.0
+	golang.org/x/term v0.31.0
+	golang.org/x/text v0.24.0
+	google.golang.org/api v0.229.0
+	google.golang.org/grpc v1.72.0
 	google.golang.org/protobuf v1.36.6
 	gopkg.in/kothar/go-backblaze.v0 v0.0.0-20210124194846-35409b867216
 )
 
 require (
 	al.essio.dev/pkg/shellescape v1.5.1 // indirect
-	cel.dev/expr v0.19.2 // indirect
+	cel.dev/expr v0.20.0 // indirect
 	cloud.google.com/go v0.118.3 // indirect
-	cloud.google.com/go/auth v0.15.0 // indirect
+	cloud.google.com/go/auth v0.16.0 // indirect
 	cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect
 	cloud.google.com/go/compute/metadata v0.6.0 // indirect
 	cloud.google.com/go/iam v1.4.1 // indirect
 	cloud.google.com/go/monitoring v1.24.0 // indirect
-	github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 // indirect
-	github.com/AzureAD/microsoft-authentication-library-for-go v1.3.3 // indirect
+	github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.1 // indirect
+	github.com/AzureAD/microsoft-authentication-library-for-go v1.4.2 // indirect
 	github.com/GehirnInc/crypt v0.0.0-20230320061759-8cc1b52080c5 // indirect
-	github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.25.0 // indirect
+	github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.26.0 // indirect
 	github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.51.0 // indirect
 	github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.51.0 // indirect
 	github.com/beorn7/perks v1.0.1 // indirect
@@ -103,6 +103,7 @@ require (
 	github.com/felixge/httpsnoop v1.0.4 // indirect
 	github.com/frankban/quicktest v1.13.1 // indirect
 	github.com/go-ini/ini v1.67.0 // indirect
+	github.com/go-jose/go-jose/v4 v4.0.5 // indirect
 	github.com/go-json-experiment/json v0.0.0-20250211171154-1ae217ad3535 // indirect
 	github.com/go-logr/logr v1.4.2 // indirect
 	github.com/go-logr/stdr v1.2.2 // indirect
@@ -131,18 +132,20 @@ require (
 	github.com/pquerna/ffjson v0.0.0-20190930134022-aa0246cd15f7 // indirect
 	github.com/prometheus/procfs v0.15.1 // indirect
 	github.com/rs/xid v1.6.0 // indirect
+	github.com/spiffe/go-spiffe/v2 v2.5.0 // indirect
 	github.com/xhit/go-str2duration/v2 v2.1.0 // indirect
+	github.com/zeebo/errs v1.4.0 // indirect
 	go.opentelemetry.io/auto/sdk v1.1.0 // indirect
 	go.opentelemetry.io/contrib/detectors/gcp v1.34.0 // indirect
-	go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.59.0 // indirect
-	go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.59.0 // indirect
+	go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0 // indirect
+	go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 // indirect
 	go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.35.0 // indirect
 	go.opentelemetry.io/otel/metric v1.35.0 // indirect
-	go.opentelemetry.io/otel/sdk/metric v1.34.0 // indirect
+	go.opentelemetry.io/otel/sdk/metric v1.35.0 // indirect
 	go.opentelemetry.io/proto/otlp v1.5.0 // indirect
 	golang.org/x/time v0.11.0 // indirect
 	google.golang.org/genproto v0.0.0-20250303144028-a0af3efb3deb // indirect
 	google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb // indirect
-	google.golang.org/genproto/googleapis/rpc v0.0.0-20250313205543-e70fdf4c4cb4 // indirect
+	google.golang.org/genproto/googleapis/rpc v0.0.0-20250414145226-207652e42e2e // indirect
 	gopkg.in/yaml.v3 v3.0.1 // indirect
 )
diff --git a/go.sum b/go.sum
index bb0e13fc..d4e2e5a3 100644
--- a/go.sum
+++ b/go.sum
@@ -1,11 +1,11 @@
 al.essio.dev/pkg/shellescape v1.5.1 h1:86HrALUujYS/h+GtqoB26SBEdkWfmMI6FubjXlsXyho=
 al.essio.dev/pkg/shellescape v1.5.1/go.mod h1:6sIqp7X2P6mThCQ7twERpZTuigpr6KbZWtls1U8I890=
-cel.dev/expr v0.19.2 h1:V354PbqIXr9IQdwy4SYA4xa0HXaWq1BUPAGzugBY5V4=
-cel.dev/expr v0.19.2/go.mod h1:MrpN08Q+lEBs+bGYdLxxHkZoUSsCp0nSKTs0nTymJgw=
+cel.dev/expr v0.20.0 h1:OunBvVCfvpWlt4dN7zg3FM6TDkzOePe1+foGJ9AXeeI=
+cel.dev/expr v0.20.0/go.mod h1:MrpN08Q+lEBs+bGYdLxxHkZoUSsCp0nSKTs0nTymJgw=
 cloud.google.com/go v0.118.3 h1:jsypSnrE/w4mJysioGdMBg4MiW/hHx/sArFpaBWHdME=
 cloud.google.com/go v0.118.3/go.mod h1:Lhs3YLnBlwJ4KA6nuObNMZ/fCbOQBPuWKPoE0Wa/9Vc=
-cloud.google.com/go/auth v0.15.0 h1:Ly0u4aA5vG/fsSsxu98qCQBemXtAtJf+95z9HK+cxps=
-cloud.google.com/go/auth v0.15.0/go.mod h1:WJDGqZ1o9E9wKIL+IwStfyn/+s59zl4Bi+1KQNVXLZ8=
+cloud.google.com/go/auth v0.16.0 h1:Pd8P1s9WkcrBE2n/PhAwKsdrR35V3Sg2II9B+ndM3CU=
+cloud.google.com/go/auth v0.16.0/go.mod h1:1howDHJ5IETh/LwYs3ZxvlkXF48aSqqJUM+5o02dNOI=
 cloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc=
 cloud.google.com/go/auth/oauth2adapt v0.2.8/go.mod h1:XQ9y31RkqZCcwJWNSx2Xvric3RrU88hAYYbjDWYDL+c=
 cloud.google.com/go/compute/metadata v0.6.0 h1:A6hENjEsCDtC1k8byVsgwvVcioamEHvZ4j01OwKxG9I=
@@ -22,27 +22,27 @@ cloud.google.com/go/storage v1.51.0 h1:ZVZ11zCiD7b3k+cH5lQs/qcNaoSz3U9I0jgwVzqDl
 cloud.google.com/go/storage v1.51.0/go.mod h1:YEJfu/Ki3i5oHC/7jyTgsGZwdQ8P9hqMqvpi5kRKGgc=
 cloud.google.com/go/trace v1.11.3 h1:c+I4YFjxRQjvAhRmSsmjpASUKq88chOX854ied0K/pE=
 cloud.google.com/go/trace v1.11.3/go.mod h1:pt7zCYiDSQjC9Y2oqCsh9jF4GStB/hmjrYLsxRR27q8=
-github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.1 h1:DSDNVxqkoXJiko6x8a90zidoYqnYYa6c1MTzDKzKkTo=
-github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.1/go.mod h1:zGqV2R4Cr/k8Uye5w+dgQ06WJtEcbQG/8J7BB6hnCr4=
-github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.2 h1:F0gBpfdPLGsw+nsgk6aqqkZS1jiixa5WwFe3fk/T3Ys=
-github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.2/go.mod h1:SqINnQ9lVVdRlyC8cd1lCI0SdX4n2paeABd2K8ggfnE=
+github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.0 h1:Gt0j3wceWMwPmiazCa8MzMA0MfhmPIz0Qp0FJ6qcM0U=
+github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.0/go.mod h1:Ot/6aikWnKWi4l9QB7qVSwa8iMphQNqkWALMoNT3rzM=
+github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.9.0 h1:OVoM452qUFBrX+URdH3VpR299ma4kfom0yB0URYky9g=
+github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.9.0/go.mod h1:kUjrAo8bgEwLeZ/CmHqNl3Z/kPm7y6FKfxxK0izYUg4=
 github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.2 h1:yz1bePFlP5Vws5+8ez6T3HWXPmwOK7Yvq8QxDBD3SKY=
 github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.2/go.mod h1:Pa9ZNPuoNu/GztvBSKk9J1cDJW6vk/n0zLtV4mgd8N8=
-github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 h1:ywEEhmNahHBihViHepv3xPBn1663uRv2t2q/ESv9seY=
-github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0/go.mod h1:iZDifYGJTIgIIkYRNWPENUnqx6bJ2xnSDFI2tjwZNuY=
+github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.1 h1:FPKJS1T+clwv+OLGt13a8UjqeRuh0O4SJ3lUriThc+4=
+github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.1/go.mod h1:j2chePtV91HrC22tGoRX3sGY42uF13WzmmV80/OdVAA=
 github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/storage/armstorage v1.6.0 h1:PiSrjRPpkQNjrM8H0WwKMnZUdu1RGMtd/LdGKUrOo+c=
 github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/storage/armstorage v1.6.0/go.mod h1:oDrbWx4ewMylP7xHivfgixbfGBT6APAwsSoHRKotnIc=
 github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.6.0 h1:UXT0o77lXQrikd1kgwIPQOUect7EoR/+sbP4wQKdzxM=
 github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.6.0/go.mod h1:cTvi54pg19DoT07ekoeMgE/taAwNtCShVeZqA+Iv2xI=
 github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1 h1:WJTmL004Abzc5wDB5VtZG2PJk5ndYDgVacGqfirKxjM=
 github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1/go.mod h1:tCcJZ0uHAmvjsVYzEFivsRTN00oz5BEsRgQHu5JZ9WE=
-github.com/AzureAD/microsoft-authentication-library-for-go v1.3.3 h1:H5xDQaE3XowWfhZRUpnfC+rGZMEVoSiji+b+/HFAPU4=
-github.com/AzureAD/microsoft-authentication-library-for-go v1.3.3/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI=
+github.com/AzureAD/microsoft-authentication-library-for-go v1.4.2 h1:oygO0locgZJe7PpYPXT5A29ZkwJaPqcva7BVeemZOZs=
+github.com/AzureAD/microsoft-authentication-library-for-go v1.4.2/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI=
 github.com/GehirnInc/crypt v0.0.0-20190301055215-6c0105aabd46/go.mod h1:kC29dT1vFpj7py2OvG1khBdQpo3kInWP+6QipLbdngo=
 github.com/GehirnInc/crypt v0.0.0-20230320061759-8cc1b52080c5 h1:IEjq88XO4PuBDcvmjQJcQGg+w+UaafSy8G5Kcb5tBhI=
 github.com/GehirnInc/crypt v0.0.0-20230320061759-8cc1b52080c5/go.mod h1:exZ0C/1emQJAw5tHOaUDyY1ycttqBAPcxuzf7QbY6ec=
-github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.25.0 h1:3c8yed4lgqTt+oTQ+JNMDo+F4xprBf+O/il4ZC0nRLw=
-github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.25.0/go.mod h1:obipzmGjfSjam60XLwGfqUkJsfiheAl+TUjG+4yzyPM=
+github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.26.0 h1:f2Qw/Ehhimh5uO1fayV0QIW7DShEQqhtUfhYc+cBPlw=
+github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.26.0/go.mod h1:2bIszWvQRlJVmJLiuLhukLImRjKPcYdzzsx6darK02A=
 github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.51.0 h1:fYE9p3esPxA/C0rQ0AHhP0drtPXDRhaWiwg1DPqO7IU=
 github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.51.0/go.mod h1:BnBReJLvVYx2CS/UHOgVz2BXKXD9wsQPxZug20nZhd0=
 github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.51.0 h1:OqVGm6Ei3x5+yZmSJG1Mh2NwHvpVmZ08CB5qJhT9Nuk=
@@ -61,10 +61,10 @@ github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UF
 github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
 github.com/chmduquesne/rollinghash v4.0.0+incompatible h1:hnREQO+DXjqIw3rUTzWN7/+Dpw+N5Um8zpKV0JOEgbo=
 github.com/chmduquesne/rollinghash v4.0.0+incompatible/go.mod h1:Uc2I36RRfTAf7Dge82bi3RU0OQUmXT9iweIcPqvr8A0=
-github.com/chromedp/cdproto v0.0.0-20250319231242-a755498943c8 h1:AqW2bDQf67Zbq6Tpop/+yJSIknxhiQecO2B8jNYTAPs=
-github.com/chromedp/cdproto v0.0.0-20250319231242-a755498943c8/go.mod h1:NItd7aLkcfOA/dcMXvl8p1u+lQqioRMq/SqDp71Pb/k=
-github.com/chromedp/chromedp v0.13.3 h1:c6nTn97XQBykzcXiGYL5LLebw3h3CEyrCihm4HquYh0=
-github.com/chromedp/chromedp v0.13.3/go.mod h1:khsDP9OP20GrowpJfZ7N05iGCwcAYxk7qf9AZBzR3Qw=
+github.com/chromedp/cdproto v0.0.0-20250403032234-65de8f5d025b h1:jJmiCljLNTaq/O1ju9Bzz2MPpFlmiTn0F7LwCoeDZVw=
+github.com/chromedp/cdproto v0.0.0-20250403032234-65de8f5d025b/go.mod h1:NItd7aLkcfOA/dcMXvl8p1u+lQqioRMq/SqDp71Pb/k=
+github.com/chromedp/chromedp v0.13.6 h1:xlNunMyzS5bu3r/QKrb3fzX6ow3WBQ6oao+J65PGZxk=
+github.com/chromedp/chromedp v0.13.6/go.mod h1:h8GPP6ZtLMLsU8zFbTcb7ZDGCvCy8j/vRoFmRltQx9A=
 github.com/chromedp/sysutil v1.1.0 h1:PUFNv5EcprjqXZD9nJb9b/c9ibAbxiYo4exNWZyipwM=
 github.com/chromedp/sysutil v1.1.0/go.mod h1:WiThHUdltqCNKGc4gaU50XgYjwjYIhKWoHGPTUfWTJ8=
 github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
@@ -109,6 +109,8 @@ github.com/frankban/quicktest v1.13.1 h1:xVm/f9seEhZFL9+n5kv5XLrGwy6elc4V9v/XFY2
 github.com/frankban/quicktest v1.13.1/go.mod h1:NeW+ay9A/U67EYXNFA1nPE8e/tnQv/09mUdL/ijj8og=
 github.com/go-ini/ini v1.67.0 h1:z6ZrTEZqSWOTyH2FlglNbNgARyHG8oLW9gMELqKr06A=
 github.com/go-ini/ini v1.67.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8=
+github.com/go-jose/go-jose/v4 v4.0.5 h1:M6T8+mKZl/+fNNuFHvGIzDz7BTLQPIounk/b9dw3AaE=
+github.com/go-jose/go-jose/v4 v4.0.5/go.mod h1:s3P1lRrkT8igV8D9OjyL4WRyHvjB6a4JSllnOrmmBOA=
 github.com/go-json-experiment/json v0.0.0-20250211171154-1ae217ad3535 h1:yE7argOs92u+sSCRgqqe6eF+cDaVhSPlioy1UkA0p/w=
 github.com/go-json-experiment/json v0.0.0-20250211171154-1ae217ad3535/go.mod h1:BWmvoE1Xia34f3l/ibJweyhrT+aROb/FQ6d+37F0e2s=
 github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
@@ -171,8 +173,8 @@ github.com/hanwen/go-fuse/v2 v2.7.2/go.mod h1:ugNaD/iv5JYyS1Rcvi57Wz7/vrLQJo10mm
 github.com/hashicorp/cronexpr v1.1.2 h1:wG/ZYIKT+RT3QkOdgYc+xsKWVRgnxJ1OJtjjy84fJ9A=
 github.com/hashicorp/cronexpr v1.1.2/go.mod h1:P4wA0KBl9C5q2hABiMO7cp6jcIg96CDh1Efb3g1PWA4=
 github.com/ianlancetaylor/demangle v0.0.0-20210905161508-09a460cdf81d/go.mod h1:aYm2/VgdVmcIU8iMfdMvDMsRAQjcfZSKFby6HOFvi/w=
-github.com/keybase/go-keychain v0.0.0-20231219164618-57a3676c3af6 h1:IsMZxCuZqKuao2vNdfD82fjjgPLfyHLpR41Z88viRWs=
-github.com/keybase/go-keychain v0.0.0-20231219164618-57a3676c3af6/go.mod h1:3VeWNIJaW+O5xpRQbPp0Ybqu1vJd/pm7s2F473HRrkw=
+github.com/keybase/go-keychain v0.0.1 h1:way+bWYa6lDppZoZcgMbYsvC7GxljxrskdNInRtuthU=
+github.com/keybase/go-keychain v0.0.1/go.mod h1:PdEILRW3i9D8JcdM+FmY6RwkHGnhHxXwkPPMeUgOK1k=
 github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
 github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
 github.com/klauspost/cpuid/v2 v2.0.1/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
@@ -182,8 +184,8 @@ github.com/klauspost/pgzip v1.2.6 h1:8RXeL5crjEUFnR2/Sn6GJNWtSQ3Dk8pq4CL3jvdDyjU
 github.com/klauspost/pgzip v1.2.6/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs=
 github.com/klauspost/reedsolomon v1.12.4 h1:5aDr3ZGoJbgu/8+j45KtUJxzYm8k08JGtB9Wx1VQ4OA=
 github.com/klauspost/reedsolomon v1.12.4/go.mod h1:d3CzOMOt0JXGIFZm1StgkyF14EYr3xneR2rNWo7NcMU=
-github.com/kopia/htmluibuild v0.0.1-0.20250303064157-676b1b5bc735 h1:jhTarsFN8q9gglhSiFHapzVa2i2Wsg/zbm35MWchDYk=
-github.com/kopia/htmluibuild v0.0.1-0.20250303064157-676b1b5bc735/go.mod h1:h53A5JM3t2qiwxqxusBe+PFgGcgZdS+DWCQvG5PTlto=
+github.com/kopia/htmluibuild v0.0.1-0.20250416164135-16793194ee70 h1:fKJT9+uw2vrQyCV0wJXHqkDtVf5fBBgMtfl0PK8kY2s=
+github.com/kopia/htmluibuild v0.0.1-0.20250416164135-16793194ee70/go.mod h1:h53A5JM3t2qiwxqxusBe+PFgGcgZdS+DWCQvG5PTlto=
 github.com/kr/fs v0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8=
 github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
 github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
@@ -206,8 +208,8 @@ github.com/minio/crc64nvme v1.0.1 h1:DHQPrYPdqK7jQG/Ls5CTBZWeex/2FMS3G5XGkycuFrY
 github.com/minio/crc64nvme v1.0.1/go.mod h1:eVfm2fAzLlxMdUGc0EEBGSMmPwmXD5XiNRpnu9J3bvg=
 github.com/minio/md5-simd v1.1.2 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34=
 github.com/minio/md5-simd v1.1.2/go.mod h1:MzdKDxYpY2BT9XQFocsiZf/NKVtR7nkE4RoEpN+20RM=
-github.com/minio/minio-go/v7 v7.0.89 h1:hx4xV5wwTUfyv8LarhJAwNecnXpoTsj9v3f3q/ZkiJU=
-github.com/minio/minio-go/v7 v7.0.89/go.mod h1:2rFnGAp02p7Dddo1Fq4S2wYOfpF0MUTSeLTRC90I204=
+github.com/minio/minio-go/v7 v7.0.90 h1:TmSj1083wtAD0kEYTx7a5pFsv3iRYMsOJ6A4crjA1lE=
+github.com/minio/minio-go/v7 v7.0.90/go.mod h1:uvMUcGrpgeSAAI6+sD3818508nUyMULw94j2Nxku/Go=
 github.com/moby/sys/mountinfo v0.6.2 h1:BzJjoreD5BMFNmD9Rus6gdd1pLuecOFPt8wC+Vygl78=
 github.com/moby/sys/mountinfo v0.6.2/go.mod h1:IJb6JQeOklcdMU9F5xQ8ZALD+CUr5VlGpwtX+VE0rpI=
 github.com/mocktools/go-smtp-mock/v2 v2.4.0 h1:u0ky0iyNW/LEMKAFRTsDivHyP8dHYxe/cV3FZC3rRjo=
@@ -239,16 +241,16 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
 github.com/pquerna/ffjson v0.0.0-20190930134022-aa0246cd15f7 h1:xoIK0ctDddBMnc74udxJYBqlo9Ylnsp1waqjLsnef20=
 github.com/pquerna/ffjson v0.0.0-20190930134022-aa0246cd15f7/go.mod h1:YARuvh7BUWHNhzDq2OM5tzR2RiCcN2D7sapiKyCel/M=
-github.com/prometheus/client_golang v1.21.1 h1:DOvXXTqVzvkIewV/CDPFdejpMCGeMcbGCQ8YOmu+Ibk=
-github.com/prometheus/client_golang v1.21.1/go.mod h1:U9NM32ykUErtVBxdvD3zfi+EuFkkaBvMb09mIfe0Zgg=
-github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E=
-github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY=
+github.com/prometheus/client_golang v1.22.0 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/Y92Vm0Zc6Q=
+github.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0=
+github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
+github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
 github.com/prometheus/common v0.63.0 h1:YR/EIY1o3mEFP/kZCD7iDMnLPlGyuU2Gb3HIcXnA98k=
 github.com/prometheus/common v0.63.0/go.mod h1:VVFF/fBIoToEnWRVkYoXEkq3R3paCoxG9PXP74SnV18=
 github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc=
 github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
-github.com/redis/go-redis/v9 v9.7.0 h1:HhLSs+B6O021gwzl+locl0zEDnyNkxMtf/Z3NNBMa9E=
-github.com/redis/go-redis/v9 v9.7.0/go.mod h1:f6zhXITC7JUJIlPEiBOTXxJgPLdZcA93GewI7inzyWw=
+github.com/redis/go-redis/v9 v9.7.3 h1:YpPyAayJV+XErNsatSElgRZZVCwXX9QzkKYNvO7x0wM=
+github.com/redis/go-redis/v9 v9.7.3/go.mod h1:bGUrSggJ9X9GUmZpZNEOQKaANxSGgOEBRltRTZHSvrA=
 github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
 github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
 github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
@@ -258,6 +260,8 @@ github.com/sanity-io/litter v1.5.8 h1:uM/2lKrWdGbRXDrIq08Lh9XtVYoeGtcQxk9rtQ7+rY
 github.com/sanity-io/litter v1.5.8/go.mod h1:9gzJgR2i4ZpjZHsKvUXIRQVk7P+yM3e+jAF7bU2UI5U=
 github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966 h1:JIAuq3EEf9cgbU6AtGPK4CTG3Zf6CKMNqf0MHTggAUA=
 github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966/go.mod h1:sUM3LWHvSMaG192sy56D9F7CNvL7jUJVXoqM1QKLnog=
+github.com/spiffe/go-spiffe/v2 v2.5.0 h1:N2I01KCUkv1FAjZXJMwh95KK1ZIQLYbPfhaxw8WS0hE=
+github.com/spiffe/go-spiffe/v2 v2.5.0/go.mod h1:P+NxobPc6wXhVtINNtFjNWGBTreew1GBUCwT2wPmb7g=
 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
 github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
 github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
@@ -273,8 +277,8 @@ github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOf
 github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
 github.com/studio-b12/gowebdav v0.10.0 h1:Yewz8FFiadcGEu4hxS/AAJQlHelndqln1bns3hcJIYc=
 github.com/studio-b12/gowebdav v0.10.0/go.mod h1:bHA7t77X/QFExdeAnDzK6vKM34kEZAcE1OX4MfiwjkE=
-github.com/tg123/go-htpasswd v1.2.3 h1:ALR6ZBIc2m9u70m+eAWUFt5p43ISbIvAvRFYzZPTOY8=
-github.com/tg123/go-htpasswd v1.2.3/go.mod h1:FcIrK0J+6zptgVwK1JDlqyajW/1B4PtuJ/FLWl7nx8A=
+github.com/tg123/go-htpasswd v1.2.4 h1:HgH8KKCjdmo7jjXWN9k1nefPBd7Be3tFCTjc2jPraPU=
+github.com/tg123/go-htpasswd v1.2.4/go.mod h1:EKThQok9xHkun6NBMynNv6Jmu24A33XdZzzl4Q7H1+0=
 github.com/xhit/go-str2duration/v2 v2.1.0 h1:lxklc02Drh6ynqX+DdPyp5pCKLUQpRT8bp8Ydu2Bstc=
 github.com/xhit/go-str2duration/v2 v2.1.0/go.mod h1:ohY8p+0f07DiV6Em5LKB0s2YpLtXVyJfNt1+BlmyAsU=
 github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
@@ -284,16 +288,18 @@ github.com/zeebo/assert v1.1.0 h1:hU1L1vLTHsnO8x8c9KAR5GmM5QscxHg5RNU5z5qbUWY=
 github.com/zeebo/assert v1.1.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0=
 github.com/zeebo/blake3 v0.2.4 h1:KYQPkhpRtcqh0ssGYcKLG1JYvddkEA8QwCM/yBqhaZI=
 github.com/zeebo/blake3 v0.2.4/go.mod h1:7eeQ6d2iXWRGF6npfaxl2CU+xy2Fjo2gxeyZGCRUjcE=
+github.com/zeebo/errs v1.4.0 h1:XNdoD/RRMKP7HD0UhJnIzUy74ISdGGxURlYG8HSWSfM=
+github.com/zeebo/errs v1.4.0/go.mod h1:sgbWHsvVuTPHcqJJGQ1WhI5KbWlHYz+2+2C/LSEtCw4=
 github.com/zeebo/pcg v1.0.1 h1:lyqfGeWiv4ahac6ttHs+I5hwtH/+1mrhlCtVNQM2kHo=
 github.com/zeebo/pcg v1.0.1/go.mod h1:09F0S9iiKrwn9rlI5yjLkmrug154/YRW6KnnXVDM/l4=
 go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
 go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
 go.opentelemetry.io/contrib/detectors/gcp v1.34.0 h1:JRxssobiPg23otYU5SbWtQC//snGVIM3Tx6QRzlQBao=
 go.opentelemetry.io/contrib/detectors/gcp v1.34.0/go.mod h1:cV4BMFcscUR/ckqLkbfQmF0PRsq8w/lMGzdbCSveBHo=
-go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.59.0 h1:rgMkmiGfix9vFJDcDi1PK8WEQP4FLQwLDfhp5ZLpFeE=
-go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.59.0/go.mod h1:ijPqXp5P6IRRByFVVg9DY8P5HkxkHE5ARIa+86aXPf4=
-go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.59.0 h1:CV7UdSGJt/Ao6Gp4CXckLxVRRsRgDHoI8XjbL3PDl8s=
-go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.59.0/go.mod h1:FRmFuRJfag1IZ2dPkHnEoSFVgTVPUd2qf5Vi69hLb8I=
+go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0 h1:x7wzEgXfnzJcHDwStJT+mxOz4etr2EcexjqhBvmoakw=
+go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0/go.mod h1:rg+RlpR5dKwaS95IyyZqj5Wd4E13lk/msnTS0Xl9lJM=
+go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 h1:sbiXRNDSWJOTobXh5HyQKjq6wUC5tNybqjIqDpAY4CU=
+go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0/go.mod h1:69uWxva0WgAA/4bu2Yy70SLDBwZXuQ6PbBpbsa5iZrQ=
 go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ=
 go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y=
 go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.35.0 h1:1fTNlAIJZGWLP5FVu0fikVry1IsiUnXjf7QFvoNN3Xw=
@@ -306,8 +312,8 @@ go.opentelemetry.io/otel/metric v1.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/
 go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE=
 go.opentelemetry.io/otel/sdk v1.35.0 h1:iPctf8iprVySXSKJffSS79eOjl9pvxV9ZqOWT0QejKY=
 go.opentelemetry.io/otel/sdk v1.35.0/go.mod h1:+ga1bZliga3DxJ3CQGg3updiaAJoNECOgJREo9KHGQg=
-go.opentelemetry.io/otel/sdk/metric v1.34.0 h1:5CeK9ujjbFVL5c1PhLuStg1wxA7vQv7ce1EK0Gyvahk=
-go.opentelemetry.io/otel/sdk/metric v1.34.0/go.mod h1:jQ/r8Ze28zRKoNRdkjCZxfs6YvBTG1+YIqyFVFYec5w=
+go.opentelemetry.io/otel/sdk/metric v1.35.0 h1:1RriWBmCKgkeHEhM7a2uMjMUfP7MsOF5JpUCaEqEI9o=
+go.opentelemetry.io/otel/sdk/metric v1.35.0/go.mod h1:is6XYCUMpcKi+ZsOvfluY5YstFnhW0BidkR+gL+qN+w=
 go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs=
 go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc=
 go.opentelemetry.io/proto/otlp v1.5.0 h1:xJvq7gMzB31/d406fB8U5CBdyQGw4P399D1aQWU/3i4=
@@ -325,8 +331,8 @@ golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliY
 golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
 golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
 golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
-golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34=
-golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc=
+golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE=
+golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc=
 golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 h1:k/i9J1pBpvlfR+9QsetwPyERsqu1GIbi967PQMq3Ivc=
 golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w=
 golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
@@ -345,10 +351,10 @@ golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
 golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
 golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
 golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
-golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8=
-golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
-golang.org/x/oauth2 v0.28.0 h1:CrgCKl8PPAVtLnU3c+EDw6x11699EWlsDeWNWKdIOkc=
-golang.org/x/oauth2 v0.28.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8=
+golang.org/x/net v0.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY=
+golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E=
+golang.org/x/oauth2 v0.29.0 h1:WdYw2tdTK1S8olAzWHdgeqfy+Mtm9XNhv/xJsY65d98=
+golang.org/x/oauth2 v0.29.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8=
 golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -356,8 +362,8 @@ golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
 golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
 golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
 golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
-golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw=
-golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
+golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610=
+golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
 golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -373,8 +379,8 @@ golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
 golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
 golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
-golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
-golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
+golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20=
+golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
 golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
@@ -384,8 +390,8 @@ golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
 golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
 golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
 golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=
-golang.org/x/term v0.30.0 h1:PQ39fJZ+mfadBm0y5WlL4vlM7Sx1Hgf13sMIY2+QS9Y=
-golang.org/x/term v0.30.0/go.mod h1:NYYFdzHoI5wRh/h5tDMdMqCqPJZEuNqVR5xJLd/n67g=
+golang.org/x/term v0.31.0 h1:erwDkOK1Msy6offm1mOgvspSkslFnIGsFnxOKoufg3o=
+golang.org/x/term v0.31.0/go.mod h1:R4BeIy7D95HzImkxGkTW1UQTtP54tio2RyHz7PwK0aw=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
@@ -395,8 +401,8 @@ golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
 golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
 golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
 golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
-golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
-golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=
+golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0=
+golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU=
 golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0=
 golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
 golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
@@ -407,16 +413,16 @@ golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58
 golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
 golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
-google.golang.org/api v0.228.0 h1:X2DJ/uoWGnY5obVjewbp8icSL5U4FzuCfy9OjbLSnLs=
-google.golang.org/api v0.228.0/go.mod h1:wNvRS1Pbe8r4+IfBIniV8fwCpGwTrYa+kMUDiC5z5a4=
+google.golang.org/api v0.229.0 h1:p98ymMtqeJ5i3lIBMj5MpR9kzIIgzpHHh8vQ+vgAzx8=
+google.golang.org/api v0.229.0/go.mod h1:wyDfmq5g1wYJWn29O22FDWN48P7Xcz0xz+LBpptYvB0=
 google.golang.org/genproto v0.0.0-20250303144028-a0af3efb3deb h1:ITgPrl429bc6+2ZraNSzMDk3I95nmQln2fuPstKwFDE=
 google.golang.org/genproto v0.0.0-20250303144028-a0af3efb3deb/go.mod h1:sAo5UzpjUwgFBCzupwhcLcxHVDK7vG5IqI30YnwX2eE=
 google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb h1:p31xT4yrYrSM/G4Sn2+TNUkVhFCbG9y8itM2S6Th950=
 google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb/go.mod h1:jbe3Bkdp+Dh2IrslsFCklNhweNTBgSYanP1UXhJDhKg=
-google.golang.org/genproto/googleapis/rpc v0.0.0-20250313205543-e70fdf4c4cb4 h1:iK2jbkWL86DXjEx0qiHcRE9dE4/Ahua5k6V8OWFb//c=
-google.golang.org/genproto/googleapis/rpc v0.0.0-20250313205543-e70fdf4c4cb4/go.mod h1:LuRYeWDFV6WOn90g357N17oMCaxpgCnbi/44qJvDn2I=
-google.golang.org/grpc v1.71.0 h1:kF77BGdPTQ4/JZWMlb9VpJ5pa25aqvVqogsxNHHdeBg=
-google.golang.org/grpc v1.71.0/go.mod h1:H0GRtasmQOh9LkFoCPDu3ZrwUtD1YGE+b2vYBYd/8Ec=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20250414145226-207652e42e2e h1:ztQaXfzEXTmCBvbtWYRhJxW+0iJcz2qXfd38/e9l7bA=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20250414145226-207652e42e2e/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
+google.golang.org/grpc v1.72.0 h1:S7UkcVa60b5AAQTaO6ZKamFp1zMZSU0fGDK2WZLbBnM=
+google.golang.org/grpc v1.72.0/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM=
 google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
 google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
diff --git a/internal/acl/acl_manager.go b/internal/acl/acl_manager.go
index d0883755..a53afc7e 100644
--- a/internal/acl/acl_manager.go
+++ b/internal/acl/acl_manager.go
@@ -3,10 +3,10 @@
 
 import (
 	"context"
+	"maps"
 	"strings"
 
 	"github.com/pkg/errors"
-	"golang.org/x/exp/maps"
 
 	"github.com/kopia/kopia/repo"
 	"github.com/kopia/kopia/repo/manifest"
diff --git a/internal/bigmap/bigmap_internal.go b/internal/bigmap/bigmap_internal.go
index f80e2c43..8f924c18 100644
--- a/internal/bigmap/bigmap_internal.go
+++ b/internal/bigmap/bigmap_internal.go
@@ -344,7 +344,7 @@ func (m *internalMap) newMemoryMappedSegment(ctx context.Context) (mmap.MMap, er
 
 // +checklocks:m.mu
 func (m *internalMap) maybeCreateMappedFile(ctx context.Context) (*os.File, error) {
-	f, err := tempfile.Create("")
+	f, err := tempfile.CreateAutoDelete()
 	if err != nil {
 		return nil, errors.Wrap(err, "unable to create memory-mapped file")
 	}
diff --git a/internal/diff/diff.go b/internal/diff/diff.go
index 84cd6bb4..39fe0be6 100644
--- a/internal/diff/diff.go
+++ b/internal/diff/diff.go
@@ -21,7 +21,7 @@
 
 var log = logging.Module("diff")
 
-// EntryTypeStats accumulates specific stats for the snapshots being compared.
+// EntryTypeStats accumulates stats for an FS entry type.
 type EntryTypeStats struct {
 	Added    uint32 `json:"added"`
 	Removed  uint32 `json:"removed"`
@@ -111,9 +111,9 @@ func (c *Comparer) compareEntry(ctx context.Context, e1, e2 fs.Entry, path strin
 	if e1HasObjectID && e2HasObjectID {
 		if h1.ObjectID() == h2.ObjectID() {
 			if _, isDir := e1.(fs.Directory); isDir {
-				c.compareDirMetadataAndComputeStats(ctx, e1, e2, path)
+				compareMetadata(ctx, e1, e2, path, &c.stats.DirectoryEntries)
 			} else {
-				c.compareFileMetadataAndComputeStats(ctx, e1, e2, path)
+				compareMetadata(ctx, e1, e2, path, &c.stats.FileEntries)
 			}
 
 			return nil
@@ -181,7 +181,6 @@ func (c *Comparer) compareEntry(ctx context.Context, e1, e2 fs.Entry, path strin
 
 	if isDir2 {
 		// left is non-directory, right is a directory
-		log(ctx).Infof("changed %v from non-directory to a directory", path)
 		c.output("changed %v from non-directory to a directory\n", path)
 
 		return nil
@@ -199,116 +198,86 @@ func (c *Comparer) compareEntry(ctx context.Context, e1, e2 fs.Entry, path strin
 		}
 	}
 
+	// don't compare filesystem boundaries (e1.Device()), it's pretty useless and is not stored in backups
+
 	return nil
 }
 
-func (c *Comparer) compareDirMetadataAndComputeStats(ctx context.Context, e1, e2 fs.Entry, path string) {
-	// check for metadata changes pertaining to directories given that content hasn't changed and gather aggregate statistics
-	equal := true
+// Checks for changes in e1's and e2's metadata when they have the same content,
+// and upates the stats accordingly.
+// The function is not concurrency safe, as it updates st without any locking.
+func compareMetadata(ctx context.Context, e1, e2 fs.Entry, path string, st *EntryTypeStats) {
+	var changed bool
 
 	if m1, m2 := e1.Mode(), e2.Mode(); m1 != m2 {
-		equal = false
-		c.stats.DirectoryEntries.SameContentButDifferentMode++
+		changed = true
+		st.SameContentButDifferentMode++
 	}
 
 	if mt1, mt2 := e1.ModTime(), e2.ModTime(); !mt1.Equal(mt2) {
-		equal = false
-		c.stats.DirectoryEntries.SameContentButDifferentModificationTime++
+		changed = true
+		st.SameContentButDifferentModificationTime++
 	}
 
 	o1, o2 := e1.Owner(), e2.Owner()
 	if o1.UserID != o2.UserID {
-		equal = false
-		c.stats.DirectoryEntries.SameContentButDifferentUserOwner++
+		changed = true
+		st.SameContentButDifferentUserOwner++
 	}
 
 	if o1.GroupID != o2.GroupID {
-		equal = false
-		c.stats.DirectoryEntries.SameContentButDifferentGroupOwner++
+		changed = true
+		st.SameContentButDifferentGroupOwner++
 	}
 
-	if !equal {
-		c.stats.DirectoryEntries.SameContentButDifferentMetadata++
+	if changed {
+		st.SameContentButDifferentMetadata++
 
 		log(ctx).Debugf("content unchanged but metadata has been modified: %v", path)
 	}
 }
 
-func (c *Comparer) compareFileMetadataAndComputeStats(ctx context.Context, e1, e2 fs.Entry, path string) {
-	// check for metadata changes pertaining to files given that content hasn't changed and gather aggregate statistics
-	equal := true
-	if m1, m2 := e1.Mode(), e2.Mode(); m1 != m2 {
-		equal = false
-		c.stats.FileEntries.SameContentButDifferentMode++
-	}
-
-	if mt1, mt2 := e1.ModTime(), e2.ModTime(); !mt1.Equal(mt2) {
-		equal = false
-		c.stats.FileEntries.SameContentButDifferentModificationTime++
-	}
-
-	o1, o2 := e1.Owner(), e2.Owner()
-	if o1.UserID != o2.UserID {
-		equal = false
-		c.stats.FileEntries.SameContentButDifferentUserOwner++
-	}
-
-	if o1.GroupID != o2.GroupID {
-		equal = false
-		c.stats.FileEntries.SameContentButDifferentGroupOwner++
-	}
-
-	if !equal {
-		c.stats.FileEntries.SameContentButDifferentMetadata++
-
-		log(ctx).Debugf("content unchanged but metadata has been modified: %v", path)
-	}
-}
-
-func (c *Comparer) compareEntryMetadata(e1, e2 fs.Entry, fullpath string) bool {
-	if e1 == e2 { // in particular e1 == nil && e2 == nil
-		return true
-	}
-
-	if e1 == nil {
+func (c *Comparer) compareEntryMetadata(e1, e2 fs.Entry, fullpath string) {
+	switch {
+	case e1 == e2: // in particular e1 == nil && e2 == nil
+		return
+	case e1 == nil:
 		c.output("%v does not exist in source directory\n", fullpath)
-		return false
-	}
-
-	if e2 == nil {
+		return
+	case e2 == nil:
 		c.output("%v does not exist in destination directory\n", fullpath)
-		return false
+		return
 	}
 
-	equal := true
+	var changed bool
 
 	if m1, m2 := e1.Mode(), e2.Mode(); m1 != m2 {
-		equal = false
+		changed = true
 
 		c.output("%v modes differ: %v %v\n", fullpath, m1, m2)
 	}
 
 	if s1, s2 := e1.Size(), e2.Size(); s1 != s2 {
-		equal = false
+		changed = true
 
 		c.output("%v sizes differ: %v %v\n", fullpath, s1, s2)
 	}
 
 	if mt1, mt2 := e1.ModTime(), e2.ModTime(); !mt1.Equal(mt2) {
-		equal = false
+		changed = true
 
 		c.output("%v modification times differ: %v %v\n", fullpath, mt1, mt2)
 	}
 
 	o1, o2 := e1.Owner(), e2.Owner()
 	if o1.UserID != o2.UserID {
-		equal = false
+		changed = true
 
 		c.output("%v owner users differ: %v %v\n", fullpath, o1.UserID, o2.UserID)
 	}
 
 	if o1.GroupID != o2.GroupID {
-		equal = false
+		changed = true
 
 		c.output("%v owner groups differ: %v %v\n", fullpath, o1.GroupID, o2.GroupID)
 	}
@@ -316,17 +285,13 @@ func (c *Comparer) compareEntryMetadata(e1, e2 fs.Entry, fullpath string) bool {
 	_, isDir1 := e1.(fs.Directory)
 	_, isDir2 := e2.(fs.Directory)
 
-	if !equal {
+	if changed {
 		if isDir1 && isDir2 {
 			c.stats.DirectoryEntries.Modified++
 		} else {
 			c.stats.FileEntries.Modified++
 		}
 	}
-
-	// don't compare filesystem boundaries (e1.Device()), it's pretty useless and is not stored in backups
-
-	return equal
 }
 
 func (c *Comparer) compareDirectoryEntries(ctx context.Context, entries1, entries2 []fs.Entry, dirPath string) error {
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/logfile/logfile.go b/internal/logfile/logfile.go
index 2589b8f7..2a3138a9 100644
--- a/internal/logfile/logfile.go
+++ b/internal/logfile/logfile.go
@@ -19,6 +19,7 @@
 	"go.uber.org/zap/zapcore"
 
 	"github.com/kopia/kopia/cli"
+	"github.com/kopia/kopia/internal/cachedir"
 	"github.com/kopia/kopia/internal/clock"
 	"github.com/kopia/kopia/internal/ospath"
 	"github.com/kopia/kopia/internal/zaplogutil"
@@ -213,6 +214,10 @@ func (c *loggingFlags) setupLogFileBasedLogger(now time.Time, subdir, suffix, lo
 		fmt.Fprintln(os.Stderr, "Unable to create logs directory:", err)
 	}
 
+	if err := cachedir.WriteCacheMarker(logDir); err != nil {
+		fmt.Fprintf(os.Stderr, "Unable to create cache marker in log directory %q: %s", logDir, err)
+	}
+
 	sweepLogWG := &sync.WaitGroup{}
 	doSweep := func() {}
 
diff --git a/internal/metrics/metrics_distribution.go b/internal/metrics/metrics_distribution.go
index 5de5602c..391a37ed 100644
--- a/internal/metrics/metrics_distribution.go
+++ b/internal/metrics/metrics_distribution.go
@@ -9,8 +9,12 @@
 	"golang.org/x/exp/constraints"
 )
 
+type realNumber interface {
+	constraints.Float | constraints.Integer
+}
+
 // DistributionState captures momentary state of a Distribution.
-type DistributionState[T constraints.Float | constraints.Integer] struct {
+type DistributionState[T realNumber] struct {
 	Min              T       `json:"min"`
 	Max              T       `json:"max"`
 	Sum              T       `json:"sum"`
@@ -65,7 +69,7 @@ func (s *DistributionState[T]) Mean() T {
 }
 
 // Distribution measures distribution/summary of values.
-type Distribution[T constraints.Integer | constraints.Float] struct {
+type Distribution[T realNumber] struct {
 	mu               sync.Mutex
 	state            atomic.Pointer[DistributionState[T]] // +checklocksignore
 	bucketThresholds []T                                  // +checklocksignore
diff --git a/internal/metrics/metrics_thresholds.go b/internal/metrics/metrics_thresholds.go
index f56d3a5b..f16afc1f 100644
--- a/internal/metrics/metrics_thresholds.go
+++ b/internal/metrics/metrics_thresholds.go
@@ -2,12 +2,10 @@
 
 import (
 	"time"
-
-	"golang.org/x/exp/constraints"
 )
 
 // Thresholds encapsulates a set of bucket thresholds used in Summary[T].
-type Thresholds[T constraints.Float | constraints.Integer] struct {
+type Thresholds[T realNumber] struct {
 	values           []T
 	promScale        float64
 	prometheusSuffix string
@@ -108,7 +106,7 @@ type Thresholds[T constraints.Float | constraints.Integer] struct {
 	"_ns",
 }
 
-func bucketForThresholds[T constraints.Integer | constraints.Float](thresholds []T, d T) int {
+func bucketForThresholds[T realNumber](thresholds []T, d T) int {
 	l, r := 0, len(thresholds)-1
 
 	for l <= r {
diff --git a/internal/metrics/prom_cache.go b/internal/metrics/prom_cache.go
index a3493cb7..5d979ad9 100644
--- a/internal/metrics/prom_cache.go
+++ b/internal/metrics/prom_cache.go
@@ -1,11 +1,12 @@
 package metrics
 
 import (
+	"maps"
+	"slices"
 	"sync"
 
 	"github.com/prometheus/client_golang/prometheus"
 	"github.com/prometheus/client_golang/prometheus/promauto"
-	"golang.org/x/exp/maps"
 )
 
 const (
@@ -28,12 +29,12 @@ func getPrometheusCounter(opts prometheus.CounterOpts, labels map[string]string)
 
 	prom := promCounters[opts.Name]
 	if prom == nil {
-		prom = promauto.NewCounterVec(opts, maps.Keys(labels))
+		prom = promauto.NewCounterVec(opts, mapKeys(labels))
 
 		promCounters[opts.Name] = prom
 	}
 
-	return prom.WithLabelValues(maps.Values(labels)...)
+	return prom.WithLabelValues(mapValues(labels)...)
 }
 
 func getPrometheusHistogram(opts prometheus.HistogramOpts, labels map[string]string) prometheus.Observer { //nolint:gocritic
@@ -42,10 +43,18 @@ func getPrometheusHistogram(opts prometheus.HistogramOpts, labels map[string]str
 
 	prom := promHistograms[opts.Name]
 	if prom == nil {
-		prom = promauto.NewHistogramVec(opts, maps.Keys(labels))
+		prom = promauto.NewHistogramVec(opts, mapKeys(labels))
 
 		promHistograms[opts.Name] = prom
 	}
 
-	return prom.WithLabelValues(maps.Values(labels)...)
+	return prom.WithLabelValues(mapValues(labels)...)
+}
+
+func mapKeys[Map ~map[K]V, K comparable, V any](m Map) []K {
+	return slices.AppendSeq(make([]K, 0, len(m)), maps.Keys(m))
+}
+
+func mapValues[Map ~map[K]V, K comparable, V any](m Map) []V {
+	return slices.AppendSeq(make([]V, 0, len(m)), maps.Values(m))
 }
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.go b/internal/tempfile/tempfile.go
index 85d687cf..1bd82346 100644
--- a/internal/tempfile/tempfile.go
+++ b/internal/tempfile/tempfile.go
@@ -1,13 +1,3 @@
 // Package tempfile provides a cross-platform abstraction for creating private
 // read-write temporary files which are automatically deleted when closed.
 package tempfile
-
-import "os"
-
-func tempDirOr(dir string) string {
-	if dir != "" {
-		return dir
-	}
-
-	return os.TempDir()
-}
diff --git a/internal/tempfile/tempfile_linux.go b/internal/tempfile/tempfile_linux.go
index 5481b127..d0598039 100644
--- a/internal/tempfile/tempfile_linux.go
+++ b/internal/tempfile/tempfile_linux.go
@@ -8,8 +8,12 @@
 	"golang.org/x/sys/unix"
 )
 
-// Create creates a temporary file that will be automatically deleted on close.
-func Create(dir string) (*os.File, error) {
+const permissions = 0o600
+
+// CreateAutoDelete creates a temporary file that will be automatically deleted on close.
+func CreateAutoDelete() (*os.File, error) {
+	dir := os.TempDir()
+
 	// 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)
@@ -18,7 +22,7 @@ func Create(dir string) (*os.File, error) {
 	}
 
 	if errors.Is(err, syscall.EISDIR) || errors.Is(err, syscall.EOPNOTSUPP) {
-		return createUnixFallback(dir)
+		return createUnixFallback()
 	}
 
 	return nil, &os.PathError{
diff --git a/internal/tempfile/tempfile_unix_fallback.go b/internal/tempfile/tempfile_unix_fallback.go
index 565e3a6c..f1ac3cb0 100644
--- a/internal/tempfile/tempfile_unix_fallback.go
+++ b/internal/tempfile/tempfile_unix_fallback.go
@@ -5,25 +5,19 @@
 
 import (
 	"os"
-	"path/filepath"
 
-	"github.com/google/uuid"
 	"github.com/pkg/errors"
 )
 
-const permissions = 0o600
-
 // createUnixFallback creates a temporary file that does not need to be removed on close.
-func createUnixFallback(dir string) (*os.File, error) {
-	fullPath := filepath.Join(tempDirOr(dir), uuid.NewString())
-
-	f, err := os.OpenFile(fullPath, os.O_CREATE|os.O_EXCL|os.O_RDWR, permissions) //nolint:gosec
+func createUnixFallback() (*os.File, error) {
+	f, err := os.CreateTemp("", "kt-")
 	if err != nil {
 		return nil, err //nolint:wrapcheck
 	}
 
 	// immediately remove/unlink the file while we keep the handle open.
-	if derr := os.Remove(fullPath); derr != nil {
+	if derr := os.Remove(f.Name()); derr != nil {
 		f.Close() //nolint:errcheck
 		return nil, errors.Wrap(derr, "unable to unlink temporary file")
 	}
diff --git a/internal/tempfile/tempfile_unix_nonlinux.go b/internal/tempfile/tempfile_unix_nonlinux.go
index b58fff52..b0f28acc 100644
--- a/internal/tempfile/tempfile_unix_nonlinux.go
+++ b/internal/tempfile/tempfile_unix_nonlinux.go
@@ -7,7 +7,7 @@
 	"os"
 )
 
-// Create creates a temporary file that does not need to be removed on close.
-func Create(dir string) (*os.File, error) {
-	return createUnixFallback(dir)
+// CreateAutoDelete creates a temporary file that does not need to be explicitly removed on close.
+func CreateAutoDelete() (*os.File, error) {
+	return createUnixFallback()
 }
diff --git a/internal/tempfile/tempfile_windows.go b/internal/tempfile/tempfile_windows.go
index 8a6b00b5..6d5cc3db 100644
--- a/internal/tempfile/tempfile_windows.go
+++ b/internal/tempfile/tempfile_windows.go
@@ -9,9 +9,9 @@
 	"golang.org/x/sys/windows"
 )
 
-// Create creates a temporary file that will be automatically deleted on close.
-func Create(dir string) (*os.File, error) {
-	fullpath := filepath.Join(tempDirOr(dir), uuid.NewString())
+// CreateAutoDelete creates a temporary file that will be automatically deleted on close.
+func CreateAutoDelete() (*os.File, error) {
+	fullpath := filepath.Join(os.TempDir(), uuid.NewString())
 
 	fname, err := syscall.UTF16PtrFromString(fullpath)
 	if err != nil {
diff --git a/internal/user/user_manager.go b/internal/user/user_manager.go
index a51b44d4..29d66eeb 100644
--- a/internal/user/user_manager.go
+++ b/internal/user/user_manager.go
@@ -3,8 +3,10 @@
 
 import (
 	"context"
+	"maps"
 	"regexp"
-	"sort"
+	"slices"
+	"strings"
 
 	"github.com/pkg/errors"
 
@@ -61,22 +63,16 @@ func LoadProfileMap(ctx context.Context, rep repo.Repository, old map[string]*Pr
 
 // ListUserProfiles gets the list of all user profiles in the system.
 func ListUserProfiles(ctx context.Context, rep repo.Repository) ([]*Profile, error) {
-	var result []*Profile
-
 	users, err := LoadProfileMap(ctx, rep, nil)
 	if err != nil {
 		return nil, err
 	}
 
-	for _, v := range users {
-		result = append(result, v)
-	}
-
-	sort.Slice(result, func(i, j int) bool {
-		return result[i].Username < result[j].Username
+	profs := slices.SortedFunc(maps.Values(users), func(p1, p2 *Profile) int {
+		return strings.Compare(p1.Username, p2.Username)
 	})
 
-	return result, nil
+	return profs, nil
 }
 
 // GetUserProfile returns the user profile with a given username.
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/notification/notifytemplate/repotemplate.go b/notification/notifytemplate/repotemplate.go
index 311f9949..50fc10f2 100644
--- a/notification/notifytemplate/repotemplate.go
+++ b/notification/notifytemplate/repotemplate.go
@@ -2,11 +2,12 @@
 
 import (
 	"context"
+	"maps"
+	"slices"
 	"strings"
 	"time"
 
 	"github.com/pkg/errors"
-	"golang.org/x/exp/maps"
 
 	"github.com/kopia/kopia/repo"
 	"github.com/kopia/kopia/repo/manifest"
@@ -106,7 +107,7 @@ func ListTemplates(ctx context.Context, rep repo.Repository, prefix string) ([]I
 		}
 	}
 
-	return maps.Values(infos), nil
+	return slices.AppendSeq(make([]Info, 0, len(infos)), maps.Values(infos)), nil
 }
 
 // SetTemplate saves a template in the repository.
diff --git a/notification/sender/notification_message.go b/notification/sender/notification_message.go
index 8d219ca4..02dacfa5 100644
--- a/notification/sender/notification_message.go
+++ b/notification/sender/notification_message.go
@@ -6,11 +6,12 @@
 	"context"
 	"fmt"
 	"io"
+	"maps"
+	"slices"
 	"sort"
 	"strings"
 
 	"github.com/pkg/errors"
-	"golang.org/x/exp/maps"
 )
 
 // Severity represents the severity of a notification message.
@@ -80,7 +81,7 @@ func (m Message) ToString() string {
 
 	fmt.Fprintf(&buf, "Subject: %v\n", m.Subject)
 
-	headers := maps.Keys(m.Headers)
+	headers := slices.AppendSeq(make([]string, 0, len(m.Headers)), maps.Keys(m.Headers))
 
 	sort.Strings(headers)
 
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/buildinfo.go b/repo/buildinfo.go
new file mode 100644
index 00000000..c6f7a054
--- /dev/null
+++ b/repo/buildinfo.go
@@ -0,0 +1,84 @@
+package repo
+
+import (
+	stdlib "log"
+	"runtime/debug"
+	"strings"
+)
+
+// Kopia's build information.
+//
+//nolint:gochecknoglobals
+var (
+	BuildInfo       = ""
+	BuildVersion    = ""
+	BuildGitHubRepo = ""
+)
+
+func init() {
+	// fill in from executable's build info when these are unset
+	BuildInfo, BuildVersion = getBuildInfoAndVersion(BuildInfo, BuildVersion)
+}
+
+func getBuildInfoAndVersion(linkedInfo, linkedVersion string) (info, version string) {
+	info, version = linkedInfo, linkedVersion
+
+	if info != "" && version != "" {
+		return // use the values specified at link time
+	}
+
+	// a value was not set at link time, set it from the executable's build
+	// info if available.
+	bi, ok := debug.ReadBuildInfo()
+	if !ok {
+		// logging not yet set up, use stdlib's logging
+		stdlib.Println("executable build information is not available")
+		return // executable's build info is not available, use values set at link time, if any
+	}
+
+	if version == "" {
+		version = "v0-unofficial"
+
+		if bi.Main.Version != "" && bi.Main.Version != "(devel)" { // set to '(devel)' during tests in Go 1.24
+			version = bi.Main.Version
+		}
+	}
+
+	if info == "" {
+		info = getRevisionString(bi.Settings)
+	}
+
+	return
+}
+
+func getRevisionString(s []debug.BuildSetting) string {
+	var (
+		revision, vcsTime string
+		modified          bool
+	)
+
+	for _, v := range s {
+		switch v.Key {
+		case "vcs.revision":
+			revision = v.Value
+		case "vcs.time":
+			vcsTime = v.Value
+		case "vcs.modified":
+			if strings.EqualFold(v.Value, "true") {
+				modified = true
+			}
+		}
+	}
+
+	if revision == "" {
+		revision = "(unknown_revision)"
+	}
+
+	var modStr string
+
+	if modified {
+		modStr = "+dirty"
+	}
+
+	return vcsTime + "-" + revision + modStr
+}
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/id.go b/repo/content/index/id.go
index d058af83..52cb4bf0 100644
--- a/repo/content/index/id.go
+++ b/repo/content/index/id.go
@@ -37,7 +37,7 @@ type ID struct {
 
 	// those 2 could be packed into one byte, but that seems like overkill
 	prefix byte
-	idLen  byte
+	idLen  uint8
 }
 
 // MarshalJSON implements JSON serialization.
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..3ff82639 100644
--- a/repo/grpc_repository_client.go
+++ b/repo/grpc_repository_client.go
@@ -732,7 +732,7 @@ func (r *grpcRepositoryClient) doWriteAsync(ctx context.Context, contentID conte
 	r.opt.OnUpload(int64(len(data)))
 
 	if _, err := inSessionWithoutRetry(ctx, r, func(ctx context.Context, sess *grpcInnerSession) (content.ID, error) {
-		sess.WriteContentAsyncAndVerify(ctx, contentID, data, prefix, comp, r.asyncWritesWG)
+		sess.writeContentAsyncAndVerify(ctx, contentID, data, prefix, comp, r.asyncWritesWG)
 		return contentID, nil
 	}); err != nil {
 		return err
@@ -777,7 +777,7 @@ func (r *grpcRepositoryClient) WriteContent(ctx context.Context, data gather.Byt
 	return contentID, nil
 }
 
-func (r *grpcInnerSession) WriteContentAsyncAndVerify(ctx context.Context, contentID content.ID, data []byte, prefix content.IDPrefix, comp compression.HeaderID, eg *errgroup.Group) {
+func (r *grpcInnerSession) writeContentAsyncAndVerify(ctx context.Context, contentID content.ID, data []byte, prefix content.IDPrefix, comp compression.HeaderID, eg *errgroup.Group) {
 	ch := r.sendRequest(ctx, &apipb.SessionRequest{
 		Request: &apipb.SessionRequest_WriteContent{
 			WriteContent: &apipb.WriteContentRequest{
@@ -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 7b9e7a77..fd10b1e0 100644
--- a/repo/initialize.go
+++ b/repo/initialize.go
@@ -18,15 +18,6 @@
 	"github.com/kopia/kopia/repo/splitter"
 )
 
-// BuildInfo is the build information of Kopia.
-//
-//nolint:gochecknoglobals
-var (
-	BuildInfo       = "unknown"
-	BuildVersion    = "v0-unofficial"
-	BuildGitHubRepo = ""
-)
-
 const (
 	hmacSecretLength = 32
 	masterKeyLength  = 32
@@ -120,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/open.go b/repo/open.go
index 2745b252..137cd984 100644
--- a/repo/open.go
+++ b/repo/open.go
@@ -7,6 +7,7 @@
 	"path/filepath"
 	"strings"
 	"sync"
+	"sync/atomic"
 	"time"
 
 	"github.com/pkg/errors"
@@ -365,7 +366,7 @@ func openWithConfig(ctx context.Context, st blob.Storage, cliOpts ClientOptions,
 			timeNow:          cmOpts.TimeNow,
 			cliOpts:          cliOpts,
 			configFile:       configFile,
-			nextWriterID:     new(int32),
+			nextWriterID:     &atomic.Int32{},
 			throttler:        throttler,
 			metricsRegistry:  mr,
 			refCountedCloser: closer,
diff --git a/repo/repository.go b/repo/repository.go
index 301c4c90..fc751950 100644
--- a/repo/repository.go
+++ b/repo/repository.go
@@ -95,12 +95,6 @@ type DirectRepositoryWriter interface {
 	DirectRepository
 	BlobStorage() blob.Storage
 	ContentManager() *content.WriteManager
-	// SetParameters(ctx context.Context, m format.MutableParameters, blobcfg format.BlobStorageConfiguration, requiredFeatures []feature.Required) error
-	// ChangePassword(ctx context.Context, newPassword string) error
-	// GetUpgradeLockIntent(ctx context.Context) (*format.UpgradeLockIntent, error)
-	// SetUpgradeLockIntent(ctx context.Context, l format.UpgradeLockIntent) (*format.UpgradeLockIntent, error)
-	// CommitUpgrade(ctx context.Context) error
-	// RollbackUpgrade(ctx context.Context) error
 }
 
 type immutableDirectRepositoryParameters struct {
@@ -109,7 +103,7 @@ type immutableDirectRepositoryParameters struct {
 	cliOpts         ClientOptions
 	timeNow         func() time.Time
 	fmgr            *format.Manager
-	nextWriterID    *int32
+	nextWriterID    *atomic.Int32
 	throttler       throttling.SettableThrottler
 	metricsRegistry *metrics.Registry
 	beforeFlush     []RepositoryWriterCallback
@@ -282,7 +276,7 @@ func (r *directRepository) NewWriter(ctx context.Context, opt WriteSessionOption
 
 // NewDirectWriter returns new DirectRepositoryWriter session for repository.
 func (r *directRepository) NewDirectWriter(ctx context.Context, opt WriteSessionOptions) (context.Context, DirectRepositoryWriter, error) {
-	writeManagerID := fmt.Sprintf("writer-%v:%v", atomic.AddInt32(r.nextWriterID, 1), opt.Purpose)
+	writeManagerID := fmt.Sprintf("writer-%v:%v", r.nextWriterID.Add(1), opt.Purpose)
 
 	cmgr := content.NewWriteManager(ctx, r.sm, content.SessionOptions{
 		SessionUser: r.cliOpts.Username,
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/site/content/docs/Getting started/_index.md b/site/content/docs/Getting started/_index.md
index 294425e3..3368bb5b 100755
--- a/site/content/docs/Getting started/_index.md
+++ b/site/content/docs/Getting started/_index.md
@@ -38,7 +38,15 @@ After the initial snapshot, for every snapshot afterwards Kopia will rescan the
 
 > PRO TIP: If you pick a value for `Snapshot Frequency` when creating a `policy`, then Kopia will automatically take snapshots at that frequency (e.g., every one hour or whatever value you pick), and you do not need to remember to manually run the snapshot. If you do not pick a `Snapshot Frequency`, then Kopia will not automatically take snapshots, and you need to manually run snapshots from the `Snapshots` tab (just click the `Snapshot Now` button as needed).
 
-Note that you can set policies at two levels in `KopiaUI` -- at the `global` level, where the settings are applied by default to all policies that do not define their own settings, or at the individual `policy` level, where the settings are applied only to that particular policy. By default, all new policies are set to inherit settings from the `global` policy. The `global` policy is the one that says `*` for `Username`, `Host`, and `Path`.
+Note that you can set policies at four levels in `KopiaUI`:
+* at the `global` level (shown with `*` for `Username`, `Host` and `Path`),
+* at the per-host `@host` level (shown with `*` for `Username` and `Path`),
+* at the per-user `user@host` level (shown with `*` for `Path`),
+* and at the individual `Path` level, where the settings are applied only to that particular policy.
+
+By default, all new policies are set to inherit settings from the `global` policy, as well as from the applicable per-host and per-user policies. More specific policies settings take priority. This means that individual per-path policies settings come first, followed by per-user, then per-host and finally global policy settings.
+
+For example, say you have a path policy for `foo@bar:/path` which doesn't define setting `xyz`, a per-user policy for `foo@bar` setting `xyz` to `1` and the global policy setting `xyz` to `2`. In this case, the effective value of `xyz` for the policy `foo@bar:/path` will be `1`.
 
 > PRO TIP: Kopia does not currently support the ability to save one snapshot to multiple different repositories. However, you can use `KopiaUI` to connect to multiple different repositories simultaneously and create identical policies for each repository, which essentially achieves the same outcome of saving one snapshot to multiple different repositories. Connecting to more than one repository in `KopiaUI` is easy: just right-click the icon of the desktop application and select `Connect To Another Repository...`. Currently, this is only available in the desktop version of `KopiaUI` and not the web-based `KopiaUI`. However, if you are using the web-based `KopiaUI`, you can manually run multiple instances of `KopiaUI` to achieve the same outcome.
 
@@ -286,6 +294,8 @@ Files policy:
 
 We can change policy settings using the [`kopia policy set` command](../reference/command-line/common/policy-set/). This command allows you to change the `global` policy or change specific policies for a 'user@host', a '@host', a 'user@host:path', or a particular directory. For example, here we tell Kopia to set the policy to ignore two directories from being included in the snapshot of `jarek@jareks-mbp:/Users/jarek/Projects/Kopia/site`:
 
+> NOTE: When referring to policies, `user@host` and `@host` are used to refer to per-user and per-host policies, respectively. They do NOT refer to all policies defined under that user or host.
+
 ```
 $ kopia policy set --add-ignore public/ --add-ignore node_modules/ .
 Setting policy for jarek@jareks-mbp:/Users/jarek/Projects/Kopia/site
diff --git a/snapshot/manager.go b/snapshot/manager.go
index d25c3f91..a35828d2 100644
--- a/snapshot/manager.go
+++ b/snapshot/manager.go
@@ -6,6 +6,7 @@
 
 	"github.com/pkg/errors"
 
+	"github.com/kopia/kopia/fs"
 	"github.com/kopia/kopia/repo"
 	"github.com/kopia/kopia/repo/logging"
 	"github.com/kopia/kopia/repo/manifest"
@@ -247,6 +248,50 @@ func UpdateSnapshot(ctx context.Context, rep repo.RepositoryWriter, m *Manifest)
 	return nil
 }
 
+// FindPreviousManifests returns the list of previous snapshots for a given source, including
+// last complete snapshot and possibly some number of incomplete snapshots following it.
+func FindPreviousManifests(ctx context.Context, rep repo.Repository, sourceInfo SourceInfo, noLaterThan *fs.UTCTimestamp) ([]*Manifest, error) {
+	man, err := ListSnapshots(ctx, rep, sourceInfo)
+	if err != nil {
+		return nil, errors.Wrap(err, "error listing previous snapshots")
+	}
+
+	// phase 1 - find latest complete snapshot.
+	var (
+		previousComplete          *Manifest
+		previousCompleteStartTime fs.UTCTimestamp
+		result                    []*Manifest
+	)
+
+	for _, p := range man {
+		if noLaterThan != nil && p.StartTime.After(*noLaterThan) {
+			continue
+		}
+
+		if p.IncompleteReason == "" && (previousComplete == nil || p.StartTime.After(previousComplete.StartTime)) {
+			previousComplete = p
+			previousCompleteStartTime = p.StartTime
+		}
+	}
+
+	if previousComplete != nil {
+		result = append(result, previousComplete)
+	}
+
+	// add all incomplete snapshots after that
+	for _, p := range man {
+		if noLaterThan != nil && p.StartTime.After(*noLaterThan) {
+			continue
+		}
+
+		if p.IncompleteReason != "" && p.StartTime.After(previousCompleteStartTime) {
+			result = append(result, p)
+		}
+	}
+
+	return result, nil
+}
+
 func entryIDs(entries []*manifest.EntryMetadata) []manifest.ID {
 	var ids []manifest.ID
 	for _, e := range entries {
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..eda32e7a 100644
--- a/tools/cli2md/cli2md.go
+++ b/tools/cli2md/cli2md.go
@@ -8,6 +8,7 @@
 	"log"
 	"os"
 	"path/filepath"
+	"regexp"
 	"sort"
 	"strings"
 
@@ -31,6 +32,14 @@
 	dirMode = 0o750
 )
 
+// The group at the start is to avoid matching things like "in-place".
+var escapeFlagsRe = regexp.MustCompile(`(^|\s)(--?\w+[-\w]*)`)
+
+// escapeFlags escapes command-line flag references in help text by wrapping them in backticks.
+func escapeFlags(text string) string {
+	return escapeFlagsRe.ReplaceAllString(text, "$1`$2`")
+}
+
 //nolint:gochecknoglobals
 var overrideDefault = map[string]string{
 	"config-file": "repository.config",
@@ -75,9 +84,9 @@ func emitFlags(w io.Writer, flags []*kingpin.FlagModel) {
 				defaultValue = "`false`"
 			}
 
-			fmt.Fprintf(w, "| `--[no-]%v` | %v | %v | %v%v |\n", f.Name, shortFlag, defaultValue, maybeAdvanced, f.Help) //nolint:errcheck
+			fmt.Fprintf(w, "| `--[no-]%v` | %v | %v | %v%v |\n", f.Name, shortFlag, defaultValue, maybeAdvanced, escapeFlags(f.Help)) //nolint:errcheck
 		} else {
-			fmt.Fprintf(w, "| `--%v` | %v | %v | %v%v |\n", f.Name, shortFlag, defaultValue, maybeAdvanced, f.Help) //nolint:errcheck
+			fmt.Fprintf(w, "| `--%v` | %v | %v | %v%v |\n", f.Name, shortFlag, defaultValue, maybeAdvanced, escapeFlags(f.Help)) //nolint:errcheck
 		}
 	}
 
@@ -122,7 +131,7 @@ func emitArgs(w io.Writer, args []*kingpin.ArgModel) {
 	})
 
 	for _, f := range args2 {
-		fmt.Fprintf(w, "| `%v` | %v |\n", f.Name, f.Help) //nolint:errcheck
+		fmt.Fprintf(w, "| `%v` | %v |\n", f.Name, escapeFlags(f.Help)) //nolint:errcheck
 	}
 
 	fmt.Fprintf(w, "\n") //nolint:errcheck
@@ -251,7 +260,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
@@ -297,7 +306,7 @@ func generateSubcommandPage(fname string, cmd *kingpin.CmdModel) {
 	}
 
 	fmt.Fprintf(f, "```shell\n$ kopia %v%v%v\n```\n\n", cmd.FullCommand, flagSummary, argSummary) //nolint:errcheck
-	fmt.Fprintf(f, "%v\n\n", cmd.Help)                                                            //nolint:errcheck
+	fmt.Fprintf(f, "%v\n\n", escapeFlags(cmd.Help))                                               //nolint:errcheck
 
 	emitFlags(f, cmd.Flags)
 	emitArgs(f, cmd.Args)
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..6f62ef9b 100644
--- a/tools/tools.mk
+++ b/tools/tools.mk
@@ -71,7 +71,7 @@ export REPO_OWNER=$(GITHUB_REPOSITORY:%/kopia=%)
 endif
 
 # e.g. 2021-02-19 06:56:21 -0800
-git_commit_date:=$(shell git show -s --format=%ci HEAD)
+git_commit_date:=$(shell TZ=UTC git show -s --date=iso-local --format=%cd HEAD)
 
 # compute build date and time from the current commit as yyyyMMdd
 commit_date_ymd:=$(subst -,,$(word 1, $(git_commit_date)))
@@ -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/.github/workflows/compat-test.yml b/.github/workflows/compat-test.yml
index 99a77864..55805051 100644
--- a/.github/workflows/compat-test.yml
+++ b/.github/workflows/compat-test.yml
@@ -18,7 +18,7 @@ jobs:
       with:
         fetch-depth: 0
     - name: Set up Go
-      uses: actions/setup-go@f111f3307d8850f501ac008e886eec1fd1932a34 # v5.3.0
+      uses: actions/setup-go@0aaccfd150d50ccaeb58ebd88d36e91967a5f35b # v5.4.0
       with:
         go-version-file: 'go.mod'
         check-latest: true
@@ -26,7 +26,7 @@ jobs:
     - name: Compat Test
       run: make compat-tests
     - name: Upload Logs
-      uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4.6.1
+      uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
       with:
         name: logs
         path: .logs/**/*.log
diff --git a/.github/workflows/endurance-test.yml b/.github/workflows/endurance-test.yml
index bc40731a..c8dc8137 100644
--- a/.github/workflows/endurance-test.yml
+++ b/.github/workflows/endurance-test.yml
@@ -23,7 +23,7 @@ jobs:
       with:
         fetch-depth: 0
     - name: Set up Go
-      uses: actions/setup-go@f111f3307d8850f501ac008e886eec1fd1932a34 # v5.3.0
+      uses: actions/setup-go@0aaccfd150d50ccaeb58ebd88d36e91967a5f35b # v5.4.0
       with:
         go-version-file: 'go.mod'
         check-latest: true
@@ -31,7 +31,7 @@ jobs:
     - name: Endurance Tests
       run: make endurance-tests
     - name: Upload Logs
-      uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4.6.1
+      uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
       with:
         name: logs
         path: .logs/**/*.log
diff --git a/.github/workflows/htmlui-tests.yml b/.github/workflows/htmlui-tests.yml
index 8ca110a2..224b45e3 100644
--- a/.github/workflows/htmlui-tests.yml
+++ b/.github/workflows/htmlui-tests.yml
@@ -31,7 +31,7 @@ jobs:
       with:
         fetch-depth: 0
     - name: Set up Go
-      uses: actions/setup-go@f111f3307d8850f501ac008e886eec1fd1932a34 # v5.3.0
+      uses: actions/setup-go@0aaccfd150d50ccaeb58ebd88d36e91967a5f35b # v5.4.0
       with:
         go-version-file: 'go.mod'
         check-latest: true
@@ -39,7 +39,7 @@ jobs:
     - name: Run Tests
       run: make htmlui-e2e-test
     - name: Upload Screenshots
-      uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4.6.1
+      uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
       with:
         path: .screenshots/**/*.png
         if-no-files-found: ignore
diff --git a/.github/workflows/stress-test.yml b/.github/workflows/stress-test.yml
index ad9e2b45..fb4b8711 100644
--- a/.github/workflows/stress-test.yml
+++ b/.github/workflows/stress-test.yml
@@ -22,7 +22,7 @@ jobs:
       with:
         fetch-depth: 0
     - name: Set up Go
-      uses: actions/setup-go@f111f3307d8850f501ac008e886eec1fd1932a34 # v5.3.0
+      uses: actions/setup-go@0aaccfd150d50ccaeb58ebd88d36e91967a5f35b # v5.4.0
       with:
         go-version-file: 'go.mod'
         check-latest: true
@@ -30,7 +30,7 @@ jobs:
     - name: Stress Test
       run: make stress-test
     - name: Upload Logs
-      uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4.6.1
+      uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
       with:
         name: logs
         path: .logs/**/*.log
diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
index 3ec39202..272cde4f 100644
--- a/.github/workflows/tests.yml
+++ b/.github/workflows/tests.yml
@@ -38,7 +38,7 @@ jobs:
       with:
         fetch-depth: 0
     - name: Set up Go
-      uses: actions/setup-go@f111f3307d8850f501ac008e886eec1fd1932a34 # v5.3.0
+      uses: actions/setup-go@0aaccfd150d50ccaeb58ebd88d36e91967a5f35b # v5.4.0
       with:
         go-version-file: 'go.mod'
         check-latest: true
@@ -58,7 +58,7 @@ jobs:
     - name: Integration Tests
       run: make -j2 ci-integration-tests
     - name: Upload Logs
-      uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4.6.1
+      uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
       with:
         name: logs-${{ matrix.os }}
         path: .logs/**/*.log
diff --git a/.github/workflows/volume-shadow-copy-test.yml b/.github/workflows/volume-shadow-copy-test.yml
index 44805cd0..0c7a8936 100644
--- a/.github/workflows/volume-shadow-copy-test.yml
+++ b/.github/workflows/volume-shadow-copy-test.yml
@@ -19,7 +19,7 @@ jobs:
       with:
         fetch-depth: 0
     - name: Set up Go
-      uses: actions/setup-go@f111f3307d8850f501ac008e886eec1fd1932a34 # v5.3.0
+      uses: actions/setup-go@0aaccfd150d50ccaeb58ebd88d36e91967a5f35b # v5.4.0
       with:
         go-version-file: 'go.mod'
         check-latest: true
@@ -34,7 +34,7 @@ jobs:
     - name: Non-Admin Test
       run: gsudo -i Medium make os-snapshot-tests
     - name: Upload Logs
-      uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4.6.1
+      uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
       with:
         name: logs
         path: .logs/**/*.log
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/fs/localfs/local_fs_test.go b/fs/localfs/local_fs_test.go
index 81b97f92..900f5e26 100644
--- a/fs/localfs/local_fs_test.go
+++ b/fs/localfs/local_fs_test.go
@@ -29,9 +29,9 @@ func TestSymlink(t *testing.T) {
 	absLink := filepath.Join(tmp, "abslink")
 	relLink := filepath.Join(tmp, "rellink")
 
-	assertNoError(t, os.WriteFile(fn, []byte{1, 2, 3}, 0o777))
-	assertNoError(t, os.Symlink(fn, absLink))
-	assertNoError(t, os.Symlink("./target", relLink))
+	require.NoError(t, os.WriteFile(fn, []byte{1, 2, 3}, 0o777))
+	require.NoError(t, os.Symlink(fn, absLink))
+	require.NoError(t, os.Symlink("./target", relLink))
 
 	verifyLink(t, absLink, fn)
 	verifyLink(t, relLink, fn)
@@ -68,41 +68,26 @@ func verifyLink(t *testing.T, path, expected string) {
 //nolint:gocyclo
 func TestFiles(t *testing.T) {
 	ctx := testlogging.Context(t)
-
-	var err error
-
 	tmp := testutil.TempDirectory(t)
 
-	var dir fs.Directory
-
 	// Try listing directory that does not exist.
-	_, err = Directory(fmt.Sprintf("/no-such-dir-%v", clock.Now().Nanosecond()))
-	if err == nil {
-		t.Errorf("expected error when dir directory that does not exist.")
-	}
+	_, err := Directory(fmt.Sprintf("/no-such-dir-%v", clock.Now().Nanosecond()))
+	require.Error(t, err, "expected error when dir directory that does not exist.")
 
 	// Now list an empty directory that does exist.
-	dir, err = Directory(tmp)
-	if err != nil {
-		t.Errorf("error when dir empty directory: %v", err)
-	}
+	dir, err := Directory(tmp)
+	require.NoError(t, err, "error when dir empty directory")
 
 	entries, err := fs.GetAllEntries(ctx, dir)
-	if err != nil {
-		t.Errorf("error gettind dir Entries: %v", err)
-	}
-
-	if len(entries) > 0 {
-		t.Errorf("expected empty directory, got %v", dir)
-	}
+	require.NoError(t, err, "error gettind dir Entries")
+	require.Empty(t, entries, "expected empty directory")
 
 	// Now list a directory with 3 files.
-	assertNoError(t, os.WriteFile(filepath.Join(tmp, "f3"), []byte{1, 2, 3}, 0o777))
-	assertNoError(t, os.WriteFile(filepath.Join(tmp, "f2"), []byte{1, 2, 3, 4}, 0o777))
-	assertNoError(t, os.WriteFile(filepath.Join(tmp, "f1"), []byte{1, 2, 3, 4, 5}, 0o777))
-
-	assertNoError(t, os.Mkdir(filepath.Join(tmp, "z"), 0o777))
-	assertNoError(t, os.Mkdir(filepath.Join(tmp, "y"), 0o777))
+	require.NoError(t, os.WriteFile(filepath.Join(tmp, "f3"), []byte{1, 2, 3}, 0o777))
+	require.NoError(t, os.WriteFile(filepath.Join(tmp, "f2"), []byte{1, 2, 3, 4}, 0o777))
+	require.NoError(t, os.WriteFile(filepath.Join(tmp, "f1"), []byte{1, 2, 3, 4, 5}, 0o777))
+	require.NoError(t, os.Mkdir(filepath.Join(tmp, "z"), 0o777))
+	require.NoError(t, os.Mkdir(filepath.Join(tmp, "y"), 0o777))
 
 	expected := map[string]fileEnt{
 		"f1": {
@@ -128,14 +113,10 @@ func TestFiles(t *testing.T) {
 	}
 
 	dir, err = Directory(tmp)
-	if err != nil {
-		t.Errorf("error when dir directory with files: %v", err)
-	}
+	require.NoError(t, err, "error when dir directory with files")
 
 	entries, err = fs.GetAllEntries(ctx, dir)
-	if err != nil {
-		t.Errorf("error gettind dir Entries: %v", err)
-	}
+	require.NoError(t, err, "error gettind dir Entries")
 
 	goodCount := 0
 
@@ -201,7 +182,7 @@ func testIterate(t *testing.T, nFiles int) {
 	tmp := testutil.TempDirectory(t)
 
 	for i := range nFiles {
-		assertNoError(t, os.WriteFile(filepath.Join(tmp, fmt.Sprintf("f%v", i)), []byte{1, 2, 3}, 0o777))
+		require.NoError(t, os.WriteFile(filepath.Join(tmp, fmt.Sprintf("f%v", i)), []byte{1, 2, 3}, 0o777))
 	}
 
 	dir, err := Directory(tmp)
@@ -249,41 +230,23 @@ func verifyChild(t *testing.T, dir fs.Directory) {
 	t.Helper()
 
 	ctx := testlogging.Context(t)
-
 	child, err := dir.Child(ctx, "f3")
-	if err != nil {
-		t.Errorf("child error: %v", err)
-	}
 
-	if _, err = dir.Child(ctx, "f4"); !errors.Is(err, fs.ErrEntryNotFound) {
-		t.Errorf("unexpected child error: %v", err)
-	}
-
-	if got, want := child.Name(), "f3"; got != want {
-		t.Errorf("unexpected child name: %v, want %v", got, want)
-	}
+	require.NoError(t, err, "child error")
+	require.Equal(t, "f3", child.Name(), "unexpected child name")
+	require.Equal(t, int64(3), child.Size(), "unexpected child size")
 
-	if got, want := child.Size(), int64(3); got != want {
-		t.Errorf("unexpected child size: %v, want %v", got, want)
-	}
+	_, err = dir.Child(ctx, "f4")
+	require.ErrorIs(t, err, fs.ErrEntryNotFound, "unexpected child error")
 
-	if _, err = fs.IterateEntriesAndFindChild(ctx, dir, "f4"); !errors.Is(err, fs.ErrEntryNotFound) {
-		t.Errorf("unexpected child error: %v", err)
-	}
+	_, err = fs.IterateEntriesAndFindChild(ctx, dir, "f4")
+	require.ErrorIs(t, err, fs.ErrEntryNotFound, "unexpected child error")
 
 	// read child again, this time using IterateEntriesAndFindChild
 	child2, err := fs.IterateEntriesAndFindChild(ctx, dir, "f3")
-	if err != nil {
-		t.Errorf("child2 error: %v", err)
-	}
-
-	if got, want := child2.Name(), "f3"; got != want {
-		t.Errorf("unexpected child2 name: %v, want %v", got, want)
-	}
-
-	if got, want := child2.Size(), int64(3); got != want {
-		t.Errorf("unexpected child2 size: %v, want %v", got, want)
-	}
+	require.NoError(t, err, "child2 error")
+	require.Equal(t, "f3", child2.Name(), "unexpected child name")
+	require.Equal(t, int64(3), child2.Size(), "unexpected child size")
 }
 
 func TestLocalFilesystemPath(t *testing.T) {
@@ -336,11 +299,3 @@ func TestDirPrefix(t *testing.T) {
 		require.Equal(t, want, dirPrefix(input), input)
 	}
 }
-
-func assertNoError(t *testing.T, err error) {
-	t.Helper()
-
-	if err != nil {
-		t.Errorf("err: %v", err)
-	}
-}
diff --git a/internal/cache/content_cache_test.go b/internal/cache/content_cache_test.go
index 9069c225..4367849e 100644
--- a/internal/cache/content_cache_test.go
+++ b/internal/cache/content_cache_test.go
@@ -3,6 +3,7 @@
 import (
 	"bytes"
 	"context"
+	"slices"
 	"sort"
 	"sync"
 	"testing"
@@ -11,7 +12,6 @@
 	"github.com/pkg/errors"
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/require"
-	"golang.org/x/exp/slices"
 
 	"github.com/kopia/kopia/internal/blobtesting"
 	"github.com/kopia/kopia/internal/cache"
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/logfile/logfile_test.go b/internal/logfile/logfile_test.go
index a82621a6..a2142da2 100644
--- a/internal/logfile/logfile_test.go
+++ b/internal/logfile/logfile_test.go
@@ -12,6 +12,7 @@
 
 	"github.com/stretchr/testify/require"
 
+	"github.com/kopia/kopia/internal/cachedir"
 	"github.com/kopia/kopia/internal/clock"
 	"github.com/kopia/kopia/internal/logfile"
 	"github.com/kopia/kopia/internal/testutil"
@@ -133,7 +134,7 @@ func TestLogFileRotation(t *testing.T) {
 				require.NoError(t, err)
 
 				t.Logf("%v %v", info.Name(), info.Size())
-				if info.Mode().IsRegular() {
+				if info.Mode().IsRegular() && info.Name() != cachedir.CacheDirMarkerFile {
 					gotEntryCount++
 				}
 
@@ -194,6 +195,20 @@ func TestLogFileMaxTotalSize(t *testing.T) {
 	}
 }
 
+func TestCacheFileExists(t *testing.T) {
+	t.Parallel()
+
+	runner := testenv.NewInProcRunner(t)
+	runner.CustomizeApp = logfile.Attach
+	env := testenv.NewCLITest(t, testenv.RepoFormatNotImportant, runner)
+	tmpLogDir := testutil.TempDirectory(t)
+
+	env.RunAndExpectSuccess(t, "repo", "create", "filesystem", "--path", env.RepoDir, "--log-dir", tmpLogDir)
+
+	require.FileExists(t, filepath.Join(tmpLogDir, "cli-logs", cachedir.CacheDirMarkerFile))
+	require.FileExists(t, filepath.Join(tmpLogDir, "content-logs", cachedir.CacheDirMarkerFile))
+}
+
 func verifyFileLogFormat(t *testing.T, fname string, re *regexp.Regexp) {
 	t.Helper()
 
@@ -224,6 +239,10 @@ func getTotalDirSize(t *testing.T, dir string) int {
 	var totalSize int
 
 	for _, ent := range entries {
+		if ent.Name() == cachedir.CacheDirMarkerFile {
+			continue
+		}
+
 		info, err := ent.Info()
 		require.NoError(t, err)
 
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/pproflogging/pproflogging_test.go b/internal/pproflogging/pproflogging_test.go
index 3aed2824..dc7e1cb7 100644
--- a/internal/pproflogging/pproflogging_test.go
+++ b/internal/pproflogging/pproflogging_test.go
@@ -5,12 +5,13 @@
 	"context"
 	"fmt"
 	"io"
+	"maps"
 	"os"
 	"regexp"
+	"slices"
 	"testing"
 
 	"github.com/stretchr/testify/require"
-	"golang.org/x/exp/maps"
 
 	"github.com/kopia/kopia/repo/logging"
 )
@@ -163,7 +164,7 @@ func TestDebug_parseProfileConfigs(t *testing.T) {
 			require.ErrorIs(t, tc.expectError, err)
 			require.Len(t, pbs, tc.n)
 			pb, ok := pbs[tc.key] // no negative testing for missing keys (see newProfileConfigs)
-			require.Equalf(t, !tc.expectMissing, ok, "key %q for set %q expect missing %t", tc.key, maps.Keys(pbs), tc.expectMissing)
+			require.Equalf(t, !tc.expectMissing, ok, "key %q for set %q expect missing %t", tc.key, mapKeys(pbs), tc.expectMissing)
 			if tc.expectMissing {
 				return
 			}
@@ -174,6 +175,10 @@ func TestDebug_parseProfileConfigs(t *testing.T) {
 	}
 }
 
+func mapKeys[Map ~map[K]V, K comparable, V any](m Map) []K {
+	return slices.AppendSeq(make([]K, 0, len(m)), maps.Keys(m))
+}
+
 func TestDebug_newProfileConfigs(t *testing.T) {
 	saveLockEnv(t)
 
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_linux_fallback_test.go b/internal/tempfile/tempfile_linux_fallback_test.go
new file mode 100644
index 00000000..dbc88e48
--- /dev/null
+++ b/internal/tempfile/tempfile_linux_fallback_test.go
@@ -0,0 +1,16 @@
+//go:build linux
+// +build linux
+
+package tempfile
+
+import (
+	"testing"
+)
+
+// Explicitly test the create fallback function.
+// This test only applies to Linux to explicitly test the fallback function.
+// In other unix platforms the fallback is the default implementation, so it
+// is already tested in the tests for the Create function.
+func TestCreateFallback(t *testing.T) {
+	VerifyTempfile(t, createUnixFallback)
+}
diff --git a/internal/tempfile/tempfile_test.go b/internal/tempfile/tempfile_test.go
index c2a2f645..5069c964 100644
--- a/internal/tempfile/tempfile_test.go
+++ b/internal/tempfile/tempfile_test.go
@@ -1,38 +1,11 @@
 package tempfile_test
 
 import (
-	"io"
-	"os"
 	"testing"
 
-	"github.com/stretchr/testify/require"
-
 	"github.com/kopia/kopia/internal/tempfile"
 )
 
 func TestTempFile(t *testing.T) {
-	td := t.TempDir()
-
-	f, err := tempfile.Create(td)
-	require.NoError(t, err)
-
-	n, err := f.WriteString("hello")
-	require.NoError(t, err)
-	require.Equal(t, 5, n)
-
-	off, err := f.Seek(1, io.SeekStart)
-	require.Equal(t, int64(1), off)
-	require.NoError(t, err)
-
-	buf := make([]byte, 4)
-	n2, err := f.Read(buf)
-	require.NoError(t, err)
-	require.Equal(t, 4, n2)
-	require.Equal(t, []byte("ello"), buf)
-
-	f.Close()
-
-	files, err := os.ReadDir(td)
-	require.NoError(t, err)
-	require.Empty(t, files)
+	tempfile.VerifyTempfile(t, tempfile.CreateAutoDelete)
 }
diff --git a/internal/tempfile/tempfile_verify_test.go b/internal/tempfile/tempfile_verify_test.go
new file mode 100644
index 00000000..5feeeac1
--- /dev/null
+++ b/internal/tempfile/tempfile_verify_test.go
@@ -0,0 +1,48 @@
+package tempfile
+
+import (
+	"io"
+	"os"
+	"runtime"
+	"testing"
+
+	"github.com/stretchr/testify/require"
+)
+
+func VerifyTempfile(t *testing.T, create func() (*os.File, error)) {
+	t.Helper()
+
+	f, err := create()
+	require.NoError(t, err)
+
+	n, err := f.WriteString("hello")
+	require.NoError(t, err)
+	require.Equal(t, 5, n)
+
+	off, err := f.Seek(1, io.SeekStart)
+	require.Equal(t, int64(1), off)
+	require.NoError(t, err)
+
+	buf := make([]byte, 4)
+	n2, err := f.Read(buf)
+	require.NoError(t, err)
+	require.Equal(t, 4, n2)
+	require.Equal(t, []byte("ello"), buf)
+
+	f.Close()
+
+	if n := f.Name(); n != "" {
+		var perr *os.PathError
+
+		// there should be no directory entry for this file
+		_, err := os.Stat(n)
+
+		require.Error(t, err)
+		require.ErrorAs(t, err, &perr)
+		if runtime.GOOS == "windows" {
+			require.ErrorContains(t, err, "The system cannot find the file specified")
+		} else {
+			require.ErrorContains(t, err, "no such file or directory")
+		}
+	}
+}
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/buildinfo_test.go b/repo/buildinfo_test.go
new file mode 100644
index 00000000..5164ff7f
--- /dev/null
+++ b/repo/buildinfo_test.go
@@ -0,0 +1,113 @@
+package repo
+
+import (
+	"runtime/debug"
+	"testing"
+
+	"github.com/stretchr/testify/require"
+)
+
+func TestGetRevisionString(t *testing.T) {
+	cases := []struct {
+		input []debug.BuildSetting
+		want  string
+	}{
+		{
+			want: "-(unknown_revision)",
+		},
+		{
+			input: []debug.BuildSetting{
+				{
+					Key:   "vcs.modified",
+					Value: "true",
+				},
+			},
+			want: "-(unknown_revision)+dirty",
+		},
+		{
+			input: []debug.BuildSetting{
+				{
+					Key:   "vcs.time",
+					Value: "2025-04-12T16:01:30Z",
+				},
+			},
+			want: "2025-04-12T16:01:30Z-(unknown_revision)",
+		},
+		{
+			input: []debug.BuildSetting{
+				{
+					Key:   "vcs.time",
+					Value: "2025-04-12T16:01:30Z",
+				},
+				{
+					Key:   "vcs.modified",
+					Value: "true",
+				},
+			},
+			want: "2025-04-12T16:01:30Z-(unknown_revision)+dirty",
+		},
+		{
+			input: []debug.BuildSetting{
+				{
+					Key:   "vcs.time",
+					Value: "2025-04-12T16:01:30Z",
+				},
+				{
+					Key:   "vcs.revision",
+					Value: "353676da445938316fa00b2b812a61f4b1dd3ffa",
+				},
+			},
+			want: "2025-04-12T16:01:30Z-353676da445938316fa00b2b812a61f4b1dd3ffa",
+		},
+		{
+			input: []debug.BuildSetting{
+				{
+					Key:   "vcs.time",
+					Value: "2025-04-12T16:01:30Z",
+				},
+				{
+					Key:   "vcs.revision",
+					Value: "353676da4459",
+				},
+			},
+			want: "2025-04-12T16:01:30Z-353676da4459",
+		},
+		{
+			input: []debug.BuildSetting{
+				{
+					Key:   "vcs.time",
+					Value: "2025-04-12T16:01:30Z",
+				},
+				{
+					Key:   "vcs.revision",
+					Value: "353676da",
+				},
+			},
+			want: "2025-04-12T16:01:30Z-353676da",
+		},
+		{
+			input: []debug.BuildSetting{
+				{
+					Key:   "vcs.time",
+					Value: "2025-04-12T16:01:30Z",
+				},
+				{
+					Key:   "vcs.revision",
+					Value: "353676da445938316fa00b2b812a61f4b1dd3ffa",
+				},
+				{
+					Key:   "vcs.modified",
+					Value: "true",
+				},
+			},
+			want: "2025-04-12T16:01:30Z-353676da445938316fa00b2b812a61f4b1dd3ffa+dirty",
+		},
+	}
+
+	for _, c := range cases {
+		t.Run("buildinfo", func(t *testing.T) {
+			got := getRevisionString(c.input)
+			require.Equal(t, c.want, got)
+		})
+	}
+}
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/snapshotmaintenance/helper_test.go b/snapshot/snapshotmaintenance/helper_test.go
index 0cb4a19a..2574b52d 100644
--- a/snapshot/snapshotmaintenance/helper_test.go
+++ b/snapshot/snapshotmaintenance/helper_test.go
@@ -23,7 +23,7 @@ func createSnapshot(ctx context.Context, rep repo.RepositoryWriter, e fs.Entry,
 		return nil, errors.Wrap(err, "unable to get policy tree")
 	}
 
-	previous, err := findPreviousSnapshotManifest(ctx, rep, si)
+	previous, err := snapshot.FindPreviousManifests(ctx, rep, si, nil)
 	if err != nil {
 		return nil, err
 	}
@@ -42,37 +42,3 @@ func createSnapshot(ctx context.Context, rep repo.RepositoryWriter, e fs.Entry,
 
 	return manifest, nil
 }
-
-// findPreviousSnapshotManifest returns the list of previous snapshots for a given source, including
-// last complete snapshot and possibly some number of incomplete snapshots following it.
-// this would belong in the snapshot package.
-func findPreviousSnapshotManifest(ctx context.Context, rep repo.Repository, sourceInfo snapshot.SourceInfo) ([]*snapshot.Manifest, error) {
-	man, err := snapshot.ListSnapshots(ctx, rep, sourceInfo)
-	if err != nil {
-		return nil, errors.Wrap(err, "error listing previous snapshots")
-	}
-
-	// phase 1 - find latest complete snapshot.
-	var previousComplete *snapshot.Manifest
-
-	var result []*snapshot.Manifest
-
-	for _, p := range man {
-		if p.IncompleteReason == "" && (previousComplete == nil || p.StartTime.After(previousComplete.StartTime)) {
-			previousComplete = p
-		}
-	}
-
-	if previousComplete != nil {
-		result = append(result, previousComplete)
-	}
-
-	// add all incomplete snapshots after that
-	for _, p := range man {
-		if p.IncompleteReason != "" && p.StartTime.After(previousComplete.StartTime) {
-			result = append(result, p)
-		}
-	}
-
-	return result, nil
-}
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/os_snapshot_test/os_snapshot_windows_test.go b/tests/os_snapshot_test/os_snapshot_windows_test.go
index 0127582d..01ff6b3a 100644
--- a/tests/os_snapshot_test/os_snapshot_windows_test.go
+++ b/tests/os_snapshot_test/os_snapshot_windows_test.go
@@ -2,13 +2,16 @@
 
 import (
 	"os"
+	"path/filepath"
+	"syscall"
 	"testing"
 
+	"github.com/google/uuid"
 	"github.com/mxk/go-vss"
 	"github.com/pkg/errors"
 	"github.com/stretchr/testify/require"
+	"golang.org/x/sys/windows"
 
-	"github.com/kopia/kopia/internal/tempfile"
 	"github.com/kopia/kopia/internal/testutil"
 	"github.com/kopia/kopia/tests/clitestutil"
 	"github.com/kopia/kopia/tests/testenv"
@@ -21,21 +24,19 @@ func TestShadowCopy(t *testing.T) {
 	}
 
 	runner := testenv.NewExeRunnerWithBinary(t, kopiaExe)
-
 	e := testenv.NewCLITest(t, testenv.RepoFormatNotImportant, runner)
+
 	e.RunAndExpectSuccess(t, "repo", "create", "filesystem", "--path", e.RepoDir)
+	e.RunAndExpectSuccess(t, "policy", "set", "--global", "--enable-volume-shadow-copy=when-available")
 
+	// create a file that cannot be accessed and then attempt to create a snapshot
 	root := testutil.TempDirectory(t)
-	f, err := tempfile.Create(root)
-	require.NoError(t, err)
-	_, err = f.WriteString("locked file\n")
+	f := createAutoDelete(t, root)
+	_, err := f.WriteString("locked file\n")
+
 	require.NoError(t, err)
 	require.NoError(t, f.Sync())
 
-	defer f.Close()
-
-	e.RunAndExpectSuccess(t, "policy", "set", "--global", "--enable-volume-shadow-copy=when-available")
-
 	_, err = vss.Get("{00000000-0000-0000-0000-000000000000}")
 
 	isAdmin := !errors.Is(err, os.ErrPermission)
@@ -43,7 +44,7 @@ func TestShadowCopy(t *testing.T) {
 		t.Log("Running as admin, expecting snapshot creation to succeed")
 		e.RunAndExpectSuccess(t, "snap", "create", root)
 	} else {
-		t.Log("Not running as admin, expecting snapshot creation to fail")
+		t.Log("Not running as admin, expecting snapshot creation to fail because it cannot access the file that is in use")
 		e.RunAndExpectFailure(t, "snap", "create", root)
 	}
 
@@ -54,11 +55,42 @@ func TestShadowCopy(t *testing.T) {
 
 	oid := sources[0].Snapshots[0].ObjectID
 	entries := clitestutil.ListDirectory(t, e, oid)
+	t.Log("sources[0].Snapshots[0].ObjectID entries:", entries)
 
 	if isAdmin {
+		require.NotEmpty(t, entries)
 		lines := e.RunAndExpectSuccess(t, "show", entries[0].ObjectID)
 		require.Equal(t, []string{"locked file"}, lines)
 	} else {
 		require.Empty(t, entries)
 	}
 }
+
+func createAutoDelete(t *testing.T, dir string) *os.File {
+	t.Helper()
+
+	fullpath := filepath.Join(dir, uuid.NewString())
+
+	fname, err := syscall.UTF16PtrFromString(fullpath)
+	require.NoError(t, err, "constructing file name UTF16Ptr")
+
+	// This call creates a file that's automatically deleted on close.
+	h, err := syscall.CreateFile(
+		fname,
+		windows.GENERIC_READ|windows.GENERIC_WRITE,
+		0,
+		nil,
+		syscall.OPEN_ALWAYS,
+		uint32(windows.FILE_FLAG_DELETE_ON_CLOSE),
+		0)
+
+	require.NoError(t, err, "creating file")
+
+	f := os.NewFile(uintptr(h), fullpath)
+
+	t.Cleanup(func() {
+		f.Close()
+	})
+
+	return f
+}
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/multiclient_test/multiclient_test.go b/tests/robustness/multiclient_test/multiclient_test.go
index 31136f84..5b822b96 100644
--- a/tests/robustness/multiclient_test/multiclient_test.go
+++ b/tests/robustness/multiclient_test/multiclient_test.go
@@ -102,7 +102,7 @@ func TestOneLargeFile(t *testing.T) {
 	th.RunN(ctx, t, numClients, f)
 }
 
-func TestManySmallFilesAcrossDirecoryTree(t *testing.T) {
+func TestManySmallFilesAcrossDirectoryTree(t *testing.T) {
 	// TODO: Test takes too long - need to address performance issues with fio writes
 	const (
 		fileSize      = 4096
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/robustness_test/robustness_test.go b/tests/robustness/robustness_test/robustness_test.go
index bbfe2a80..c1d2bd34 100644
--- a/tests/robustness/robustness_test/robustness_test.go
+++ b/tests/robustness/robustness_test/robustness_test.go
@@ -71,7 +71,7 @@ func TestOneLargeFile(t *testing.T) {
 	require.NoError(t, err)
 }
 
-func TestManySmallFilesAcrossDirecoryTree(t *testing.T) {
+func TestManySmallFilesAcrossDirectoryTree(t *testing.T) {
 	// TODO: Test takes too long - need to address performance issues with fio writes
 	const (
 		fileSize      = 4096
diff --git a/tests/robustness/snapmeta/index_test.go b/tests/robustness/snapmeta/index_test.go
index 766df532..9805bab9 100644
--- a/tests/robustness/snapmeta/index_test.go
+++ b/tests/robustness/snapmeta/index_test.go
@@ -5,6 +5,8 @@
 
 import (
 	"testing"
+
+	"github.com/stretchr/testify/require"
 )
 
 func TestIndex(t *testing.T) {
@@ -18,18 +20,11 @@ func TestIndex(t *testing.T) {
 	idx.AddToIndex(snapIDKey, snapshotIndexName)
 
 	keys := idx.GetKeys(snapshotIndexName)
-	if got, want := len(keys), 1; got != want {
-		t.Fatalf("expected %v keys but got %v", want, got)
-	}
-
-	if got, want := keys[0], snapIDKey; got != want {
-		t.Fatalf("expected %v but got %v", want, got)
-	}
+	require.Len(t, keys, 1, "unexpected number of keys")
+	require.Equal(t, snapIDKey, keys[0])
 
 	idx.RemoveFromIndex(snapIDKey, snapshotIndexName)
 
 	keys = idx.GetKeys(snapshotIndexName)
-	if got, want := len(keys), 0; got != want {
-		t.Fatalf("expected %v keys but got %v", want, got)
-	}
+	require.Empty(t, keys)
 }
diff --git a/tests/robustness/snapmeta/kopia_persister_light_test.go b/tests/robustness/snapmeta/kopia_persister_light_test.go
index 280ebffb..5000430a 100644
--- a/tests/robustness/snapmeta/kopia_persister_light_test.go
+++ b/tests/robustness/snapmeta/kopia_persister_light_test.go
@@ -4,29 +4,25 @@
 package snapmeta
 
 import (
-	"bytes"
 	"context"
-	"log"
 	"os"
 	"strconv"
 	"testing"
-)
 
-var (
-	key = "mykey"
-	val = []byte("myval")
+	"github.com/stretchr/testify/require"
 )
 
-func TestStoreLoadDelete(t *testing.T) {
-	ctx := context.Background()
+const key = "mykey"
 
-	repoPath, err := os.MkdirTemp("", "kopia-test-repo-")
-	assertNoError(t, err)
-
-	defer os.RemoveAll(repoPath)
+var val = []byte("myval")
 
+func TestStoreLoadDelete(t *testing.T) {
+	repoPath := t.TempDir()
 	kpl := initKPL(t, repoPath)
-	defer kpl.Cleanup()
+
+	t.Cleanup(kpl.Cleanup)
+
+	ctx := context.Background()
 
 	kpl.testStoreLoad(ctx, t, key, val)
 	kpl.testDelete(ctx, t, key)
@@ -35,13 +31,10 @@ func TestStoreLoadDelete(t *testing.T) {
 func TestConcurrency(t *testing.T) {
 	ctx := context.Background()
 
-	repoPath, err := os.MkdirTemp("", "kopia-test-repo-")
-	assertNoError(t, err)
-
-	defer os.RemoveAll(repoPath)
-
+	repoPath := t.TempDir()
 	kpl := initKPL(t, repoPath)
-	defer kpl.Cleanup()
+
+	t.Cleanup(kpl.Cleanup)
 
 	keys := []string{"key1", "key2", "key3"}
 	vals := [][]byte{[]byte("val1"), []byte("val2"), []byte("val3")}
@@ -68,14 +61,12 @@ func TestConcurrency(t *testing.T) {
 // Store and test that subsequent Load succeeds.
 func (kpl *KopiaPersisterLight) testStoreLoad(ctx context.Context, t *testing.T, key string, val []byte) { //nolint:thelper
 	err := kpl.Store(ctx, key, val)
-	assertNoError(t, err)
+	require.NoError(t, err)
 
-	valOut, err := kpl.Load(ctx, key)
-	assertNoError(t, err)
+	got, err := kpl.Load(ctx, key)
+	require.NoError(t, err)
 
-	if !bytes.Equal(valOut, val) {
-		t.Fatal("loaded value does not equal stored value", valOut, val)
-	}
+	require.Equal(t, val, got)
 }
 
 // Delete and test that subsequent Load fails.
@@ -83,85 +74,67 @@ func (kpl *KopiaPersisterLight) testDelete(ctx context.Context, t *testing.T, ke
 	kpl.Delete(ctx, key)
 
 	_, err := kpl.Load(ctx, key)
-	log.Println("err:", err)
 
-	if err == nil {
-		t.Fatal("snapshot was not deleted properly")
-	}
+	require.Error(t, err, "snapshot was not deleted properly")
 }
 
 func TestPersistence(t *testing.T) {
 	ctx := context.Background()
 
-	repoPath, err := os.MkdirTemp("", "kopia-test-repo-")
-	assertNoError(t, err)
+	repoPath := t.TempDir()
 
 	kpl := initKPL(t, repoPath)
 
 	// Persistence directory should be set.
-	if persistDir := kpl.GetPersistDir(); persistDir == "" {
-		t.Error("could not get persistence directory")
-	}
+	persistDir := kpl.GetPersistDir()
+	require.NotEmpty(t, persistDir, "could not get persistence directory")
 
 	// These are no-ops and should always succeed.
-	err = kpl.LoadMetadata()
-	assertNoError(t, err)
+	err := kpl.LoadMetadata()
+	require.NoError(t, err)
 	err = kpl.FlushMetadata()
-	assertNoError(t, err)
+	require.NoError(t, err)
 
 	// Store and cleanup kpl
 	err = kpl.Store(ctx, key, val)
-	assertNoError(t, err)
+	require.NoError(t, err)
 
 	kpl.Cleanup()
 
 	// Re-initialize and Load
 	kpl = initKPL(t, repoPath)
-	valOut, err := kpl.Load(ctx, key)
-	assertNoError(t, err)
-
-	if !bytes.Equal(valOut, val) {
-		t.Fatal("loaded value does not equal stored value")
-	}
+	t.Cleanup(kpl.Cleanup)
 
-	kpl.Cleanup()
-	os.RemoveAll(repoPath)
+	got, err := kpl.Load(ctx, key)
+	require.NoError(t, err)
+	require.Equal(t, val, got, "loaded value is not equal to stored value")
 }
 
 func TestS3Connect(t *testing.T) {
-	repoPath, err := os.MkdirTemp("", "kopia-test-repo-")
-	assertNoError(t, err)
+	repoPath := t.TempDir()
 
 	// Test the S3 code path by attempting to connect to a nonexistent bucket.
 	t.Setenv(S3BucketNameEnvKey, "does-not-exist")
 
 	kpl, err := NewPersisterLight("")
-	assertNoError(t, err)
+	require.NoError(t, err)
 
-	if err := kpl.ConnectOrCreateRepo(repoPath); err == nil {
-		t.Error("should not be able to connect to nonexistent S3 bucket")
-	}
+	err = kpl.ConnectOrCreateRepo(repoPath)
+	require.Error(t, err, "should not be able to connect to nonexistent S3 bucket")
 
 	kpl.Cleanup()
-	os.RemoveAll(repoPath)
 }
 
-func initKPL(t *testing.T, repoPath string) *KopiaPersisterLight { //nolint:thelper
+func initKPL(t *testing.T, repoPath string) *KopiaPersisterLight {
+	t.Helper()
+
 	os.Unsetenv(S3BucketNameEnvKey)
 
 	kpl, err := NewPersisterLight("")
-	assertNoError(t, err)
+	require.NoError(t, err)
 
 	err = kpl.ConnectOrCreateRepo(repoPath)
-	assertNoError(t, err)
+	require.NoError(t, err)
 
 	return kpl
 }
-
-func assertNoError(t *testing.T, err error) {
-	t.Helper()
-
-	if err != nil {
-		t.Errorf("err: %v", err)
-	}
-}
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/robustness/snapmeta/simple_test.go b/tests/robustness/snapmeta/simple_test.go
index 6233cc6c..cd36869c 100644
--- a/tests/robustness/snapmeta/simple_test.go
+++ b/tests/robustness/snapmeta/simple_test.go
@@ -4,11 +4,11 @@
 package snapmeta
 
 import (
-	"bytes"
 	"context"
-	"errors"
 	"testing"
 
+	"github.com/stretchr/testify/require"
+
 	"github.com/kopia/kopia/tests/robustness"
 )
 
@@ -18,35 +18,20 @@ func TestSimpleBasic(t *testing.T) {
 	simple := NewSimple()
 
 	gotData, err := simple.Load(ctx, "non-existent-key")
-	if !errors.Is(err, robustness.ErrKeyNotFound) {
-		t.Fatalf("Did not get expected error: %q", err)
-	}
-
-	if gotData != nil {
-		t.Fatalf("Expecting nil data return from a key that does not exist")
-	}
+	require.ErrorIs(t, err, robustness.ErrKeyNotFound, "Did not get expected error")
+	require.Nil(t, gotData, "Expecting nil data return from a key that does not exist")
 
 	storeKey := "key-to-store"
 	data := []byte("some stored data")
 	simple.Store(ctx, storeKey, data)
 
 	gotData, err = simple.Load(ctx, storeKey)
-	if err != nil {
-		t.Fatalf("Error getting key: %v", err)
-	}
-
-	if !bytes.Equal(gotData, data) {
-		t.Fatalf("Did not get the correct data")
-	}
+	require.NoError(t, err, "Error getting key")
+	require.Equal(t, data, gotData, "Did not get the correct data")
 
 	simple.Delete(ctx, storeKey)
 
 	gotData, err = simple.Load(ctx, storeKey)
-	if !errors.Is(err, robustness.ErrKeyNotFound) {
-		t.Fatalf("Did not get expected error: %q", err)
-	}
-
-	if gotData != nil {
-		t.Fatalf("Expecting nil data return from a deleted key")
-	}
+	require.ErrorIs(t, err, robustness.ErrKeyNotFound, "Did not get expected error")
+	require.Nil(t, gotData, "Expecting nil data return from a deleted key")
 }
diff --git a/tests/socketactivation_test/socketactivation_test.go b/tests/socketactivation_test/socketactivation_test.go
index 793a07ee..a12d2e0f 100644
--- a/tests/socketactivation_test/socketactivation_test.go
+++ b/tests/socketactivation_test/socketactivation_test.go
@@ -37,9 +37,7 @@ func TestServerControlSocketActivated(t *testing.T) {
 	env.Environment["LISTEN_FDS"] = "1"
 
 	l1, err := net.Listen("tcp", ":0")
-	if err != nil {
-		t.Fatalf("Failed to open Listener")
-	}
+	require.NoError(t, err, "Failed to open Listener")
 
 	defer func() {
 		l1.Close()
@@ -57,7 +55,7 @@ func TestServerControlSocketActivated(t *testing.T) {
 	go func() {
 		l1File, err := l1.(*net.TCPListener).File()
 		if err != nil {
-			t.Logf("ERROR: Failed to get filehandle for socket")
+			t.Log("ERROR: Failed to get filehandle for socket")
 			close(serverStarted)
 
 			return
@@ -77,14 +75,11 @@ func TestServerControlSocketActivated(t *testing.T) {
 
 	select {
 	case <-serverStarted:
-		if sp.BaseURL == "" {
-			t.Fatalf("Failed to start server")
-		}
-
+		require.NotEmpty(t, sp.BaseURL, "Failed to start server")
 		t.Logf("server started on %v", sp.BaseURL)
 
 	case <-time.After(5 * time.Second):
-		t.Fatalf("server did not start in time")
+		t.Fatal("server did not start in time")
 	}
 
 	require.Contains(t, sp.BaseURL, ":"+strconv.Itoa(port))
@@ -97,10 +92,10 @@ func TestServerControlSocketActivated(t *testing.T) {
 
 	select {
 	case <-serverStopped:
-		t.Logf("server shut down")
+		t.Log("server shut down")
 
 	case <-time.After(15 * time.Second):
-		t.Fatalf("server did not shutdown in time")
+		t.Fatal("server did not shutdown in time")
 	}
 }
 
@@ -120,9 +115,7 @@ func TestServerControlSocketActivatedTooManyFDs(t *testing.T) {
 	env.Environment["LISTEN_FDS"] = "2"
 
 	l1, err := net.Listen("tcp", ":0")
-	if err != nil {
-		t.Fatalf("Failed to open Listener")
-	}
+	require.NoError(t, err, "Failed to open Listener")
 
 	defer func() {
 		l1.Close()
@@ -137,7 +130,7 @@ func TestServerControlSocketActivatedTooManyFDs(t *testing.T) {
 	go func() {
 		l1File, err := l1.(*net.TCPListener).File()
 		if err != nil {
-			t.Logf("Failed to get filehandle for socket")
+			t.Log("Failed to get filehandle for socket")
 			close(serverStarted)
 
 			return
@@ -145,7 +138,7 @@ func TestServerControlSocketActivatedTooManyFDs(t *testing.T) {
 
 		l2File, err := l1.(*net.TCPListener).File()
 		if err != nil {
-			t.Logf("Failed to get 2nd filehandle for socket")
+			t.Log("Failed to get 2nd filehandle for socket")
 			close(serverStarted)
 
 			return
@@ -164,9 +157,9 @@ func TestServerControlSocketActivatedTooManyFDs(t *testing.T) {
 	select {
 	case stderr := <-serverStarted:
 		require.Contains(t, strings.Join(stderr, ""), "Too many activated sockets found.  Expected 1, got 2")
-		t.Logf("Done")
+		t.Log("Done")
 
 	case <-time.After(5 * time.Second):
-		t.Fatalf("server did not exit in time")
+		t.Fatal("server did not exit in time")
 	}
 }
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"
diff --git a/tools/cli2md/cli2md_test.go b/tools/cli2md/cli2md_test.go
new file mode 100644
index 00000000..ced62f5b
--- /dev/null
+++ b/tools/cli2md/cli2md_test.go
@@ -0,0 +1,83 @@
+package main
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/require"
+)
+
+func TestEscapeFlags(t *testing.T) {
+	cases := []struct {
+		name     string
+		input    string
+		expected string
+	}{
+		{
+			name:     "empty string",
+			input:    "",
+			expected: "",
+		},
+		{
+			name:     "basic single flag",
+			input:    "use -flag to enable",
+			expected: "use `-flag` to enable",
+		},
+		{
+			name:     "double dash flag",
+			input:    "use --long-flag-name to configure",
+			expected: "use `--long-flag-name` to configure",
+		},
+		{
+			name:     "multiple flags",
+			input:    "use -a or --bee flags",
+			expected: "use `-a` or `--bee` flags",
+		},
+		{
+			name:     "should not match in-place",
+			input:    "performs in-place modification",
+			expected: "performs in-place modification",
+		},
+		{
+			name:     "flags with numbers and hyphens",
+			input:    "use --http2-max-streams or -h2-timeout",
+			expected: "use `--http2-max-streams` or `-h2-timeout`",
+		},
+		{
+			name:     "flag at start of string",
+			input:    "-flag at start",
+			expected: "`-flag` at start",
+		},
+		{
+			name:     "existing backticks",
+			input:    "use `--existing` and -new flags",
+			expected: "use `--existing` and `-new` flags",
+		},
+		{
+			name:     "multiple spaces before flag",
+			input:    "test   -flag with spaces",
+			expected: "test   `-flag` with spaces",
+		},
+		{
+			name:     "mixed valid and invalid patterns",
+			input:    "test in-place and -valid --flags",
+			expected: "test in-place and `-valid` `--flags`",
+		},
+		{
+			name:     "separator line",
+			input:    "---------------",
+			expected: "---------------",
+		},
+		{
+			name:     "too many dashes",
+			input:    "none ---of these --- dashes ----should match",
+			expected: "none ---of these --- dashes ----should match",
+		},
+	}
+
+	for _, tc := range cases {
+		t.Run(tc.name, func(t *testing.T) {
+			got := escapeFlags(tc.input)
+			require.Equal(t, tc.expected, got)
+		})
+	}
+}