Repository: kopia/kopia
Pull request #2543 (2022-11-02 22:30:27)

fix(repository): Manifest label comparison when value is empty
Allow matching on labels with empty values. Previously, the "empty value" for `map[string]string` ("") would match the empty value of the label key, causing the system to return all manifests. This separates key existence from value equality to handle labels with empty values.

codecov[bot]: # [Codecov](https://codecov.io/gh/kopia/kopia/pull/2543?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kopia) Report
Base: **74.48**% // Head: **74.47**% // Decreases project coverage by **`-0.01%`** :warning:
> Coverage data is based on head [(`b8d2bbc`)](https://codecov.io/gh/kopia/kopia/pull/2543?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kopia) compared to base [(`fd7f4e6`)](https://codecov.io/gh/kopia/kopia/commit/fd7f4e63c7797fff5d89410dd49333af39938717?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    #2543      +/-   ##
==========================================
- Coverage   74.48%   74.47%   -0.02%     
==========================================
  Files         442      442              
  Lines       34891    34891              
==========================================
- Hits        25990    25984       -6     
- Misses       7016     7022       +6     
  Partials     1885     1885              
```


| [Impacted Files](https://codecov.io/gh/kopia/kopia/pull/2543?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kopia) | Coverage Δ | |
|---|---|---|
| [repo/manifest/manifest\_manager.go](https://codecov.io/gh/kopia/kopia/pull/2543/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kopia#diff-cmVwby9tYW5pZmVzdC9tYW5pZmVzdF9tYW5hZ2VyLmdv) | `92.56% <100.00%> (ø)` | |
| [repo/blob/throttling/throttling\_semaphore.go](https://codecov.io/gh/kopia/kopia/pull/2543/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kopia#diff-cmVwby9ibG9iL3Rocm90dGxpbmcvdGhyb3R0bGluZ19zZW1hcGhvcmUuZ28=) | `97.05% <0.00%> (-2.95%)` | :arrow_down: |
| [repo/grpc\_repository\_client.go](https://codecov.io/gh/kopia/kopia/pull/2543/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kopia#diff-cmVwby9ncnBjX3JlcG9zaXRvcnlfY2xpZW50Lmdv) | `76.10% <0.00%> (-0.68%)` | :arrow_down: |
| [internal/server/server.go](https://codecov.io/gh/kopia/kopia/pull/2543/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kopia#diff-aW50ZXJuYWwvc2VydmVyL3NlcnZlci5nbw==) | `72.67% <0.00%> (-0.57%)` | :arrow_down: |
| [snapshot/snapshotfs/repofs.go](https://codecov.io/gh/kopia/kopia/pull/2543/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kopia#diff-c25hcHNob3Qvc25hcHNob3Rmcy9yZXBvZnMuZ28=) | `80.80% <0.00%> (+1.51%)` | :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/2543?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/go.mod b/go.mod
index 8eba2c33..d855d3cb 100644
--- a/go.mod
+++ b/go.mod
@@ -9,7 +9,7 @@ require (
 	github.com/GehirnInc/crypt v0.0.0-20200316065508-bb7000b8a962 // indirect
 	github.com/alecthomas/kingpin v1.3.8-0.20220615105907-eae6867f4166 // this is pulling master, which is newer than v2
 	github.com/alecthomas/units v0.0.0-20210927113745-59d0afb8317a
-	github.com/aws/aws-sdk-go v1.44.128
+	github.com/aws/aws-sdk-go v1.44.129
 	github.com/chmduquesne/rollinghash v4.0.0+incompatible
 	github.com/dustinkirkland/golang-petname v0.0.0-20191129215211-8e5a1ed0cff0
 	github.com/fatih/color v1.13.0
@@ -24,13 +24,13 @@ require (
 	github.com/google/uuid v1.3.0
 	github.com/gorilla/mux v1.8.0
 	github.com/hanwen/go-fuse/v2 v2.1.1-0.20220112183258-f57e95bda82d
-	github.com/klauspost/compress v1.15.11
+	github.com/klauspost/compress v1.15.12
 	github.com/klauspost/cpuid/v2 v2.1.1 // indirect
 	github.com/klauspost/pgzip v1.2.5
 	github.com/kylelemons/godebug v1.1.0
 	github.com/mattn/go-colorable v0.1.13
 	github.com/minio/md5-simd v1.1.2 // indirect
-	github.com/minio/minio-go/v7 v7.0.41
+	github.com/minio/minio-go/v7 v7.0.43
 	github.com/minio/sha256-simd v1.0.0 // indirect
 	github.com/natefinch/atomic v1.0.1
 	github.com/pierrec/lz4 v2.6.1+incompatible
@@ -38,7 +38,7 @@ require (
 	github.com/pkg/profile v1.6.0
 	github.com/pkg/sftp v1.13.5
 	github.com/pquerna/ffjson v0.0.0-20190930134022-aa0246cd15f7 // indirect
-	github.com/prometheus/client_golang v1.13.0
+	github.com/prometheus/client_golang v1.13.1
 	github.com/sanity-io/litter v1.5.5
 	github.com/sirupsen/logrus v1.9.0 // indirect
 	github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966
diff --git a/go.sum b/go.sum
index fcdcab77..0fe20012 100644
--- a/go.sum
+++ b/go.sum
@@ -96,8 +96,8 @@ github.com/alecthomas/units v0.0.0-20210927113745-59d0afb8317a/go.mod h1:OMCwj8V
 github.com/alessio/shellescape v1.4.1 h1:V7yhSDDn8LP4lc4jS8pFkt0zCnzVJlG5JXy9BVKJUX0=
 github.com/alessio/shellescape v1.4.1/go.mod h1:PZAiSCk0LJaZkiCSkPv8qIobYglO3FPpyFjDCtHLS30=
 github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
-github.com/aws/aws-sdk-go v1.44.128 h1:X34pX5t0LIZXjBY11yf9JKMP3c1aZgirh+5PjtaZyJ4=
-github.com/aws/aws-sdk-go v1.44.128/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo=
+github.com/aws/aws-sdk-go v1.44.129 h1:yld8Rc8OCahLtenY1mnve4w1jVeBu/rSiscGzodaDOs=
+github.com/aws/aws-sdk-go v1.44.129/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo=
 github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8=
 github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
 github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
@@ -313,8 +313,8 @@ github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/X
 github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
 github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
 github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
-github.com/klauspost/compress v1.15.11 h1:Lcadnb3RKGin4FYM/orgq0qde+nc15E5Cbqg4B9Sx9c=
-github.com/klauspost/compress v1.15.11/go.mod h1:QPwzmACJjUTFsnSHH934V6woptycfrDDJnH7hvFVbGM=
+github.com/klauspost/compress v1.15.12 h1:YClS/PImqYbn+UILDnqxQCZ3RehC9N318SU3kElDUEM=
+github.com/klauspost/compress v1.15.12/go.mod h1:QPwzmACJjUTFsnSHH934V6woptycfrDDJnH7hvFVbGM=
 github.com/klauspost/cpuid/v2 v2.0.1/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
 github.com/klauspost/cpuid/v2 v2.0.4/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
 github.com/klauspost/cpuid/v2 v2.0.12/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c=
@@ -358,8 +358,8 @@ github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0j
 github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
 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.41 h1:Qhc82nDRep+VSuDEPSawKUHkARnZI5st7acEqgqVX+k=
-github.com/minio/minio-go/v7 v7.0.41/go.mod h1:nCrRzjoSUQh8hgKKtu3Y708OLvRLtuASMg2/nvmbarw=
+github.com/minio/minio-go/v7 v7.0.43 h1:14Q4lwblqTdlAmba05oq5xL0VBLHi06zS4yLnIkz6hI=
+github.com/minio/minio-go/v7 v7.0.43/go.mod h1:nCrRzjoSUQh8hgKKtu3Y708OLvRLtuASMg2/nvmbarw=
 github.com/minio/sha256-simd v1.0.0 h1:v1ta+49hkWZyvaKwrQB8elexRqm6Y0aMLjCNsrYxo6g=
 github.com/minio/sha256-simd v1.0.0/go.mod h1:OuYzVNI5vcoYIAmbIvHPl3N3jUzVedXbKy5RFepssQM=
 github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
@@ -395,8 +395,8 @@ github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5Fsn
 github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
 github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0=
 github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY=
-github.com/prometheus/client_golang v1.13.0 h1:b71QUfeo5M8gq2+evJdTPfZhYMAU0uKPkyPJ7TPsloU=
-github.com/prometheus/client_golang v1.13.0/go.mod h1:vTeo+zgvILHsnnj/39Ou/1fPN5nJFOEMgftOUOmlvYQ=
+github.com/prometheus/client_golang v1.13.1 h1:3gMjIY2+/hzmqhtUC/aQNYldJA6DtH3CgQvwS+02K1c=
+github.com/prometheus/client_golang v1.13.1/go.mod h1:vTeo+zgvILHsnnj/39Ou/1fPN5nJFOEMgftOUOmlvYQ=
 github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
 github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
 github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
diff --git a/repo/manifest/manifest_manager.go b/repo/manifest/manifest_manager.go
index dc945295..cfd2638b 100644
--- a/repo/manifest/manifest_manager.go
+++ b/repo/manifest/manifest_manager.go
@@ -199,7 +199,7 @@ func cloneEntryMetadata(e *manifestEntry) *EntryMetadata {
 // matchesLabels returns true when all entries in 'b' are found in the 'a'.
 func matchesLabels(a, b map[string]string) bool {
 	for k, v := range b {
-		if a[k] != v {
+		if av, ok := a[k]; !ok || av != v {
 			return false
 		}
 	}


Test patch:
diff --git a/repo/manifest/manifest_manager_test.go b/repo/manifest/manifest_manager_test.go
index 73afc4dd..da412360 100644
--- a/repo/manifest/manifest_manager_test.go
+++ b/repo/manifest/manifest_manager_test.go
@@ -32,14 +32,17 @@ func TestManifest(t *testing.T) {
 	item1 := map[string]int{"foo": 1, "bar": 2}
 	item2 := map[string]int{"foo": 2, "bar": 3}
 	item3 := map[string]int{"foo": 3, "bar": 4}
+	item4 := map[string]int{"foo": 4, "bar": 5}
 
 	labels1 := map[string]string{"type": "item", "color": "red"}
 	labels2 := map[string]string{"type": "item", "color": "blue", "shape": "square"}
 	labels3 := map[string]string{"type": "item", "shape": "square", "color": "red"}
+	labels4 := map[string]string{"type": "item", "rounded": ""}
 
 	id1 := addAndVerify(ctx, t, mgr, labels1, item1)
 	id2 := addAndVerify(ctx, t, mgr, labels2, item2)
 	id3 := addAndVerify(ctx, t, mgr, labels3, item3)
+	id4 := addAndVerify(ctx, t, mgr, labels4, item4)
 
 	cases := []struct {
 		criteria map[string]string
@@ -51,6 +54,7 @@ func TestManifest(t *testing.T) {
 		{map[string]string{"color": "red", "shape": "square"}, []ID{id3}},
 		{map[string]string{"color": "blue", "shape": "square"}, []ID{id2}},
 		{map[string]string{"color": "red", "shape": "circle"}, nil},
+		{map[string]string{"rounded": ""}, []ID{id4}},
 	}
 
 	// verify before flush
@@ -61,6 +65,7 @@ func TestManifest(t *testing.T) {
 	verifyItem(ctx, t, mgr, id1, labels1, item1)
 	verifyItem(ctx, t, mgr, id2, labels2, item2)
 	verifyItem(ctx, t, mgr, id3, labels3, item3)
+	verifyItem(ctx, t, mgr, id4, labels4, item4)
 
 	if err := mgr.Flush(ctx); err != nil {
 		t.Errorf("flush error: %v", err)
@@ -78,6 +83,7 @@ func TestManifest(t *testing.T) {
 	verifyItem(ctx, t, mgr, id1, labels1, item1)
 	verifyItem(ctx, t, mgr, id2, labels2, item2)
 	verifyItem(ctx, t, mgr, id3, labels3, item3)
+	verifyItem(ctx, t, mgr, id4, labels4, item4)
 
 	// flush underlying content manager and verify in new manifest manager.
 	mgr.b.Flush(ctx)
@@ -90,6 +96,7 @@ func TestManifest(t *testing.T) {
 	verifyItem(ctx, t, mgr2, id1, labels1, item1)
 	verifyItem(ctx, t, mgr2, id2, labels2, item2)
 	verifyItem(ctx, t, mgr2, id3, labels3, item3)
+	verifyItem(ctx, t, mgr2, id4, labels4, item4)
 
 	if err := mgr2.Flush(ctx); err != nil {
 		t.Errorf("flush error: %v", err)
@@ -136,6 +143,7 @@ func(ci content.Info) error {
 	verifyItem(ctx, t, mgr3, id1, labels1, item1)
 	verifyItem(ctx, t, mgr3, id2, labels2, item2)
 	verifyItemNotFound(ctx, t, mgr3, id3)
+	verifyItem(ctx, t, mgr3, id4, labels4, item4)
 }
 
 func TestManifestInitCorruptedBlock(t *testing.T) {