compare: add CompareSame()
I have a use case where I'd like to know the files that are the same in the
tree, as well as the differences.
I could do this with a separate walk and excluding the paths that were
different, but since mtree is already doing all of this for me, it makes
sense to include it here. I've added a new function so that the behavior
stays the same for existing users of Compare(), since I assume mostly this
will be slower given that most files stay the same. I'd be happy to merge
it into one, though.
Signed-off-by: Tycho Andersen <tycho@tycho.ws>
Tycho Andersen
5 years ago
28 | 28 | // have different values (or have not been set in one of the |
29 | 29 | // manifests). |
30 | 30 | Modified DifferenceType = "modified" |
31 | ||
32 | // Same represents the case where two files are the same. These are | |
33 | // only generated from CompareSame(). | |
34 | Same DifferenceType = "same" | |
31 | 35 | |
32 | 36 | // ErrorDifference represents an attempted update to the values of |
33 | 37 | // a keyword that failed |
304 | 308 | name: name, |
305 | 309 | old: diff.Old.Value(), |
306 | 310 | new: diff.New.Value(), |
311 | }) | |
312 | } | |
313 | } | |
314 | } | |
315 | ||
316 | return results, nil | |
317 | } | |
318 | ||
319 | // compare is the actual workhorse for Compare() and CompareSame() | |
320 | func compare(oldDh, newDh *DirectoryHierarchy, keys []Keyword, same bool) ([]InodeDelta, error) { | |
321 | // Represents the new and old states for an entry. | |
322 | type stateT struct { | |
323 | Old *Entry | |
324 | New *Entry | |
325 | } | |
326 | ||
327 | // To deal with different orderings of the entries, use a path-keyed | |
328 | // map to make sure we don't start comparing unrelated entries. | |
329 | diffs := map[string]*stateT{} | |
330 | ||
331 | // First, iterate over the old hierarchy. If nil, pretend it's empty. | |
332 | if oldDh != nil { | |
333 | for _, e := range oldDh.Entries { | |
334 | if e.Type == RelativeType || e.Type == FullType { | |
335 | path, err := e.Path() | |
336 | if err != nil { | |
337 | return nil, err | |
338 | } | |
339 | //fmt.Printf("new: %q\n", path) | |
340 | ||
341 | // Cannot take &kv because it's the iterator. | |
342 | cEntry := new(Entry) | |
343 | *cEntry = e | |
344 | ||
345 | _, ok := diffs[path] | |
346 | if !ok { | |
347 | diffs[path] = &stateT{} | |
348 | } | |
349 | diffs[path].Old = cEntry | |
350 | } | |
351 | } | |
352 | } | |
353 | ||
354 | // Then, iterate over the new hierarchy. If nil, pretend it's empty. | |
355 | if newDh != nil { | |
356 | for _, e := range newDh.Entries { | |
357 | if e.Type == RelativeType || e.Type == FullType { | |
358 | path, err := e.Path() | |
359 | if err != nil { | |
360 | return nil, err | |
361 | } | |
362 | //fmt.Printf("old: %q\n", path) | |
363 | ||
364 | // Cannot take &kv because it's the iterator. | |
365 | cEntry := new(Entry) | |
366 | *cEntry = e | |
367 | ||
368 | _, ok := diffs[path] | |
369 | if !ok { | |
370 | diffs[path] = &stateT{} | |
371 | } | |
372 | diffs[path].New = cEntry | |
373 | } | |
374 | } | |
375 | } | |
376 | ||
377 | // Now we compute the diff. | |
378 | var results []InodeDelta | |
379 | for path, diff := range diffs { | |
380 | // Invalid | |
381 | if diff.Old == nil && diff.New == nil { | |
382 | return nil, fmt.Errorf("invalid state: both old and new are nil: path=%s", path) | |
383 | } | |
384 | ||
385 | switch { | |
386 | // Missing | |
387 | case diff.New == nil: | |
388 | results = append(results, InodeDelta{ | |
389 | diff: Missing, | |
390 | path: path, | |
391 | old: *diff.Old, | |
392 | }) | |
393 | ||
394 | // Extra | |
395 | case diff.Old == nil: | |
396 | results = append(results, InodeDelta{ | |
397 | diff: Extra, | |
398 | path: path, | |
399 | new: *diff.New, | |
400 | }) | |
401 | ||
402 | // Modified | |
403 | default: | |
404 | changed, err := compareEntry(*diff.Old, *diff.New) | |
405 | if err != nil { | |
406 | return nil, fmt.Errorf("comparison failed %s: %s", path, err) | |
407 | } | |
408 | ||
409 | // Now remove "changed" entries that don't match the keys. | |
410 | if keys != nil { | |
411 | var filterChanged []KeyDelta | |
412 | for _, keyDiff := range changed { | |
413 | if InKeywordSlice(keyDiff.name.Prefix(), keys) { | |
414 | filterChanged = append(filterChanged, keyDiff) | |
415 | } | |
416 | } | |
417 | changed = filterChanged | |
418 | } | |
419 | ||
420 | // Check if there were any actual changes. | |
421 | if len(changed) > 0 { | |
422 | results = append(results, InodeDelta{ | |
423 | diff: Modified, | |
424 | path: path, | |
425 | old: *diff.Old, | |
426 | new: *diff.New, | |
427 | keys: changed, | |
428 | }) | |
429 | } else if same { | |
430 | // this means that nothing changed, i.e. that | |
431 | // the files are the same. | |
432 | results = append(results, InodeDelta{ | |
433 | diff: Same, | |
434 | path: path, | |
435 | old: *diff.Old, | |
436 | new: *diff.New, | |
437 | keys: changed, | |
307 | 438 | }) |
308 | 439 | } |
309 | 440 | } |
331 | 462 | // NB: The order of the parameters matters (old, new) because Extra and |
332 | 463 | // Missing are considered as different discrepancy types. |
333 | 464 | func Compare(oldDh, newDh *DirectoryHierarchy, keys []Keyword) ([]InodeDelta, error) { |
334 | // Represents the new and old states for an entry. | |
335 | type stateT struct { | |
336 | Old *Entry | |
337 | New *Entry | |
338 | } | |
339 | ||
340 | // To deal with different orderings of the entries, use a path-keyed | |
341 | // map to make sure we don't start comparing unrelated entries. | |
342 | diffs := map[string]*stateT{} | |
343 | ||
344 | // First, iterate over the old hierarchy. If nil, pretend it's empty. | |
345 | if oldDh != nil { | |
346 | for _, e := range oldDh.Entries { | |
347 | if e.Type == RelativeType || e.Type == FullType { | |
348 | path, err := e.Path() | |
349 | if err != nil { | |
350 | return nil, err | |
351 | } | |
352 | //fmt.Printf("new: %q\n", path) | |
353 | ||
354 | // Cannot take &kv because it's the iterator. | |
355 | cEntry := new(Entry) | |
356 | *cEntry = e | |
357 | ||
358 | _, ok := diffs[path] | |
359 | if !ok { | |
360 | diffs[path] = &stateT{} | |
361 | } | |
362 | diffs[path].Old = cEntry | |
363 | } | |
364 | } | |
365 | } | |
366 | ||
367 | // Then, iterate over the new hierarchy. If nil, pretend it's empty. | |
368 | if newDh != nil { | |
369 | for _, e := range newDh.Entries { | |
370 | if e.Type == RelativeType || e.Type == FullType { | |
371 | path, err := e.Path() | |
372 | if err != nil { | |
373 | return nil, err | |
374 | } | |
375 | //fmt.Printf("old: %q\n", path) | |
376 | ||
377 | // Cannot take &kv because it's the iterator. | |
378 | cEntry := new(Entry) | |
379 | *cEntry = e | |
380 | ||
381 | _, ok := diffs[path] | |
382 | if !ok { | |
383 | diffs[path] = &stateT{} | |
384 | } | |
385 | diffs[path].New = cEntry | |
386 | } | |
387 | } | |
388 | } | |
389 | ||
390 | // Now we compute the diff. | |
391 | var results []InodeDelta | |
392 | for path, diff := range diffs { | |
393 | // Invalid | |
394 | if diff.Old == nil && diff.New == nil { | |
395 | return nil, fmt.Errorf("invalid state: both old and new are nil: path=%s", path) | |
396 | } | |
397 | ||
398 | switch { | |
399 | // Missing | |
400 | case diff.New == nil: | |
401 | results = append(results, InodeDelta{ | |
402 | diff: Missing, | |
403 | path: path, | |
404 | old: *diff.Old, | |
405 | }) | |
406 | ||
407 | // Extra | |
408 | case diff.Old == nil: | |
409 | results = append(results, InodeDelta{ | |
410 | diff: Extra, | |
411 | path: path, | |
412 | new: *diff.New, | |
413 | }) | |
414 | ||
415 | // Modified | |
416 | default: | |
417 | changed, err := compareEntry(*diff.Old, *diff.New) | |
418 | if err != nil { | |
419 | return nil, fmt.Errorf("comparison failed %s: %s", path, err) | |
420 | } | |
421 | ||
422 | // Now remove "changed" entries that don't match the keys. | |
423 | if keys != nil { | |
424 | var filterChanged []KeyDelta | |
425 | for _, keyDiff := range changed { | |
426 | if InKeywordSlice(keyDiff.name.Prefix(), keys) { | |
427 | filterChanged = append(filterChanged, keyDiff) | |
428 | } | |
429 | } | |
430 | changed = filterChanged | |
431 | } | |
432 | ||
433 | // Check if there were any actual changes. | |
434 | if len(changed) > 0 { | |
435 | results = append(results, InodeDelta{ | |
436 | diff: Modified, | |
437 | path: path, | |
438 | old: *diff.Old, | |
439 | new: *diff.New, | |
440 | keys: changed, | |
441 | }) | |
442 | } | |
443 | } | |
444 | } | |
445 | ||
446 | return results, nil | |
447 | } | |
465 | return compare(oldDh, newDh, keys, false) | |
466 | } | |
467 | ||
468 | // CompareSame is the same as Compare, except it also includes the entries | |
469 | // that are the same with a Same DifferenceType. | |
470 | func CompareSame(oldDh, newDh *DirectoryHierarchy, keys []Keyword) ([]InodeDelta, error) { | |
471 | return compare(oldDh, newDh, keys, true) | |
472 | } |