@@ -17,13 +17,105 @@ limitations under the License.
1717package clientcmd
1818
1919import (
20- "github.com/imdario/mergo"
20+ "fmt"
21+ "reflect"
22+ "strings"
2123)
2224
2325// 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
2528// - maps are shallow merged with src keys taking priority over dst
2629// - non-zero src fields encountered during recursion that are not maps or structs overwrite and recursion stops
2730func 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
29121}
0 commit comments