@@ -97,7 +97,11 @@ func (m Merge3) Merge() error {
97
97
})
98
98
99
99
rmMatcher := ResourceMergeMatcher {MergeOnPath : m .MergeOnPath }
100
- kyamlMerge := filters.Merge3 {Matcher : & rmMatcher }
100
+ resourceHandler := resourceHandler {}
101
+ kyamlMerge := filters.Merge3 {
102
+ Matcher : & rmMatcher ,
103
+ Handler : & resourceHandler ,
104
+ }
101
105
102
106
return kio.Pipeline {
103
107
Inputs : inputs ,
@@ -268,3 +272,79 @@ func metadataComment(node *yaml.RNode) string {
268
272
}
269
273
return mf .Key .YNode ().LineComment
270
274
}
275
+
276
+ // resourceHandler is an implementation of the ResourceHandler interface from
277
+ // kyaml. It is used to decide how a resource should be handled during the
278
+ // 3-way merge. This differs from the default implementation in that if a
279
+ // resource is deleted from upstream, it will only be deleted from local if
280
+ // there is no diff between origin and local.
281
+ type resourceHandler struct {
282
+ keptResources []* yaml.RNode
283
+ }
284
+
285
+ func (r * resourceHandler ) Handle (origin , upstream , local * yaml.RNode ) (filters.ResourceMergeStrategy , error ) {
286
+ var strategy filters.ResourceMergeStrategy
287
+ switch {
288
+ // Keep the resource if added locally.
289
+ case origin == nil && upstream == nil && local != nil :
290
+ strategy = filters .KeepDest
291
+ // Add the resource if added in upstream.
292
+ case origin == nil && upstream != nil && local == nil :
293
+ strategy = filters .KeepUpdated
294
+ // Do not re-add the resource if deleted from both upstream and local
295
+ case upstream == nil && local == nil :
296
+ strategy = filters .Skip
297
+ // If deleted from upstream, only delete if local fork does not have changes.
298
+ case origin != nil && upstream == nil :
299
+ equal , err := r .equals (origin , local )
300
+ if err != nil {
301
+ return strategy , err
302
+ }
303
+ if equal {
304
+ strategy = filters .Skip
305
+ } else {
306
+ r .keptResources = append (r .keptResources , local )
307
+ strategy = filters .KeepDest
308
+ }
309
+ // Do not re-add if deleted from local.
310
+ case origin != nil && local == nil :
311
+ strategy = filters .Skip
312
+ default :
313
+ strategy = filters .Merge
314
+ }
315
+ return strategy , nil
316
+ }
317
+
318
+ func (* resourceHandler ) equals (r1 , r2 * yaml.RNode ) (bool , error ) {
319
+ // We need to create new copies of the resources since we need to
320
+ // mutate them before comparing them.
321
+ r1Clone , err := yaml .Parse (r1 .MustString ())
322
+ if err != nil {
323
+ return false , err
324
+ }
325
+ r2Clone , err := yaml .Parse (r2 .MustString ())
326
+ if err != nil {
327
+ return false , err
328
+ }
329
+
330
+ // The resources include annotations with information used during the merge
331
+ // process. We need to remove those before comparing the resources.
332
+ if err := stripKyamlAnnos (r1Clone ); err != nil {
333
+ return false , err
334
+ }
335
+ if err := stripKyamlAnnos (r2Clone ); err != nil {
336
+ return false , err
337
+ }
338
+
339
+ return r1Clone .MustString () == r2Clone .MustString (), nil
340
+ }
341
+
342
+ func stripKyamlAnnos (n * yaml.RNode ) error {
343
+ for _ , a := range []string {mergeSourceAnnotation , kioutil .PathAnnotation , kioutil .IndexAnnotation } {
344
+ err := n .PipeE (yaml .ClearAnnotation (a ))
345
+ if err != nil {
346
+ return err
347
+ }
348
+ }
349
+ return nil
350
+ }
0 commit comments