@@ -17,13 +17,105 @@ limitations under the License.
17
17
package clientcmd
18
18
19
19
import (
20
- "github.com/imdario/mergo"
20
+ "fmt"
21
+ "reflect"
22
+ "strings"
21
23
)
22
24
23
25
// recursively merges src into dst:
24
- // - non-pointer struct fields are recursively merged
26
+ // - non-pointer struct fields with any exported fields are recursively merged
27
+ // - non-pointer struct fields with only unexported fields prefer src if the field is non-zero
25
28
// - maps are shallow merged with src keys taking priority over dst
26
29
// - non-zero src fields encountered during recursion that are not maps or structs overwrite and recursion stops
27
30
func merge [T any ](dst , src * T ) error {
28
- return mergo .Merge (dst , src , mergo .WithOverride )
31
+ if dst == nil {
32
+ return fmt .Errorf ("cannot merge into nil pointer" )
33
+ }
34
+ if src == nil {
35
+ return nil
36
+ }
37
+ return mergeValues (nil , reflect .ValueOf (dst ).Elem (), reflect .ValueOf (src ).Elem ())
38
+ }
39
+
40
+ func mergeValues (fieldNames []string , dst , src reflect.Value ) error {
41
+ dstType := dst .Type ()
42
+ // no-op if we can't read the src
43
+ if ! src .IsValid () {
44
+ return nil
45
+ }
46
+ // sanity check types match
47
+ if srcType := src .Type (); dstType != srcType {
48
+ return fmt .Errorf ("cannot merge mismatched types (%s, %s) at %s" , dstType , srcType , strings .Join (fieldNames , "." ))
49
+ }
50
+
51
+ switch dstType .Kind () {
52
+ case reflect .Struct :
53
+ if hasExportedField (dstType ) {
54
+ // recursively merge
55
+ for i , n := 0 , dstType .NumField (); i < n ; i ++ {
56
+ if err := mergeValues (append (fieldNames , dstType .Field (i ).Name ), dst .Field (i ), src .Field (i )); err != nil {
57
+ return err
58
+ }
59
+ }
60
+ } else if dst .CanSet () {
61
+ // If all fields are unexported, overwrite with src.
62
+ // Using src.IsZero() would make more sense but that's not what mergo did.
63
+ dst .Set (src )
64
+ }
65
+
66
+ case reflect .Map :
67
+ if dst .CanSet () && ! src .IsZero () {
68
+ // initialize dst if needed
69
+ if dst .IsZero () {
70
+ dst .Set (reflect .MakeMap (dstType ))
71
+ }
72
+ // shallow-merge overwriting dst keys with src keys
73
+ for _ , mapKey := range src .MapKeys () {
74
+ dst .SetMapIndex (mapKey , src .MapIndex (mapKey ))
75
+ }
76
+ }
77
+
78
+ case reflect .Slice :
79
+ if dst .CanSet () && src .Len () > 0 {
80
+ // overwrite dst with non-empty src slice
81
+ dst .Set (src )
82
+ }
83
+
84
+ case reflect .Pointer :
85
+ if dst .CanSet () && ! src .IsZero () {
86
+ // overwrite dst with non-zero values for other types
87
+ if dstType .Elem ().Kind () == reflect .Struct {
88
+ // use struct pointer as-is
89
+ dst .Set (src )
90
+ } else {
91
+ // shallow-copy non-struct pointer (interfaces, primitives, etc)
92
+ dst .Set (reflect .New (dstType .Elem ()))
93
+ dst .Elem ().Set (src .Elem ())
94
+ }
95
+ }
96
+
97
+ default :
98
+ if dst .CanSet () && ! src .IsZero () {
99
+ // overwrite dst with non-zero values for other types
100
+ dst .Set (src )
101
+ }
102
+ }
103
+
104
+ return nil
105
+ }
106
+
107
+ // hasExportedField returns true if the given type has any exported fields,
108
+ // or if it has any anonymous/embedded struct fields with exported fields
109
+ func hasExportedField (dstType reflect.Type ) bool {
110
+ for i , n := 0 , dstType .NumField (); i < n ; i ++ {
111
+ field := dstType .Field (i )
112
+ if field .Anonymous && field .Type .Kind () == reflect .Struct {
113
+ if hasExportedField (dstType .Field (i ).Type ) {
114
+ return true
115
+ }
116
+ } else if len (field .PkgPath ) == 0 {
117
+ return true
118
+ }
119
+ }
120
+ return false
29
121
}
0 commit comments