github.com/vchain-us/vcn@v0.9.11-0.20210921212052-a2484d23c0b3/pkg/bundle/diff.go (about) 1 /* 2 * Copyright (c) 2018-2020 vChain, Inc. All Rights Reserved. 3 * This software is released under GPL3. 4 * The full license information can be found under: 5 * https://www.gnu.org/licenses/gpl-3.0.en.html 6 * 7 */ 8 9 package bundle 10 11 import ( 12 "fmt" 13 "strings" 14 15 "github.com/dustin/go-humanize" 16 "github.com/google/go-cmp/cmp" 17 digest "github.com/opencontainers/go-digest" 18 ) 19 20 // Diff returns a human-readable report as string of the raw differences between m and x. 21 // 22 // Do not depend on this output being stable. 23 func (m Manifest) Diff(x Manifest) (report string, equal bool, err error) { 24 var r diffReporter 25 equal = cmp.Equal(x, m, cmp.Reporter(&r)) 26 report = r.String() 27 return 28 } 29 30 type diffReporter struct { 31 path cmp.Path 32 lines []string 33 } 34 35 func (r *diffReporter) PushStep(ps cmp.PathStep) { 36 r.path = append(r.path, ps) 37 } 38 39 func (r *diffReporter) Report(rs cmp.Result) { 40 if !rs.Equal() { 41 vx, vy := r.path.Last().Values() 42 var line string 43 switch true { 44 case !vx.IsValid(): 45 line = fmt.Sprintf("%#v:\n\t+: %+v\n", r.path, vy) 46 case !vy.IsValid(): 47 line = fmt.Sprintf("%#v:\n\t-: %+v\n", r.path, vx) 48 default: 49 line = fmt.Sprintf("%#v:\n\t-: %+v\n\t+: %+v\n", r.path, vx, vy) 50 } 51 r.lines = append(r.lines, line) 52 } 53 } 54 55 func (r *diffReporter) PopStep() { 56 r.path = r.path[:len(r.path)-1] 57 } 58 59 func (r *diffReporter) String() string { 60 return strings.Join(r.lines, "\n") 61 } 62 63 // DiffByPath returns a human-readable report as string containing 64 // additions, modifications, renamings, deletions of x.Items relative to m.Items 65 // listed by path. 66 // 67 // Do not depend on this output being stable. 68 func (m Manifest) DiffByPath(x Manifest) (report string, equal bool, err error) { 69 type modDiff struct { 70 path string 71 from Descriptor 72 to Descriptor 73 } 74 75 type pathDiff struct { 76 desc Descriptor 77 from string 78 to string 79 } 80 81 type itemDiff struct { 82 path string 83 desc Descriptor 84 } 85 86 adds := make([]itemDiff, 0) 87 mods := make([]modDiff, 0) 88 rens := make([]pathDiff, 0) 89 dels := make([]itemDiff, 0) 90 91 mByPath := make(map[string]Descriptor) 92 xByPath := make(map[string]Descriptor) 93 newPaths := make(map[digest.Digest][]string) 94 95 for _, d := range x.Items { 96 for _, path := range d.Paths { 97 xByPath[path] = d 98 } 99 } 100 for _, d := range m.Items { 101 for _, path := range d.Paths { 102 mByPath[path] = d 103 if _, ok := xByPath[path]; !ok { 104 newPaths[d.Digest] = append(newPaths[d.Digest], path) 105 } 106 } 107 } 108 109 for path, xd := range xByPath { 110 111 // try by path 112 if md, ok := mByPath[path]; ok { 113 if md.Digest != xd.Digest { 114 // modified 115 mods = append(mods, modDiff{ 116 path: path, 117 from: xd, 118 to: md, 119 }) 120 } 121 // else: 122 // same content, so no diff 123 124 } else { // try by digest 125 byDig := xd.Digest 126 if mPaths, ok := newPaths[byDig]; ok && len(mPaths) > 0 { 127 // renamed 128 newPath := mPaths[0] 129 newPaths[byDig] = mPaths[1:] 130 rens = append(rens, pathDiff{ 131 desc: xd, 132 from: path, 133 to: newPath, 134 }) 135 delete(mByPath, newPath) 136 } else { 137 // deleted 138 dels = append(dels, itemDiff{ 139 path: path, 140 desc: xd, 141 }) 142 } 143 144 } 145 146 delete(mByPath, path) 147 } 148 149 // finally, arrange new items 150 for path, d := range mByPath { 151 adds = append(adds, itemDiff{ 152 path: path, 153 desc: d, 154 }) 155 } 156 157 equal = len(adds) == 0 && len(mods) == 0 && len(rens) == 0 && len(dels) == 0 158 if equal { 159 return // empty diff, no need to format lines 160 } 161 162 lines := []string{} 163 164 sprintf := func(format string, a ...interface{}) { 165 lines = append(lines, fmt.Sprintf(format, a...)) 166 } 167 168 for _, d := range adds { 169 sprintf( 170 "\tnew item: %s (%s)\n\t + %s", 171 d.path, 172 humanize.Bytes(d.desc.Size), 173 d.desc.Digest.String(), 174 ) 175 } 176 for _, d := range mods { 177 sprintf( 178 "\tmodified: %s (%s -> %s)\n\t - %s\n\t + %s", 179 d.path, 180 humanize.Bytes(d.from.Size), 181 humanize.Bytes(d.to.Size), 182 d.from.Digest.String(), 183 d.to.Digest.String(), 184 ) 185 } 186 for _, d := range rens { 187 sprintf( 188 "\trenamed: %s -> %s (%s)\n\t = %s", 189 d.from, 190 d.to, 191 humanize.Bytes(d.desc.Size), 192 d.desc.Digest.String(), 193 ) 194 } 195 for _, d := range dels { 196 sprintf( 197 "\tdeleted: %s (%s)\n\t - %s", 198 d.path, 199 humanize.Bytes(d.desc.Size), 200 d.desc.Digest.String(), 201 ) 202 } 203 204 report = strings.Join(lines, "\n\n") + "\n" 205 return 206 }