fix(fsrepo): deep merge when setting config
Kyle Huntsman committed
Mar 2, 2022 at 20:26 UTC
e1d14441a090e893a145112dc2f26a6b20c9cf4b
4 files changed
+165
-5
repo/common/common.go
+30
@@ -54,3 +54,33 @@ func MapSetKV(v map[string]interface{}, key string, value interface{}) error {
54
}
55
return nil
56
}
57
+
58
+// Merges the right map into the left map, recursively traversing child maps
59
+// until a non-map value is found
60
+func MapMergeDeep(left, right map[string]interface{}) map[string]interface{} {
61
+ // We want to alter a copy of the map, not the original
62
+ result := make(map[string]interface{})
63
+ for k, v := range left {
64
+ result[k] = v
65
+ }
66
+
67
+ for key, rightVal := range right {
68
+ // If right value is a map
69
+ if rightMap, ok := rightVal.(map[string]interface{}); ok {
70
+ // If key is in left
71
+ if leftVal, found := result[key]; found {
72
+ // If left value is also a map
73
+ if leftMap, ok := leftVal.(map[string]interface{}); ok {
74
+ // Merge nested map
75
+ result[key] = MapMergeDeep(leftMap, rightMap)
76
+ continue
77
+ }
78
+ }
79
+ }
80
+
81
+ // Otherwise set new value to result
82
+ result[key] = rightVal
83
+ }
84
+
85
+ return result
86
+}
repo/common/common_test.go
new
+132
@@ -0,0 +1,132 @@
1
+package common
2
+
3
+import (
4
+ "testing"
5
+
6
+ "github.com/ipfs/go-ipfs/thirdparty/assert"
7
+)
8
+
9
+func TestMapMergeDeepReturnsNew(t *testing.T) {
10
+ leftMap := make(map[string]interface{})
11
+ leftMap["A"] = "Hello World"
12
+
13
+ rightMap := make(map[string]interface{})
14
+ rightMap["A"] = "Foo"
15
+
16
+ MapMergeDeep(leftMap, rightMap)
17
+
18
+ assert.True(leftMap["A"] == "Hello World", t, "MapMergeDeep should return a new map instance")
19
+}
20
+
21
+func TestMapMergeDeepNewKey(t *testing.T) {
22
+ leftMap := make(map[string]interface{})
23
+ leftMap["A"] = "Hello World"
24
+ /*
25
+ leftMap
26
+ {
27
+ A: "Hello World"
28
+ }
29
+ */
30
+
31
+ rightMap := make(map[string]interface{})
32
+ rightMap["B"] = "Bar"
33
+ /*
34
+ rightMap
35
+ {
36
+ B: "Bar"
37
+ }
38
+ */
39
+
40
+ result := MapMergeDeep(leftMap, rightMap)
41
+ /*
42
+ expected
43
+ {
44
+ A: "Hello World"
45
+ B: "Bar"
46
+ }
47
+ */
48
+
49
+ assert.True(result["B"] == "Bar", t, "New keys in right map should exist in resulting map")
50
+}
51
+
52
+func TestMapMergeDeepRecursesOnMaps(t *testing.T) {
53
+ leftMapA := make(map[string]interface{})
54
+ leftMapA["B"] = "A value!"
55
+ leftMapA["C"] = "Another value!"
56
+
57
+ leftMap := make(map[string]interface{})
58
+ leftMap["A"] = leftMapA
59
+ /*
60
+ leftMap
61
+ {
62
+ A: {
63
+ B: "A value!"
64
+ C: "Another value!"
65
+ }
66
+ }
67
+ */
68
+
69
+ rightMapA := make(map[string]interface{})
70
+ rightMapA["C"] = "A different value!"
71
+
72
+ rightMap := make(map[string]interface{})
73
+ rightMap["A"] = rightMapA
74
+ /*
75
+ rightMap
76
+ {
77
+ A: {
78
+ C: "A different value!"
79
+ }
80
+ }
81
+ */
82
+
83
+ result := MapMergeDeep(leftMap, rightMap)
84
+ /*
85
+ expected
86
+ {
87
+ A: {
88
+ B: "A value!"
89
+ C: "A different value!"
90
+ }
91
+ }
92
+ */
93
+
94
+ resultA := result["A"].(map[string]interface{})
95
+ assert.True(resultA["B"] == "A value!", t, "Unaltered values should not change")
96
+ assert.True(resultA["C"] == "A different value!", t, "Nested values should be altered")
97
+}
98
+
99
+func TestMapMergeDeepRightNotAMap(t *testing.T) {
100
+ leftMapA := make(map[string]interface{})
101
+ leftMapA["B"] = "A value!"
102
+
103
+ leftMap := make(map[string]interface{})
104
+ leftMap["A"] = leftMapA
105
+ /*
106
+ origMap
107
+ {
108
+ A: {
109
+ B: "A value!"
110
+ }
111
+ }
112
+ */
113
+
114
+ rightMap := make(map[string]interface{})
115
+ rightMap["A"] = "Not a map!"
116
+ /*
117
+ newMap
118
+ {
119
+ A: "Not a map!"
120
+ }
121
+ */
122
+
123
+ result := MapMergeDeep(leftMap, rightMap)
124
+ /*
125
+ expected
126
+ {
127
+ A: "Not a map!"
128
+ }
129
+ */
130
+
131
+ assert.True(result["A"] == "Not a map!", t, "Right values that are not a map should be set on the result")
132
+}
repo/fsrepo/fsrepo.go
+2
-4
@@ -559,10 +559,8 @@ func (r *FSRepo) setConfigUnsynced(updated *config.Config) error {
559
if err != nil {
560
return err
561
}
562
- for k, v := range m {
563
- mapconf[k] = v
564
- }
565
- if err := serialize.WriteConfigFile(configFilename, mapconf); err != nil {
562
+ mergedMap := common.MapMergeDeep(mapconf, m)
563
+ if err := serialize.WriteConfigFile(configFilename, mergedMap); err != nil {
564
return err
565
}
566
// Do not use `*r.config = ...`. This will modify the *shared* config
test/sharness/t0250-files-api.sh
+1
-1
@@ -876,7 +876,7 @@ test_expect_success "set up automatic sharding/unsharding data" '
876
'
877
878
# TODO: This does not need to report an error https://github.com/ipfs/go-ipfs/issues/8088
879
-test_expect_failure "reset automatic sharding" '
879
+test_expect_success "reset automatic sharding" '
880
ipfs config --json Internal.UnixFSShardingSizeThreshold null
881
'
882