Repository: kopia/kopia
Pull request #2692 (2023-01-13 18:14:48)

feat(snapshots): Return ReadCloser from StreamingFiles
Allow better resource cleanup by returning ReadClosers from StreamingFiles and having the uploader close it when it's done

This breaks the previous interface for external users for virtualfs.streamingFile as it changes one of the parameters for the initializer

codecov[bot]: # [Codecov](https://codecov.io/gh/kopia/kopia/pull/2692?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kopia) Report
Base: **74.99**% // Head: **75.02**% // Increases project coverage by **`+0.02%`** :tada:
> Coverage data is based on head [(`4b37ea7`)](https://codecov.io/gh/kopia/kopia/pull/2692?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kopia) compared to base [(`ba938cf`)](https://codecov.io/gh/kopia/kopia/commit/ba938cf58cc8db26eb373f6ddff5fc0e9c07af15?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kopia).
> Patch coverage: 100.00% of modified lines in pull request are covered.

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##           master    #2692      +/-   ##
==========================================
+ Coverage   74.99%   75.02%   +0.02%     
==========================================
  Files         457      457              
  Lines       35811    35813       +2     
==========================================
+ Hits        26857    26869      +12     
+ Misses       7056     7053       -3     
+ Partials     1898     1891       -7     
```


| [Impacted Files](https://codecov.io/gh/kopia/kopia/pull/2692?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kopia) | Coverage Δ | |
|---|---|---|
| [fs/entry.go](https://codecov.io/gh/kopia/kopia/pull/2692?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kopia#diff-ZnMvZW50cnkuZ28=) | `93.87% <ø> (ø)` | |
| [cli/command\_snapshot\_create.go](https://codecov.io/gh/kopia/kopia/pull/2692?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kopia#diff-Y2xpL2NvbW1hbmRfc25hcHNob3RfY3JlYXRlLmdv) | `80.12% <100.00%> (ø)` | |
| [fs/virtualfs/virtualfs.go](https://codecov.io/gh/kopia/kopia/pull/2692?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kopia#diff-ZnMvdmlydHVhbGZzL3ZpcnR1YWxmcy5nbw==) | `95.34% <100.00%> (ø)` | |
| [snapshot/snapshotfs/upload.go](https://codecov.io/gh/kopia/kopia/pull/2692?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kopia#diff-c25hcHNob3Qvc25hcHNob3Rmcy91cGxvYWQuZ28=) | `84.98% <100.00%> (-0.42%)` | :arrow_down: |
| [cli/command\_index\_recover.go](https://codecov.io/gh/kopia/kopia/pull/2692?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kopia#diff-Y2xpL2NvbW1hbmRfaW5kZXhfcmVjb3Zlci5nbw==) | `65.00% <0.00%> (-1.43%)` | :arrow_down: |
| [repo/open.go](https://codecov.io/gh/kopia/kopia/pull/2692?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kopia#diff-cmVwby9vcGVuLmdv) | `79.33% <0.00%> (+1.00%)` | :arrow_up: |
| [repo/grpc\_repository\_client.go](https://codecov.io/gh/kopia/kopia/pull/2692?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kopia#diff-cmVwby9ncnBjX3JlcG9zaXRvcnlfY2xpZW50Lmdv) | `76.41% <0.00%> (+1.19%)` | :arrow_up: |
| [internal/server/source\_manager.go](https://codecov.io/gh/kopia/kopia/pull/2692?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kopia#diff-aW50ZXJuYWwvc2VydmVyL3NvdXJjZV9tYW5hZ2VyLmdv) | `82.95% <0.00%> (+1.52%)` | :arrow_up: |

Help us with your feedback. Take ten seconds to tell us [how you rate us](https://about.codecov.io/nps?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kopia). Have a feature suggestion? [Share it here.](https://app.codecov.io/gh/feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kopia)

</details>

[:umbrella: View full report at Codecov](https://codecov.io/gh/kopia/kopia/pull/2692?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kopia).   
:loudspeaker: Do you have feedback about the report comment? [Let us know in this issue](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kopia).


Patch:
diff --git a/.github/auto-merge.yml b/.github/auto-merge.yml
index 8b3904b4..9dbb133c 100644
--- a/.github/auto-merge.yml
+++ b/.github/auto-merge.yml
@@ -8,6 +8,35 @@
     dependency_name: "github.com/aws/aws-sdk-go"
     update_type: "semver:minor"
 
+# golang.org/x 
+- match:
+    dependency_name: "golang.org/x/net"
+    update_type: "semver:minor"
+- match:
+    dependency_name: "golang.org/x/crypto"
+    update_type: "semver:minor"
+- match:
+    dependency_name: "golang.org/x/mod"
+    update_type: "semver:minor"
+- match:
+    dependency_name: "golang.org/x/net"
+    update_type: "semver:minor"
+- match:
+    dependency_name: "golang.org/x/oauth2"
+    update_type: "semver:minor"
+- match:
+    dependency_name: "golang.org/x/sync"
+    update_type: "semver:minor"
+- match:
+    dependency_name: "golang.org/x/sys"
+    update_type: "semver:minor"
+- match:
+    dependency_name: "golang.org/x/term"
+    update_type: "semver:minor"
+- match:
+    dependency_name: "golang.org/x/text"
+    update_type: "semver:minor"
+
 # Google-provided libraries
 - match:
     dependency_name: "google.golang.org/grpc"
diff --git a/.github/workflows/dependency-review.yml b/.github/workflows/dependency-review.yml
index 34b14c39..b0dedc42 100644
--- a/.github/workflows/dependency-review.yml
+++ b/.github/workflows/dependency-review.yml
@@ -1,6 +1,6 @@
 # Dependency Review Action
 #
-# This Action will scan dependency manifest files that change as part of a Pull Reqest, surfacing known-vulnerable versions of the packages declared or updated in the PR. Once installed, if the workflow run is marked as required, PRs introducing known-vulnerable packages will be blocked from merging.
+# This Action will scan dependency manifest files that change as part of a Pull Request, surfacing known-vulnerable versions of the packages declared or updated in the PR. Once installed, if the workflow run is marked as required, PRs introducing known-vulnerable packages will be blocked from merging.
 #
 # Source repository: https://github.com/actions/dependency-review-action
 # Public documentation: https://docs.github.com/en/code-security/supply-chain-security/understanding-your-software-supply-chain/about-dependency-review#dependency-review-enforcement
diff --git a/app/package-lock.json b/app/package-lock.json
index 62397d35..082b97ae 100644
--- a/app/package-lock.json
+++ b/app/package-lock.json
@@ -15,7 +15,7 @@
         "electron-log": "^4.4.8",
         "electron-updater": "^5.2.1",
         "minimist": "^1.2.7",
-        "uuid": "^8.3.2"
+        "uuid": "^9.0.0"
       },
       "devDependencies": {
         "@playwright/test": "^1.26.1",
@@ -2493,9 +2493,9 @@
       "optional": true
     },
     "node_modules/json5": {
-      "version": "2.2.1",
-      "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz",
-      "integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==",
+      "version": "2.2.3",
+      "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
+      "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
       "dev": true,
       "bin": {
         "json5": "lib/cli.js"
@@ -3598,9 +3598,9 @@
       "dev": true
     },
     "node_modules/uuid": {
-      "version": "8.3.2",
-      "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
-      "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==",
+      "version": "9.0.0",
+      "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz",
+      "integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==",
       "bin": {
         "uuid": "dist/bin/uuid"
       }
@@ -5670,9 +5670,9 @@
       "optional": true
     },
     "json5": {
-      "version": "2.2.1",
-      "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz",
-      "integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==",
+      "version": "2.2.3",
+      "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
+      "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
       "dev": true
     },
     "jsonfile": {
@@ -6544,9 +6544,9 @@
       "dev": true
     },
     "uuid": {
-      "version": "8.3.2",
-      "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
-      "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg=="
+      "version": "9.0.0",
+      "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz",
+      "integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg=="
     },
     "verror": {
       "version": "1.10.1",
diff --git a/app/package.json b/app/package.json
index 6eab894b..8d995627 100644
--- a/app/package.json
+++ b/app/package.json
@@ -9,7 +9,7 @@
     "electron-log": "^4.4.8",
     "electron-updater": "^5.2.1",
     "minimist": "^1.2.7",
-    "uuid": "^8.3.2"
+    "uuid": "^9.0.0"
   },
   "author": {
     "email": "kopia-pmc@googlegroups.com",
diff --git a/cli/command_policy_set_files.go b/cli/command_policy_set_files.go
index 7a9cd79b..7349bd63 100644
--- a/cli/command_policy_set_files.go
+++ b/cli/command_policy_set_files.go
@@ -21,7 +21,7 @@ type policyFilesFlags struct {
 	policySetClearDotIgnore  bool
 	policySetMaxFileSize     string
 
-	// Ignore other mounted fileystems.
+	// Ignore other mounted filesystems.
 	policyOneFileSystem string
 
 	policyIgnoreCacheDirs string
diff --git a/cli/command_repository_upgrade.go b/cli/command_repository_upgrade.go
index 1240bf9c..0b0b2f63 100644
--- a/cli/command_repository_upgrade.go
+++ b/cli/command_repository_upgrade.go
@@ -155,7 +155,7 @@ func (c *commandRepositoryUpgrade) validateAction(ctx context.Context, rep repo.
 
 	var msgs []string // a place to keep messages from the index comparison process
 
-	// both indexes will have matching contentiDs with matching indexInfo sructures.
+	// both indexes will have matching contentiDs with matching indexInfo structures.
 	for contentID, indexEntryPairs := range indexEntries {
 		iep0 := indexEntryPairs[0] // first entry of index entry pair
 		iep1 := indexEntryPairs[1] // second entry of index entry pair
diff --git a/cli/command_server_tls.go b/cli/command_server_tls.go
index 95c19940..a6c3dd54 100644
--- a/cli/command_server_tls.go
+++ b/cli/command_server_tls.go
@@ -115,7 +115,7 @@ func (c *commandServerStart) startServerWithOptionalTLSAndListener(ctx context.C
 		fmt.Fprintf(c.out.stderr(), "SERVER CERT SHA256: %v\n", hex.EncodeToString(fingerprint[:]))
 
 		if c.serverStartTLSPrintFullServerCert {
-			// dump PEM-encoded server cert, only used by KopiaUI to securely connnect.
+			// dump PEM-encoded server cert, only used by KopiaUI to securely connect.
 			var b bytes.Buffer
 
 			if err := pem.Encode(&b, &pem.Block{Type: "CERTIFICATE", Bytes: cert.Raw}); err != nil {
diff --git a/cli/command_snapshot_create.go b/cli/command_snapshot_create.go
index a564dd70..de3f9ad9 100644
--- a/cli/command_snapshot_create.go
+++ b/cli/command_snapshot_create.go
@@ -3,6 +3,7 @@
 import (
 	"context"
 	"fmt"
+	"io"
 	"path/filepath"
 	"strings"
 	"time"
@@ -273,7 +274,7 @@ func (c *commandSnapshotCreate) snapshotSingleSource(ctx context.Context, rep re
 		// stdin source will be snapshotted using a virtual static root directory with a single streaming file entry
 		// Create a new static directory with the given name and add a streaming file entry with os.Stdin reader
 		fsEntry = virtualfs.NewStaticDirectory(sourceInfo.Path, []fs.Entry{
-			virtualfs.StreamingFileFromReader(c.snapshotCreateStdinFileName, c.svc.stdin()),
+			virtualfs.StreamingFileFromReader(c.snapshotCreateStdinFileName, io.NopCloser(c.svc.stdin())),
 		})
 		setManual = true
 	} else {
diff --git a/cli/command_snapshot_migrate.go b/cli/command_snapshot_migrate.go
index 76800064..bd4c9e4f 100644
--- a/cli/command_snapshot_migrate.go
+++ b/cli/command_snapshot_migrate.go
@@ -282,7 +282,7 @@ func (c *commandSnapshotMigrate) migrateSingleSourceSnapshot(ctx context.Context
 
 	newm, err := uploader.Upload(ctx, sourceEntry, policyTree, m.Source, previous...)
 	if err != nil {
-		return errors.Wrapf(err, "error migrating shapshot %v @ %v", m.Source, m.StartTime)
+		return errors.Wrapf(err, "error migrating snapshot %v @ %v", m.Source, m.StartTime)
 	}
 
 	newm.StartTime = m.StartTime
diff --git a/fs/entry.go b/fs/entry.go
index 2fb5a7ff..26f4dd3b 100644
--- a/fs/entry.go
+++ b/fs/entry.go
@@ -53,7 +53,7 @@ type File interface {
 // StreamingFile represents an entry that is a stream.
 type StreamingFile interface {
 	Entry
-	GetReader(ctx context.Context) (io.Reader, error)
+	GetReader(ctx context.Context) (io.ReadCloser, error)
 }
 
 // Directory represents contents of a directory.
diff --git a/fs/virtualfs/virtualfs.go b/fs/virtualfs/virtualfs.go
index c11e4cc6..059d9161 100644
--- a/fs/virtualfs/virtualfs.go
+++ b/fs/virtualfs/virtualfs.go
@@ -167,7 +167,7 @@ func NewStreamingDirectory(
 // virtualFile is an implementation of fs.StreamingFile with an io.Reader.
 type virtualFile struct {
 	virtualEntry
-	reader io.Reader
+	reader io.ReadCloser
 }
 
 var errReaderAlreadyUsed = errors.New("cannot use streaming file reader more than once")
@@ -175,7 +175,7 @@ type virtualFile struct {
 // GetReader returns the streaming file's reader.
 // Note: Caller of this function has to ensure concurrency safety.
 // The file's reader is set to nil after the first call.
-func (vf *virtualFile) GetReader(ctx context.Context) (io.Reader, error) {
+func (vf *virtualFile) GetReader(ctx context.Context) (io.ReadCloser, error) {
 	if vf.reader == nil {
 		return nil, errReaderAlreadyUsed
 	}
@@ -188,12 +188,12 @@ func (vf *virtualFile) GetReader(ctx context.Context) (io.Reader, error) {
 }
 
 // StreamingFileFromReader returns a streaming file with given name and reader.
-func StreamingFileFromReader(name string, reader io.Reader) fs.StreamingFile {
+func StreamingFileFromReader(name string, reader io.ReadCloser) fs.StreamingFile {
 	return StreamingFileWithModTimeFromReader(name, clock.Now(), reader)
 }
 
 // StreamingFileWithModTimeFromReader returns a streaming file with given name, modified time, and reader.
-func StreamingFileWithModTimeFromReader(name string, t time.Time, reader io.Reader) fs.StreamingFile {
+func StreamingFileWithModTimeFromReader(name string, t time.Time, reader io.ReadCloser) fs.StreamingFile {
 	return &virtualFile{
 		virtualEntry: virtualEntry{
 			name:    name,
diff --git a/internal/auth/authz_acl.go b/internal/auth/authz_acl.go
index f2420feb..cb708f21 100644
--- a/internal/auth/authz_acl.go
+++ b/internal/auth/authz_acl.go
@@ -166,7 +166,7 @@ func (a aclEntriesAuthorizer) ManifestAccessLevel(labels map[string]string) Acce
 }
 
 // DefaultAuthorizer returns Authorizer that will fetch ACLs from the repository
-// and evaluate them in the context of current user to determine their permision levels.
+// and evaluate them in the context of current user to determine their permission levels.
 // It will fall back to legacy authorizer if no ACL entries are defined in the repository.
 func DefaultAuthorizer() Authorizer {
 	return &aclCache{
diff --git a/internal/blobcrypto/blob_crypto.go b/internal/blobcrypto/blob_crypto.go
index da4d126c..91901b00 100644
--- a/internal/blobcrypto/blob_crypto.go
+++ b/internal/blobcrypto/blob_crypto.go
@@ -40,7 +40,7 @@ func getIndexBlobIV(s blob.ID) ([]byte, error) {
 }
 
 // Encrypt encrypts the given data using crypter-defined key and returns a name that should
-// be used to save the blob in thre repository.
+// be used to save the blob in the repository.
 func Encrypt(c Crypter, payload gather.Bytes, prefix, suffix blob.ID, output *gather.WriteBuffer) (blob.ID, error) {
 	var hashOutput [hashing.MaxHashSize]byte
 
diff --git a/internal/cache/persistent_lru_cache.go b/internal/cache/persistent_lru_cache.go
index 09d0f8af..5902dc87 100644
--- a/internal/cache/persistent_lru_cache.go
+++ b/internal/cache/persistent_lru_cache.go
@@ -199,7 +199,7 @@ func (c *PersistentCache) Close(ctx context.Context) {
 	close(c.periodicSweepClosed)
 	c.periodicSweepRunning.Wait()
 
-	// if we added anything to the cache in this sesion, run one last sweep before shutting down.
+	// if we added anything to the cache in this session, run one last sweep before shutting down.
 	if c.anyChange.Load() {
 		if err := c.sweepDirectory(ctx); err != nil {
 			log(ctx).Errorf("error during final sweep of the %v: %v", c.description, err)
diff --git a/internal/clock/sleep.go b/internal/clock/sleep.go
index 54f2442b..781a04b4 100644
--- a/internal/clock/sleep.go
+++ b/internal/clock/sleep.go
@@ -5,7 +5,7 @@
 	"time"
 )
 
-// SleepInterruptibly sleeps for the given amount of time, while also honoring cancelation signal.
+// SleepInterruptibly sleeps for the given amount of time, while also honoring cancellation signal.
 // Returns false if canceled, true if slept for the entire duration.
 func SleepInterruptibly(ctx context.Context, dur time.Duration) bool {
 	select {
diff --git a/internal/epoch/epoch_manager.go b/internal/epoch/epoch_manager.go
index b44cfe42..c37779a7 100644
--- a/internal/epoch/epoch_manager.go
+++ b/internal/epoch/epoch_manager.go
@@ -578,7 +578,7 @@ func (e *Manager) maybeGenerateNextRangeCheckpointAsync(ctx context.Context, cs
 
 	e.backgroundWork.Add(1)
 
-	// we're starting background work, ignore parent cancelation signal.
+	// we're starting background work, ignore parent cancellation signal.
 	ctxutil.GoDetached(ctx, func(ctx context.Context) {
 		defer e.backgroundWork.Done()
 
@@ -594,7 +594,7 @@ func (e *Manager) maybeOptimizeRangeCheckpointsAsync(ctx context.Context, cs Cur
 func (e *Manager) maybeStartCleanupAsync(ctx context.Context, cs CurrentSnapshot, p *Parameters) {
 	e.backgroundWork.Add(1)
 
-	// we're starting background work, ignore parent cancelation signal.
+	// we're starting background work, ignore parent cancellation signal.
 	ctxutil.GoDetached(ctx, func(ctx context.Context) {
 		defer e.backgroundWork.Done()
 
@@ -958,7 +958,7 @@ func (e *Manager) getIndexesFromEpochInternal(ctx context.Context, cs CurrentSna
 	if epochSettled {
 		e.backgroundWork.Add(1)
 
-		// we're starting background work, ignore parent cancelation signal.
+		// we're starting background work, ignore parent cancellation signal.
 		ctxutil.GoDetached(ctx, func(ctx context.Context) {
 			defer e.backgroundWork.Done()
 
diff --git a/internal/listcache/listcache.go b/internal/listcache/listcache.go
index 3f9b0143..8dea982a 100644
--- a/internal/listcache/listcache.go
+++ b/internal/listcache/listcache.go
@@ -69,7 +69,7 @@ func (s *listCacheStorage) readBlobsFromCache(ctx context.Context, prefix blob.I
 	}
 
 	if err := json.NewDecoder(verified.Bytes().Reader()).Decode(&cl); err != nil {
-		log(ctx).Warnf("cant't unmarshal cached list results for %v, ignoring", prefix)
+		log(ctx).Warnf("can't unmarshal cached list results for %v, ignoring", prefix)
 		return nil
 	}
 
diff --git a/internal/server/server.go b/internal/server/server.go
index 0a960051..bb6248ac 100644
--- a/internal/server/server.go
+++ b/internal/server/server.go
@@ -382,7 +382,7 @@ func (s *Server) handleRequestPossiblyNotConnected(isAuthorized isAuthorizedFunc
 		var v interface{}
 		var err *apiError
 
-		// process the request while ignoring the cancelation signal
+		// process the request while ignoring the cancellation signal
 		// to ensure all goroutines started by it won't be canceled
 		// when the request finishes.
 		ctx = ctxutil.Detach(ctx)
diff --git a/internal/server/source_manager.go b/internal/server/source_manager.go
index f4a194f8..8f00954d 100644
--- a/internal/server/source_manager.go
+++ b/internal/server/source_manager.go
@@ -157,7 +157,7 @@ func (s *sourceManager) start(ctx context.Context, rep repo.Repository) {
 }
 
 func (s *sourceManager) run(ctx context.Context, isLocal bool) {
-	// make sure we run in a detached context, which ignores outside cancelation and deadline.
+	// make sure we run in a detached context, which ignores outside cancellation and deadline.
 	ctx = ctxutil.Detach(ctx)
 
 	s.setStatus("INITIALIZING")
diff --git a/internal/uitask/uitask.go b/internal/uitask/uitask.go
index e095d124..61313975 100644
--- a/internal/uitask/uitask.go
+++ b/internal/uitask/uitask.go
@@ -100,7 +100,7 @@ func (t *runningTaskInfo) cancel() {
 	if t.Status == StatusRunning {
 		t.Status = StatusCanceling
 		for _, c := range t.taskCancel {
-			// run cancelation functions on their own goroutines
+			// run cancellation functions on their own goroutines
 			go c()
 		}
 
diff --git a/internal/wcmatch/wcmatch.go b/internal/wcmatch/wcmatch.go
index 9b5dfe01..4197985f 100644
--- a/internal/wcmatch/wcmatch.go
+++ b/internal/wcmatch/wcmatch.go
@@ -55,7 +55,7 @@ func (matcher *WildcardMatcher) Pattern() string {
 	return matcher.pattern
 }
 
-// Negated inidicates whether the pattern used by this matcher is a negated pattern, i.e. starts with a '!'.
+// Negated indicates whether the pattern used by this matcher is a negated pattern, i.e. starts with a '!'.
 func (matcher *WildcardMatcher) Negated() bool {
 	return matcher.negated
 }
diff --git a/repo/blob/sftp/sftp_storage.go b/repo/blob/sftp/sftp_storage.go
index fcce7718..04e1f1dd 100644
--- a/repo/blob/sftp/sftp_storage.go
+++ b/repo/blob/sftp/sftp_storage.go
@@ -137,7 +137,7 @@ func (s *sftpImpl) GetBlobFromPath(ctx context.Context, dirPath, fullPath string
 			return iocopy.JustCopy(output, r)
 		}
 
-		// parial read, seek to the provided offset and read given number of bytes.
+		// partial read, seek to the provided offset and read given number of bytes.
 		if _, err = r.Seek(offset, io.SeekStart); err != nil {
 			return errors.Wrapf(blob.ErrInvalidRange, "seek error: %v", err)
 		}
diff --git a/repo/blob/storage.go b/repo/blob/storage.go
index 10d7eb2d..dd9a0d51 100644
--- a/repo/blob/storage.go
+++ b/repo/blob/storage.go
@@ -129,7 +129,7 @@ type PutOptions struct {
 }
 
 // HasRetentionOptions returns true when blob-retention settings have been
-// specified, otherwise retruns false.
+// specified, otherwise returns false.
 func (o PutOptions) HasRetentionOptions() bool {
 	return o.RetentionPeriod != 0 || o.RetentionMode != ""
 }
@@ -290,7 +290,7 @@ func MinTimestamp(mds []Metadata) time.Time {
 	return min
 }
 
-// MaxTimestamp returns maxinum timestamp for blobs in Metadata slice.
+// MaxTimestamp returns maximum timestamp for blobs in Metadata slice.
 func MaxTimestamp(mds []Metadata) time.Time {
 	max := time.Time{}
 
diff --git a/repo/content/committed_read_manager.go b/repo/content/committed_read_manager.go
index bddac324..4c3b940c 100644
--- a/repo/content/committed_read_manager.go
+++ b/repo/content/committed_read_manager.go
@@ -84,7 +84,7 @@ type SharedManager struct {
 	committedContents *committedContentIndex
 	timeNow           func() time.Time
 
-	// lock to protect the set of commtited indexes
+	// lock to protect the set of committed indexes
 	// shared lock will be acquired when writing new content to allow it to happen in parallel
 	// exclusive lock will be acquired during compaction or refresh.
 	indexesLock sync.RWMutex
diff --git a/repo/content/content_manager.go b/repo/content/content_manager.go
index 92eee98c..5d66695c 100644
--- a/repo/content/content_manager.go
+++ b/repo/content/content_manager.go
@@ -356,7 +356,7 @@ func (bm *WriteManager) DisableIndexFlush(ctx context.Context) {
 }
 
 // EnableIndexFlush decrements the counter preventing automatic index flushes.
-// The flushes will be reenabled when the index drops to zero.
+// The flushes will be re-enabled when the index drops to zero.
 func (bm *WriteManager) EnableIndexFlush(ctx context.Context) {
 	bm.lock()
 	defer bm.unlock()
@@ -655,7 +655,7 @@ func (bm *WriteManager) RewriteContent(ctx context.Context, contentID ID) error
 }
 
 func (bm *WriteManager) getContentDataAndInfo(ctx context.Context, contentID ID, output *gather.WriteBuffer) (Info, error) {
-	// acquire read lock since to preven flush from happening between getContentInfoReadLocked() and getContentDataReadLocked().
+	// acquire read lock since to prevent flush from happening between getContentInfoReadLocked() and getContentDataReadLocked().
 	bm.mu.RLock()
 	defer bm.mu.RUnlock()
 
diff --git a/repo/content/index/index_v2.go b/repo/content/index/index_v2.go
index 96255cde..31f47baa 100644
--- a/repo/content/index/index_v2.go
+++ b/repo/content/index/index_v2.go
@@ -452,7 +452,7 @@ func buildUniqueFormatToIndexMap(sortedInfos []Info) map[indexV2FormatInfo]byte
 	return result
 }
 
-// buildPackIDToIndexMap builds a map of unqiue blob IDs to their numeric identifiers.
+// buildPackIDToIndexMap builds a map of unique blob IDs to their numeric identifiers.
 func buildPackIDToIndexMap(sortedInfos []Info) map[blob.ID]int {
 	result := map[blob.ID]int{}
 
diff --git a/repo/content/indexblob/index_blob_manager_v0.go b/repo/content/indexblob/index_blob_manager_v0.go
index b6e7e428..d7a103f5 100644
--- a/repo/content/indexblob/index_blob_manager_v0.go
+++ b/repo/content/indexblob/index_blob_manager_v0.go
@@ -305,7 +305,7 @@ func (m *ManagerV0) deleteOldBlobs(ctx context.Context, latestBlob blob.Metadata
 	indexBlobsToDelete := m.findIndexBlobsToDelete(latestBlob.Timestamp, compactionBlobEntries, maxEventualConsistencySettleTime)
 
 	// note that we must always delete index blobs first before compaction logs
-	// otherwise we may inadvertedly resurrect an index blob that should have been removed.
+	// otherwise we may inadvertently resurrect an index blob that should have been removed.
 	if err := m.deleteBlobsFromStorageAndCache(ctx, indexBlobsToDelete); err != nil {
 		return errors.Wrap(err, "unable to delete compaction logs")
 	}
diff --git a/repo/format/upgrade_lock.go b/repo/format/upgrade_lock.go
index 85712f95..89355ff3 100644
--- a/repo/format/upgrade_lock.go
+++ b/repo/format/upgrade_lock.go
@@ -89,7 +89,7 @@ func WriteLegacyIndexPoisonBlob(ctx context.Context, st blob.Storage) error {
 }
 
 // CommitUpgrade removes the upgrade lock from the from the repository format
-// blob. This in-effect commits the new repository format t othe repository and
+// blob. This in-effect commits the new repository format to the repository and
 // resumes all access to the repository.
 func (m *Manager) CommitUpgrade(ctx context.Context) error {
 	if err := m.maybeRefreshNotLocked(); err != nil {
diff --git a/repo/maintenance/maintenance_run.go b/repo/maintenance/maintenance_run.go
index 25394cd0..1e08cb1c 100644
--- a/repo/maintenance/maintenance_run.go
+++ b/repo/maintenance/maintenance_run.go
@@ -20,7 +20,7 @@
 
 const maxClockSkew = 5 * time.Minute
 
-// Mode describes the mode of maintenance to perfor.
+// Mode describes the mode of maintenance to perform.
 type Mode string
 
 // Supported maintenance modes.
diff --git a/repo/object/object_manager.go b/repo/object/object_manager.go
index ce3341cc..681320f1 100644
--- a/repo/object/object_manager.go
+++ b/repo/object/object_manager.go
@@ -90,7 +90,7 @@ func (om *Manager) closedWriter(ow *objectWriter) {
 //
 // For example when uploading a 100 GB file it is beneficial to independently upload sections of [0..25GB),
 // [25..50GB), [50GB..75GB) and [75GB..100GB) and concatenate them together as this allows us to run four splitters
-// in parallel utilizing more CPU cores. Because some split points now start at fixed bounaries and not content-specific,
+// in parallel utilizing more CPU cores. Because some split points now start at fixed boundaries and not content-specific,
 // this causes some slight loss of deduplication at concatenation points (typically 1-2 contents, usually <10MB),
 // so this method should only be used for very large files where this overhead is relatively small.
 func (om *Manager) Concatenate(ctx context.Context, objectIDs []ID) (ID, error) {
diff --git a/site/content/docs/Advanced/Architecture/_index.md b/site/content/docs/Advanced/Architecture/_index.md
index 444daaae..52206748 100644
--- a/site/content/docs/Advanced/Architecture/_index.md
+++ b/site/content/docs/Advanced/Architecture/_index.md
@@ -62,7 +62,7 @@ Object IDs can also have an optional single-letter prefix `g..z` that helps quic
 
 Objects without prefixes are stored in the `p` pack while objects with prefixes are stored in the `q` metadata pack by the CABS layer.
 
-To represent objects larger than the size of a single CABS block, Kopia links together multiple blocks via special indirect JSON content. Such blocks are distinguished from regular blocks the `I` virtual prefix. Indirection can be applied to any object type, including metadata such as directory listing. However, when applied to a data block, an `x` prefix is added to ensure than the indrect JSON is stored in a metadata pack instead of a data pack by the CABS layer.
+To represent objects larger than the size of a single CABS block, Kopia links together multiple blocks via special indirect JSON content. Such blocks are distinguished from regular blocks the `I` virtual prefix. Indirection can be applied to any object type, including metadata such as directory listing. However, when applied to a data block, an `x` prefix is added to ensure than the indirect JSON is stored in a metadata pack instead of a data pack by the CABS layer.
 
 For example large file object might have an identifier such as `Ixac47f7ce280fdd81f04c670fec2353dc` and JSON content:
 
@@ -92,7 +92,7 @@ $ kopia content show xac47f7ce280fdd81f04c670fec2353dc
 $ kopia content show Ixac47f7ce280fdd81f04c670fec2353dc
 ```
 
-Note that the example above shows that `I` is a virtual prefix. The actual CABS block does not content the `I` prefix but referencing the block with the `I` prefix tells Kopia to interprete the block as an indirect JSON object and return the resolved content instead of the raw content.
+Note that the example above shows that `I` is a virtual prefix. The actual CABS block does not content the `I` prefix but referencing the block with the `I` prefix tells Kopia to interpret the block as an indirect JSON object and return the resolved content instead of the raw content.
 
 The API for CAOS can be found in https://godoc.org/github.com/kopia/kopia/repo/object
 
diff --git a/site/content/docs/Installation/_index.md b/site/content/docs/Installation/_index.md
index ce4bf09e..e7402f29 100644
--- a/site/content/docs/Installation/_index.md
+++ b/site/content/docs/Installation/_index.md
@@ -6,7 +6,7 @@ weight: 30
 
 ### Two Variants of Kopia
 
-Kopia is a standalone binary and can be used through a command-line interface (CLI) or a graphical user inferace (GUI). 
+Kopia is a standalone binary and can be used through a command-line interface (CLI) or a graphical user interface (GUI). 
 
 * If you want to use Kopia via CLI, you will install the `kopia` binary; when you want to use Kopia, you will call the `kopia` binary (along with [Kopia commands](../reference/command-line/)) in a terminal/command prompt window or within a script. 
 
@@ -20,7 +20,7 @@ Kopia CLI is recommended only if you are comfortable with command-line interface
 
 ### Kopia Download Links
 
-The following installation options are available for the latest stable verison of Kopia:
+The following installation options are available for the latest stable version of Kopia:
 
 * [Official Releases](https://github.com/kopia/kopia/releases/latest)
 * [Windows CLI (Scoop)](#windows-cli-installation-using-scoop)
diff --git a/site/content/docs/Repositories/_index.md b/site/content/docs/Repositories/_index.md
index 2aa7fe48..2ca76514 100644
--- a/site/content/docs/Repositories/_index.md
+++ b/site/content/docs/Repositories/_index.md
@@ -21,7 +21,7 @@ Kopia allows you to save your [encrypted](../features/#end-to-end-zero-knowledge
 * All remote servers or cloud storage that support [WebDAV](#webdav) 
 * All remote servers or cloud storage that support [SFTP](#sftp)
 * Some of the cloud storages supported by [Rclone](#rclone) 
-  * Rclone is a (free and open-source) third-party program that you must download and setup seperately before you can use it with Kopia
+  * Rclone is a (free and open-source) third-party program that you must download and setup separately before you can use it with Kopia
   * Once you setup Rclone, Kopia automatically manages and runs Rclone for you, so you do not need to do much beyond the initial setup, aside from enabling Rclone's self-update feature so that it stays up-to-date
   * Kopia's Rclone support is experimental: not all the cloud storages supported by Rclone have been tested to work with Kopia, and some may not work with Kopia; Kopia has been tested to work with [Dropbox](#rclone), [OneDrive](#rclone), and [Google Drive](#rclone) through Rclone
 * Your local machine and any network-attached storage or server 
@@ -39,7 +39,7 @@ Select the `Amazon S3 and Compatible Storage` option in the `Repository` tab in
 
 > NOTE: Some S3-compatible cloud storage may have slightly different names for bucket, endpoint, access key, secret key, region, and session token. This will vary between cloud storages. Read the help documentation for the cloud storage you are using to find the appropriate values. You can typically find this information by searching for the S3 API settings for your cloud storage.
 
-You will next need to enter the repository password that you want. Remember, this [password is used to encrypt your data](../faqs/#how-do-i-enable-encryption), so make sure it is a secure password! At this same password screen, you have the option to change the `Encryption` algoirthm, `Hash` algorithm, `Splitter` algorithm, `Repository Format`, `Username`, and `Hostname`. Click the `Show Advanced Options` button to access these settings. If you do not understand what these settings are, do not change them because the default settings are the best settings.
+You will next need to enter the repository password that you want. Remember, this [password is used to encrypt your data](../faqs/#how-do-i-enable-encryption), so make sure it is a secure password! At this same password screen, you have the option to change the `Encryption` algorithm, `Hash` algorithm, `Splitter` algorithm, `Repository Format`, `Username`, and `Hostname`. Click the `Show Advanced Options` button to access these settings. If you do not understand what these settings are, do not change them because the default settings are the best settings.
 
 > NOTE: Some settings, such as object locking and [actions](../advanced/actions/), can only be enabled when you create a new `repository` using command-line (see next section). However, once you create the `repository` via command-line, you can use the `repository` as normal in Kopia GUI: just connect to the `repository` as described above after you have created it in command-line.
 
@@ -74,7 +74,7 @@ Creating an Azure Blob Storage `repository` is done differently depending on if
 
 Select the `Azure Blob Storage` option in the `Repository` tab in `KopiaUI`. Then, follow on-screen instructions.  You will need to enter `Container` name, `Storage Account` name and either `Access Key` or `SAS Token`. You can optionally enter an `Azure Storage Domain`.
 
-You will next need to enter the repository password that you want. Remember, this [password is used to encrypt your data](../faqs/#how-do-i-enable-encryption), so make sure it is a secure password! At this same password screen, you have the option to change the `Encryption` algoirthm, `Hash` algorithm, `Splitter` algorithm, `Repository Format`, `Username`, and `Hostname`. Click the `Show Advanced Options` button to access these settings. If you do not understand what these settings are, do not change them because the default settings are the best settings.
+You will next need to enter the repository password that you want. Remember, this [password is used to encrypt your data](../faqs/#how-do-i-enable-encryption), so make sure it is a secure password! At this same password screen, you have the option to change the `Encryption` algorithm, `Hash` algorithm, `Splitter` algorithm, `Repository Format`, `Username`, and `Hostname`. Click the `Show Advanced Options` button to access these settings. If you do not understand what these settings are, do not change them because the default settings are the best settings.
 
 > NOTE: Some settings, such as [actions](../advanced/actions/), can only be enabled when you create a new `repository` using command-line (see next section). However, once you create the `repository` via command-line, you can use the `repository` as normal in Kopia GUI: just connect to the `repository` as described above after you have created it in command-line.
 
@@ -118,9 +118,9 @@ Creating a Backblaze B2 `repository` is done differently depending on if you use
 
 ### Kopia GUI
 
-Select the `Backblaze B2` option in the `Repository` tab in `KopiaUI`. Then, follow on-screen instructions.  You will need to enter `B2 Bucket` name, `Key ID`, and appliation `Key`.
+Select the `Backblaze B2` option in the `Repository` tab in `KopiaUI`. Then, follow on-screen instructions.  You will need to enter `B2 Bucket` name, `Key ID`, and application `Key`.
 
-You will next need to enter the repository password that you want. Remember, this [password is used to encrypt your data](../faqs/#how-do-i-enable-encryption), so make sure it is a secure password! At this same password screen, you have the option to change the `Encryption` algoirthm, `Hash` algorithm, `Splitter` algorithm, `Repository Format`, `Username`, and `Hostname`. Click the `Show Advanced Options` button to access these settings. If you do not understand what these settings are, do not change them because the default settings are the best settings.
+You will next need to enter the repository password that you want. Remember, this [password is used to encrypt your data](../faqs/#how-do-i-enable-encryption), so make sure it is a secure password! At this same password screen, you have the option to change the `Encryption` algorithm, `Hash` algorithm, `Splitter` algorithm, `Repository Format`, `Username`, and `Hostname`. Click the `Show Advanced Options` button to access these settings. If you do not understand what these settings are, do not change them because the default settings are the best settings.
 
 > NOTE: Some settings, such as [actions](../advanced/actions/), can only be enabled when you create a new `repository` using command-line (see next section). However, once you create the `repository` via command-line, you can use the `repository` as normal in Kopia GUI: just connect to the `repository` as described above after you have created it in command-line.
 
@@ -155,7 +155,7 @@ Creating a Google Cloud Storage `repository` is done differently depending on if
 
 Select the `Google Cloud Storage` option in the `Repository` tab in `KopiaUI`. Then, follow on-screen instructions.  You will need to enter the `GCS Bucket` name and enter the path to where on your machine you have saved the Google Cloud Storage `Credentials File`. The credentials file can be obtained by [creating a Google Cloud Service Account](https://cloud.google.com/docs/authentication/getting-started#create-service-account-console) that allows you to access your storage bucket and then downloading the JSON key file for that service account. You enter the path to this JSON key file in the `Credentials File` textbox in `KopiaUI`.
 
-You will next need to enter the repository password that you want. Remember, this [password is used to encrypt your data](../faqs/#how-do-i-enable-encryption), so make sure it is a secure password! At this same password screen, you have the option to change the `Encryption` algoirthm, `Hash` algorithm, `Splitter` algorithm, `Repository Format`, `Username`, and `Hostname`. Click the `Show Advanced Options` button to access these settings. If you do not understand what these settings are, do not change them because the default settings are the best settings.
+You will next need to enter the repository password that you want. Remember, this [password is used to encrypt your data](../faqs/#how-do-i-enable-encryption), so make sure it is a secure password! At this same password screen, you have the option to change the `Encryption` algorithm, `Hash` algorithm, `Splitter` algorithm, `Repository Format`, `Username`, and `Hostname`. Click the `Show Advanced Options` button to access these settings. If you do not understand what these settings are, do not change them because the default settings are the best settings.
 
 > NOTE: Some settings, such as [actions](../advanced/actions/), can only be enabled when you create a new `repository` using command-line (see next section). However, once you create the `repository` via command-line, you can use the `repository` as normal in Kopia GUI: just connect to the `repository` as described above after you have created it in command-line.
 
@@ -284,7 +284,7 @@ Creating a WebDAV `repository` is done differently depending on if you use Kopia
 
 Select the `WebDAV Server` option in the `Repository` tab in `KopiaUI`. Then, follow on-screen instructions.  You will need to enter `WebDAV Server URL`, `Username`, and `Password.
 
-You will next need to enter the repository password that you want. This password can be whatever you want, it does not need to be the same as your WebDAV password. In fact, it should not be the same! Remember, this [password is used to encrypt your data](../faqs/#how-do-i-enable-encryption), so make sure it is a secure password! At this same password screen, you have the option to change the `Encryption` algoirthm, `Hash` algorithm, `Splitter` algorithm, `Repository Format`, `Username`, and `Hostname`. Click the `Show Advanced Options` button to access these settings. If you do not understand what these settings are, do not change them because the default settings are the best settings.
+You will next need to enter the repository password that you want. This password can be whatever you want, it does not need to be the same as your WebDAV password. In fact, it should not be the same! Remember, this [password is used to encrypt your data](../faqs/#how-do-i-enable-encryption), so make sure it is a secure password! At this same password screen, you have the option to change the `Encryption` algorithm, `Hash` algorithm, `Splitter` algorithm, `Repository Format`, `Username`, and `Hostname`. Click the `Show Advanced Options` button to access these settings. If you do not understand what these settings are, do not change them because the default settings are the best settings.
 
 > NOTE: Some settings, such as [actions](../advanced/actions/), can only be enabled when you create a new `repository` using command-line (see next section). However, once you create the `repository` via command-line, you can use the `repository` as normal in Kopia GUI: just connect to the `repository` as described above after you have created it in command-line.
 
@@ -322,7 +322,7 @@ Select the `SFTP Server` option in the `Repository` tab in `KopiaUI`. Then, foll
 
 If the connection to SFTP server does not work, checking the option for `Launch external password-less SSH command` which will launch an external `ssh` process  that supports more connectivity options and may be needed for some hosts.
 
-You will next need to enter the repository password that you want. This password can be whatever you want, it does not need to be the same as your SFTP password. In fact, it should not be the same! Remember, this [password is used to encrypt your data](../faqs/#how-do-i-enable-encryption), so make sure it is a secure password! At this same password screen, you have the option to change the `Encryption` algoirthm, `Hash` algorithm, `Splitter` algorithm, `Repository Format`, `Username`, and `Hostname`. Click the `Show Advanced Options` button to access these settings. If you do not understand what these settings are, do not change them because the default settings are the best settings.
+You will next need to enter the repository password that you want. This password can be whatever you want, it does not need to be the same as your SFTP password. In fact, it should not be the same! Remember, this [password is used to encrypt your data](../faqs/#how-do-i-enable-encryption), so make sure it is a secure password! At this same password screen, you have the option to change the `Encryption` algorithm, `Hash` algorithm, `Splitter` algorithm, `Repository Format`, `Username`, and `Hostname`. Click the `Show Advanced Options` button to access these settings. If you do not understand what these settings are, do not change them because the default settings are the best settings.
 
 > NOTE: Some settings, such as [actions](../advanced/actions/), can only be enabled when you create a new `repository` using command-line (see next section). However, once you create the `repository` via command-line, you can use the `repository` as normal in Kopia GUI: just connect to the `repository` as described above after you have created it in command-line.
 
@@ -377,7 +377,7 @@ Before you can create an Rclone `repository` in Kopia, you first need to downloa
 
 Select the `Rclone Remote` option in the `Repository` tab in `KopiaUI`. Then, follow on-screen instructions.  You will need to enter `Rcone Remote Path` and `Rclone Executable Path`. The `Remote Path` is `my-remote:/some/path`, where you should replace `my-remote` with the name of the Rclone `remote` you created earlier and replace `/some/path` with the directory on the cloud storage where you want Kopia to save your snapshots. The `Executable Path` is the location on your machine where you saved the Rclone executable that you downloaded earlier.
 
-You will next need to enter the repository password that you want. Remember, this [password is used to encrypt your data](../faqs/#how-do-i-enable-encryption), so make sure it is a secure password! At this same password screen, you have the option to change the `Encryption` algoirthm, `Hash` algorithm, `Splitter` algorithm, `Repository Format`, `Username`, and `Hostname`. Click the `Show Advanced Options` button to access these settings. If you do not understand what these settings are, do not change them because the default settings are the best settings.
+You will next need to enter the repository password that you want. Remember, this [password is used to encrypt your data](../faqs/#how-do-i-enable-encryption), so make sure it is a secure password! At this same password screen, you have the option to change the `Encryption` algorithm, `Hash` algorithm, `Splitter` algorithm, `Repository Format`, `Username`, and `Hostname`. Click the `Show Advanced Options` button to access these settings. If you do not understand what these settings are, do not change them because the default settings are the best settings.
 
 > NOTE: Some settings, such as [actions](../advanced/actions/) and arguments to Rclone, can only be enabled when you create a new `repository` using command-line (see next section). However, once you create the `repository` via command-line, you can use the `repository` as normal in Kopia GUI: just connect to the `repository` as described above after you have created it in command-line.
 
@@ -413,7 +413,7 @@ Creating a filesystem `repository` is done differently depending on if you use K
 
 Select the `Local Directory or NAS` option in the `Repository` tab in `KopiaUI`. Then, follow on-screen instructions.  You will need to enter `Directory Path`.
 
-You will next need to enter the repository password that you want. Remember, this [password is used to encrypt your data](../faqs/#how-do-i-enable-encryption), so make sure it is a secure password! At this same password screen, you have the option to change the `Encryption` algoirthm, `Hash` algorithm, `Splitter` algorithm, `Repository Format`, `Username`, and `Hostname`. Click the `Show Advanced Options` button to access these settings. If you do not understand what these settings are, do not change them because the default settings are the best settings.
+You will next need to enter the repository password that you want. Remember, this [password is used to encrypt your data](../faqs/#how-do-i-enable-encryption), so make sure it is a secure password! At this same password screen, you have the option to change the `Encryption` algorithm, `Hash` algorithm, `Splitter` algorithm, `Repository Format`, `Username`, and `Hostname`. Click the `Show Advanced Options` button to access these settings. If you do not understand what these settings are, do not change them because the default settings are the best settings.
 
 > NOTE: Some settings, such as [actions](../advanced/actions/), can only be enabled when you create a new `repository` using command-line (see next section). However, once you create the `repository` via command-line, you can use the `repository` as normal in Kopia GUI: just connect to the `repository` as described above after you have created it in command-line.
 
diff --git a/snapshot/restore/restore.go b/snapshot/restore/restore.go
index ab3764e2..e8c1e7a8 100644
--- a/snapshot/restore/restore.go
+++ b/snapshot/restore/restore.go
@@ -89,7 +89,7 @@ type Options struct {
 	MinSizeForPlaceholder  int32 `json:"minSizeForPlaceholder"`
 
 	ProgressCallback func(ctx context.Context, s Stats) `json:"-"`
-	Cancel           chan struct{}                      `json:"-"` // channel that can be externally closed to signal cancelation
+	Cancel           chan struct{}                      `json:"-"` // channel that can be externally closed to signal cancellation
 }
 
 // Entry walks a snapshot root with given root entry and restores it to the provided output.
diff --git a/snapshot/snapshotfs/objref.go b/snapshot/snapshotfs/objref.go
index 6284074e..d44b9980 100644
--- a/snapshot/snapshotfs/objref.go
+++ b/snapshot/snapshotfs/objref.go
@@ -93,7 +93,7 @@ func findSnapshotByRootObjectIDOrManifestID(ctx context.Context, rep repo.Reposi
 
 	mans, err := snapshot.FindSnapshotsByRootObjectID(ctx, rep, rootOID)
 	if err != nil {
-		return nil, errors.Wrapf(err, "unable to find shapshots by ID %v", rootID)
+		return nil, errors.Wrapf(err, "unable to find snapshots by ID %v", rootID)
 	}
 
 	// no matching snapshots.
diff --git a/snapshot/snapshotfs/upload.go b/snapshot/snapshotfs/upload.go
index e08bcb8e..d615eb00 100644
--- a/snapshot/snapshotfs/upload.go
+++ b/snapshot/snapshotfs/upload.go
@@ -342,6 +342,8 @@ func (u *Uploader) uploadStreamingFileInternal(ctx context.Context, relativePath
 		return nil, errors.Wrap(err, "unable to get streaming file reader")
 	}
 
+	defer reader.Close() //nolint:errcheck
+
 	var streamSize int64
 
 	u.Progress.HashingFile(relativePath)
@@ -463,6 +465,22 @@ func newDirEntry(md fs.Entry, fname string, oid object.ID) (*snapshot.DirEntry,
 	}, nil
 }
 
+// newCachedDirEntry makes DirEntry objects for entries that are also in
+// previous snapshots. It ensures file sizes are populated correctly for
+// StreamingFiles.
+func newCachedDirEntry(md, cached fs.Entry, fname string) (*snapshot.DirEntry, error) {
+	hoid, ok := cached.(object.HasObjectID)
+	if !ok {
+		return nil, errors.New("cached entry does not implement HasObjectID")
+	}
+
+	if _, ok := md.(fs.StreamingFile); ok {
+		return newDirEntry(cached, fname, hoid.ObjectID())
+	}
+
+	return newDirEntry(md, fname, hoid.ObjectID())
+}
+
 // uploadFileWithCheckpointing uploads the specified File to the repository.
 func (u *Uploader) uploadFileWithCheckpointing(ctx context.Context, relativePath string, file fs.File, pol *policy.Policy, sourceInfo snapshot.SourceInfo) (*snapshot.DirEntry, error) {
 	var cp checkpointRegistry
@@ -527,7 +545,7 @@ func (u *Uploader) checkpointRoot(ctx context.Context, cp *checkpointRegistry, p
 }
 
 // periodicallyCheckpoint periodically (every CheckpointInterval) invokes checkpointRoot until the
-// returned cancelation function has been called.
+// returned cancellation function has been called.
 func (u *Uploader) periodicallyCheckpoint(ctx context.Context, cp *checkpointRegistry, prototypeManifest *snapshot.Manifest) (cancelFunc func()) {
 	shutdown := make(chan struct{})
 	ch := u.getTicker(u.CheckpointInterval)
@@ -627,7 +645,7 @@ func (u *Uploader) processChildren(
 	defer wg.Close()
 
 	// ignore errCancel because a more serious error may be reported in wg.Wait()
-	// we'll check for cancelation later.
+	// we'll check for cancellation later.
 
 	if err := u.processDirectoryEntries(ctx, parentDirCheckpointRegistry, parentDirBuilder, localDirPathOrEmpty, relativePath, dir, policyTree, previousDirs, &wg); err != nil && !errors.Is(err, errCanceled) {
 		return err
@@ -809,11 +827,11 @@ func (u *Uploader) processSingle(
 		// See if we had this name during either of previous passes.
 		if cachedEntry := u.maybeIgnoreCachedEntry(ctx, findCachedEntry(ctx, entryRelativePath, entry, prevDirs, policyTree)); cachedEntry != nil {
 			atomic.AddInt32(&u.stats.CachedFiles, 1)
-			atomic.AddInt64(&u.stats.TotalFileSize, entry.Size())
-			u.Progress.CachedFile(entryRelativePath, entry.Size())
+			atomic.AddInt64(&u.stats.TotalFileSize, cachedEntry.Size())
+			u.Progress.CachedFile(entryRelativePath, cachedEntry.Size())
+
+			cachedDirEntry, err := newCachedDirEntry(entry, cachedEntry, entry.Name())
 
-			// compute entryResult now, cachedEntry is short-lived
-			cachedDirEntry, err := newDirEntry(entry, entry.Name(), cachedEntry.(object.HasObjectID).ObjectID())
 			u.Progress.FinishedFile(entryRelativePath, err)
 
 			if err != nil {
@@ -1140,7 +1158,7 @@ func uploadDirInternal(
 
 func (u *Uploader) reportErrorAndMaybeCancel(err error, isIgnored bool, dmb *DirManifestBuilder, entryRelativePath string) {
 	if u.IsCanceled() && errors.Is(err, errCanceled) {
-		// alrady canceled, do not report another.
+		// already canceled, do not report another.
 		return
 	}
 


Test patch:
diff --git a/fs/virtualfs/virtualfs_test.go b/fs/virtualfs/virtualfs_test.go
index 041030de..5ed2100b 100644
--- a/fs/virtualfs/virtualfs_test.go
+++ b/fs/virtualfs/virtualfs_test.go
@@ -4,6 +4,7 @@
 	"bytes"
 	"context"
 	"errors"
+	"io"
 	"os"
 	"reflect"
 	"testing"
@@ -76,9 +77,9 @@ func TestStreamingFile(t *testing.T) {
 
 func TestStreamingFileModTime(t *testing.T) {
 	data := []byte("data")
-	f1 := StreamingFileFromReader("f1", bytes.NewReader(data))
+	f1 := StreamingFileFromReader("f1", io.NopCloser(bytes.NewReader(data)))
 	mt := time.Date(2021, 1, 2, 3, 4, 5, 0, time.UTC)
-	f2 := StreamingFileWithModTimeFromReader("f2", mt, bytes.NewReader(data))
+	f2 := StreamingFileWithModTimeFromReader("f2", mt, io.NopCloser(bytes.NewReader(data)))
 
 	assert.True(t, f1.ModTime().After(f2.ModTime()))
 }
@@ -130,7 +131,7 @@ func TestStreamingFileGetReader(t *testing.T) {
 func TestStreamingDirectory(t *testing.T) {
 	// Create a temporary file with test data
 	content := []byte("Temporary file content")
-	r := bytes.NewReader(content)
+	r := io.NopCloser(bytes.NewReader(content))
 
 	f := StreamingFileFromReader(testFileName, r)
 
@@ -167,7 +168,7 @@ func(
 func TestStreamingDirectory_MultipleIterationsFails(t *testing.T) {
 	// Create a temporary file with test data
 	content := []byte("Temporary file content")
-	r := bytes.NewReader(content)
+	r := io.NopCloser(bytes.NewReader(content))
 
 	f := StreamingFileFromReader(testFileName, r)
 
@@ -195,7 +196,7 @@ func(
 func TestStreamingDirectory_ReturnsCallbackError(t *testing.T) {
 	// Create a temporary file with test data
 	content := []byte("Temporary file content")
-	r := bytes.NewReader(content)
+	r := io.NopCloser(bytes.NewReader(content))
 
 	f := StreamingFileFromReader(testFileName, r)
 
diff --git a/internal/blobtesting/object_locking_map.go b/internal/blobtesting/object_locking_map.go
index 5ce164b8..5708cb59 100644
--- a/internal/blobtesting/object_locking_map.go
+++ b/internal/blobtesting/object_locking_map.go
@@ -26,7 +26,7 @@ type entry struct {
 // objectLockingMap is an in-memory versioned object store which maintains
 // historical versions of each blob on every put. Deletes use a delete-marker
 // overlay mechanism and lists will avoid entries if their latest object is a
-// marker. This struct manages the retention time of each blob throug hte
+// marker. This struct manages the retention time of each blob through the
 // PutBlob options.
 type objectLockingMap struct {
 	// +checklocks:mutex
diff --git a/internal/clock/now_testing.go b/internal/clock/now_testing.go
index 2862451d..f0e6d258 100644
--- a/internal/clock/now_testing.go
+++ b/internal/clock/now_testing.go
@@ -29,7 +29,7 @@ func init() {
 }
 
 // getTimeFromServer returns a function that will return timestamp as returned by the server
-// increasing it client-side by certain inteval until maximum is reached, at which point
+// increasing it client-side by certain interval until maximum is reached, at which point
 // it will ask the server again for new timestamp.
 //
 // The server endpoint must be HTTP and be set using KOPIA_FAKE_CLOCK_ENDPOINT environment
diff --git a/internal/repotesting/repotesting.go b/internal/repotesting/repotesting.go
index 2b99daec..29d4c37c 100644
--- a/internal/repotesting/repotesting.go
+++ b/internal/repotesting/repotesting.go
@@ -164,7 +164,7 @@ func (e *Environment) MustReopen(tb testing.TB, openOpts ...func(*repo.Options))
 		tb.Fatalf("close error: %v", err)
 	}
 
-	// ensure context passed to Open() is not used for cancelation signal.
+	// ensure context passed to Open() is not used for cancellation signal.
 	ctx2, cancel := context.WithCancel(ctx)
 	defer cancel()
 
diff --git a/internal/uitask/uitask_test.go b/internal/uitask/uitask_test.go
index cc06ed60..07833f09 100644
--- a/internal/uitask/uitask_test.go
+++ b/internal/uitask/uitask_test.go
@@ -242,7 +242,7 @@ func TestUITaskCancel_AfterOnCancel(t *testing.T) {
 
 		time.Sleep(time.Second)
 
-		t.Logf("requesting cancelation of %v", childTaskID)
+		t.Logf("requesting cancellation of %v", childTaskID)
 		m.CancelTask(childTaskID)
 	}()
 
diff --git a/repo/blob/s3/s3_storage_test.go b/repo/blob/s3/s3_storage_test.go
index bd19711a..2a23257e 100644
--- a/repo/blob/s3/s3_storage_test.go
+++ b/repo/blob/s3/s3_storage_test.go
@@ -37,7 +37,7 @@
 const (
 	// https://github.com/minio/minio-go
 
-	// fake creadentials used by minio server we're launching.
+	// fake credentials used by minio server we're launching.
 	minioRootAccessKeyID     = "fake-key"
 	minioRootSecretAccessKey = "fake-secret"
 	minioRegion              = "fake-region-1"
diff --git a/repo/format/upgrade_lock_test.go b/repo/format/upgrade_lock_test.go
index 9deb21a4..5f266b20 100644
--- a/repo/format/upgrade_lock_test.go
+++ b/repo/format/upgrade_lock_test.go
@@ -329,7 +329,7 @@ func TestFormatUpgradeDuringOngoingWriteSessions(t *testing.T) {
 
 	lw := rep.(repo.RepositoryWriter)
 
-	// w1, w2, w3 are indepdendent sessions.
+	// w1, w2, w3 are independent sessions.
 	_, w1, err := rep.NewWriter(ctx, repo.WriteSessionOptions{Purpose: "writer1"})
 	require.NoError(t, err)
 
diff --git a/repo/repository_test.go b/repo/repository_test.go
index b7f42e6c..65020353 100644
--- a/repo/repository_test.go
+++ b/repo/repository_test.go
@@ -311,7 +311,7 @@ func TestWriterScope(t *testing.T) {
 
 	lw := rep.(repo.RepositoryWriter)
 
-	// w1, w2, w3 are indepdendent sessions.
+	// w1, w2, w3 are independent sessions.
 	_, w1, err := rep.NewWriter(ctx, repo.WriteSessionOptions{Purpose: "writer1"})
 	require.NoError(t, err)
 
diff --git a/snapshot/snapshotfs/upload_test.go b/snapshot/snapshotfs/upload_test.go
index 9ef7d562..fbb026c8 100644
--- a/snapshot/snapshotfs/upload_test.go
+++ b/snapshot/snapshotfs/upload_test.go
@@ -886,7 +886,7 @@ func TestUpload_VirtualDirectoryWithStreamingFileWithModTime(t *testing.T) {
 		{
 			desc: "CurrentTime",
 			getFile: func() fs.StreamingFile {
-				return virtualfs.StreamingFileFromReader("a", bytes.NewReader(content))
+				return virtualfs.StreamingFileFromReader("a", io.NopCloser(bytes.NewReader(content)))
 			},
 			cachedFiles:   0,
 			uploadedFiles: 1,
@@ -894,7 +894,7 @@ func TestUpload_VirtualDirectoryWithStreamingFileWithModTime(t *testing.T) {
 		{
 			desc: "FixedTime",
 			getFile: func() fs.StreamingFile {
-				return virtualfs.StreamingFileWithModTimeFromReader("a", mt, bytes.NewReader(content))
+				return virtualfs.StreamingFileWithModTimeFromReader("a", mt, io.NopCloser(bytes.NewReader(content)))
 			},
 			cachedFiles:   1,
 			uploadedFiles: 0,
@@ -923,6 +923,7 @@ func TestUpload_VirtualDirectoryWithStreamingFileWithModTime(t *testing.T) {
 			require.Equal(t, int32(1), atomic.LoadInt32(&man1.Stats.NonCachedFiles))
 			require.Equal(t, int32(1), atomic.LoadInt32(&man1.Stats.TotalDirectoryCount))
 			require.Equal(t, int32(1), atomic.LoadInt32(&man1.Stats.TotalFileCount))
+			require.Equal(t, int64(len(content)), atomic.LoadInt64(&man1.Stats.TotalFileSize))
 
 			// wait a little bit to ensure clock moves forward which is not always the case on Windows.
 			time.Sleep(100 * time.Millisecond)
@@ -941,6 +942,7 @@ func TestUpload_VirtualDirectoryWithStreamingFileWithModTime(t *testing.T) {
 			assert.Equal(t, tc.uploadedFiles, atomic.LoadInt32(&man2.Stats.NonCachedFiles))
 			// Cached files don't count towards the total file count.
 			assert.Equal(t, tc.uploadedFiles, atomic.LoadInt32(&man2.Stats.TotalFileCount))
+			require.Equal(t, int64(len(content)), atomic.LoadInt64(&man2.Stats.TotalFileSize))
 		})
 	}
 }
diff --git a/tests/clitestutil/clitestutil.go b/tests/clitestutil/clitestutil.go
index 799c3184..5095a9c8 100644
--- a/tests/clitestutil/clitestutil.go
+++ b/tests/clitestutil/clitestutil.go
@@ -7,7 +7,7 @@
 	"time"
 )
 
-// SourceInfo reprents a single source (user@host:/path) with its snapshots.
+// SourceInfo represents a single source (user@host:/path) with its snapshots.
 type SourceInfo struct {
 	User      string
 	Host      string
diff --git a/tests/end_to_end_test/restore_test.go b/tests/end_to_end_test/restore_test.go
index c1b456ca..cd122050 100644
--- a/tests/end_to_end_test/restore_test.go
+++ b/tests/end_to_end_test/restore_test.go
@@ -464,7 +464,7 @@ func TestRestoreSnapshotOfSingleFile(t *testing.T) {
 		overriddenFilePermissions := 0o654 | os.ModeSetuid
 
 		// change source file permissions and create one more snapshot
-		// at this poing we will have multiple snapshot manifests with one root but different attributes.
+		// at this point we will have multiple snapshot manifests with one root but different attributes.
 		os.Chmod(sourceFile, overriddenFilePermissions)
 		e.RunAndExpectSuccess(t, "snapshot", "create", sourceFile)
 
diff --git a/tests/end_to_end_test/shallowrestore_test.go b/tests/end_to_end_test/shallowrestore_test.go
index 60fb6a1f..2d2ef9d6 100644
--- a/tests/end_to_end_test/shallowrestore_test.go
+++ b/tests/end_to_end_test/shallowrestore_test.go
@@ -328,7 +328,7 @@ func deepenSubtreeFile(m *mutatorArgs) {
 // deepenOneSubtreeLevel reifies a shallow directory entry with one level
 // of reification. In particular: given a path into a shallow restored
 // tree, we restore a single shallow directory and the directory should
-// become a real (mutable) directory contaning shallow entries.
+// become a real (mutable) directory containing shallow entries.
 // TODO(rjk): generalize the testing of the shallow restoration
 // validation to make sure that the restored directory of this form has
 // the correct form.
@@ -655,7 +655,7 @@ func (rdc *repoDirEntryCache) repoRootRel(t *testing.T, fpath string) string {
 }
 
 // getRepoDirEntry retrieves the directory entry for rdc.rootid/rop via kopia
-// show of the repository rdc.rootid's directory contaning rdc.rootid/rop.
+// show of the repository rdc.rootid's directory containing rdc.rootid/rop.
 // Assumption: repository paths are paths and not filepaths.
 func (rdc *repoDirEntryCache) getRepoDirEntry(t *testing.T, rop string) *snapshot.DirEntry {
 	t.Helper()
diff --git a/tests/perf_benchmark/process_results.go b/tests/perf_benchmark/process_results.go
index 793fc346..ea48f3b6 100644
--- a/tests/perf_benchmark/process_results.go
+++ b/tests/perf_benchmark/process_results.go
@@ -108,7 +108,7 @@ func parseRepoSize(fname string) (int64, error) {
 
 	fields := strings.Fields(s.Text())
 	if len(fields) != 2 {
-		return 0, errors.Errorf("unvalid repo size format")
+		return 0, errors.Errorf("invalid repo size format")
 	}
 
 	return strconv.ParseInt(fields[0], 10, 64)
diff --git a/tests/repository_stress_test/repomodel/repository_data_model.go b/tests/repository_stress_test/repomodel/repository_data_model.go
index 6d23bf67..70c46683 100644
--- a/tests/repository_stress_test/repomodel/repository_data_model.go
+++ b/tests/repository_stress_test/repomodel/repository_data_model.go
@@ -1,4 +1,4 @@
-// Package repomodel provides simplified model of repository operaton.
+// Package repomodel provides simplified model of repository operation.
 package repomodel
 
 import "sync/atomic"
diff --git a/tests/robustness/snapmeta/kopia_persister.go b/tests/robustness/snapmeta/kopia_persister.go
index 10b6555a..39146cfd 100644
--- a/tests/robustness/snapmeta/kopia_persister.go
+++ b/tests/robustness/snapmeta/kopia_persister.go
@@ -116,7 +116,7 @@ func (store *KopiaPersister) LoadMetadata() error {
 	}
 
 	if len(snapIDs) == 0 {
-		return nil // No snapshot IDs fouund in repository
+		return nil // No snapshot IDs found in repository
 	}
 
 	lastSnapID := snapIDs[len(snapIDs)-1]
diff --git a/tests/tools/kopiaclient/kopiaclient.go b/tests/tools/kopiaclient/kopiaclient.go
index 359759ed..2cec7982 100644
--- a/tests/tools/kopiaclient/kopiaclient.go
+++ b/tests/tools/kopiaclient/kopiaclient.go
@@ -210,7 +210,7 @@ func (kc *KopiaClient) getStorage(ctx context.Context, repoDir, bucketName strin
 // reads its contents from `val`.
 func (kc *KopiaClient) getSourceForKeyVal(key string, val []byte) fs.Entry {
 	return virtualfs.NewStaticDirectory(key, []fs.Entry{
-		virtualfs.StreamingFileFromReader(dataFileName, bytes.NewReader(val)),
+		virtualfs.StreamingFileFromReader(dataFileName, io.NopCloser(bytes.NewReader(val))),
 	})
 }