forked from tangled.org/core
this repo has no description
1package patchutil 2 3import ( 4 "fmt" 5 "strings" 6 7 "github.com/bluekeyes/go-gitdiff/gitdiff" 8) 9 10type InterdiffResult struct { 11 Files []*InterdiffFile 12} 13 14func (i *InterdiffResult) String() string { 15 var b strings.Builder 16 for _, f := range i.Files { 17 b.WriteString(f.String()) 18 b.WriteString("\n") 19 } 20 21 return b.String() 22} 23 24type InterdiffFile struct { 25 *gitdiff.File 26 Name string 27 Status InterdiffFileStatus 28} 29 30func (s *InterdiffFile) String() string { 31 var b strings.Builder 32 b.WriteString(s.Status.String()) 33 b.WriteString(" ") 34 35 if s.File != nil { 36 b.WriteString(bestName(s.File)) 37 b.WriteString("\n") 38 b.WriteString(s.File.String()) 39 } 40 41 return b.String() 42} 43 44type InterdiffFileStatus struct { 45 StatusKind StatusKind 46 Error error 47} 48 49func (s *InterdiffFileStatus) String() string { 50 kind := s.StatusKind.String() 51 if s.Error != nil { 52 return fmt.Sprintf("%s [%s]", kind, s.Error.Error()) 53 } else { 54 return kind 55 } 56} 57 58func (s *InterdiffFileStatus) IsOk() bool { 59 return s.StatusKind == StatusOk 60} 61 62func (s *InterdiffFileStatus) IsUnchanged() bool { 63 return s.StatusKind == StatusUnchanged 64} 65 66func (s *InterdiffFileStatus) IsOnlyInOne() bool { 67 return s.StatusKind == StatusOnlyInOne 68} 69 70func (s *InterdiffFileStatus) IsOnlyInTwo() bool { 71 return s.StatusKind == StatusOnlyInTwo 72} 73 74func (s *InterdiffFileStatus) IsRebased() bool { 75 return s.StatusKind == StatusRebased 76} 77 78func (s *InterdiffFileStatus) IsError() bool { 79 return s.StatusKind == StatusError 80} 81 82type StatusKind int 83 84func (k StatusKind) String() string { 85 switch k { 86 case StatusOnlyInOne: 87 return "only in one" 88 case StatusOnlyInTwo: 89 return "only in two" 90 case StatusUnchanged: 91 return "unchanged" 92 case StatusRebased: 93 return "rebased" 94 case StatusError: 95 return "error" 96 default: 97 return "changed" 98 } 99} 100 101const ( 102 StatusOk StatusKind = iota 103 StatusOnlyInOne 104 StatusOnlyInTwo 105 StatusUnchanged 106 StatusRebased 107 StatusError 108) 109 110func interdiffFiles(f1, f2 *gitdiff.File) *InterdiffFile { 111 re1 := CreatePreImage(f1) 112 re2 := CreatePreImage(f2) 113 114 interdiffFile := InterdiffFile{ 115 Name: bestName(f1), 116 } 117 118 merged, err := re1.Merge(&re2) 119 if err != nil { 120 interdiffFile.Status = InterdiffFileStatus{ 121 StatusKind: StatusRebased, 122 Error: err, 123 } 124 return &interdiffFile 125 } 126 127 rev1, err := merged.Apply(f1) 128 if err != nil { 129 interdiffFile.Status = InterdiffFileStatus{ 130 StatusKind: StatusError, 131 Error: err, 132 } 133 return &interdiffFile 134 } 135 136 rev2, err := merged.Apply(f2) 137 if err != nil { 138 interdiffFile.Status = InterdiffFileStatus{ 139 StatusKind: StatusError, 140 Error: err, 141 } 142 return &interdiffFile 143 } 144 145 diff, err := Unified(rev1, bestName(f1), rev2, bestName(f2)) 146 if err != nil { 147 interdiffFile.Status = InterdiffFileStatus{ 148 StatusKind: StatusError, 149 Error: err, 150 } 151 return &interdiffFile 152 } 153 154 parsed, _, err := gitdiff.Parse(strings.NewReader(diff)) 155 if err != nil { 156 interdiffFile.Status = InterdiffFileStatus{ 157 StatusKind: StatusError, 158 Error: err, 159 } 160 return &interdiffFile 161 } 162 163 if len(parsed) != 1 { 164 // files are identical? 165 interdiffFile.Status = InterdiffFileStatus{ 166 StatusKind: StatusUnchanged, 167 } 168 return &interdiffFile 169 } 170 171 if interdiffFile.Status.StatusKind == StatusOk { 172 interdiffFile.File = parsed[0] 173 } 174 175 return &interdiffFile 176} 177 178func Interdiff(patch1, patch2 []*gitdiff.File) *InterdiffResult { 179 fileToIdx1 := make(map[string]int) 180 fileToIdx2 := make(map[string]int) 181 visited := make(map[string]struct{}) 182 var result InterdiffResult 183 184 for idx, f := range patch1 { 185 fileToIdx1[bestName(f)] = idx 186 } 187 188 for idx, f := range patch2 { 189 fileToIdx2[bestName(f)] = idx 190 } 191 192 for _, f1 := range patch1 { 193 var interdiffFile *InterdiffFile 194 195 fileName := bestName(f1) 196 if idx, ok := fileToIdx2[fileName]; ok { 197 f2 := patch2[idx] 198 199 // we have f1 and f2, calculate interdiff 200 interdiffFile = interdiffFiles(f1, f2) 201 } else { 202 // only in patch 1, this change would have to be "inverted" to dissapear 203 // from patch 2, so we reverseDiff(f1) 204 reverseDiff(f1) 205 206 interdiffFile = &InterdiffFile{ 207 File: f1, 208 Name: fileName, 209 Status: InterdiffFileStatus{ 210 StatusKind: StatusOnlyInOne, 211 }, 212 } 213 } 214 215 result.Files = append(result.Files, interdiffFile) 216 visited[fileName] = struct{}{} 217 } 218 219 // for all files in patch2 that remain unvisited; we can just add them into the output 220 for _, f2 := range patch2 { 221 fileName := bestName(f2) 222 if _, ok := visited[fileName]; ok { 223 continue 224 } 225 226 result.Files = append(result.Files, &InterdiffFile{ 227 File: f2, 228 Name: fileName, 229 Status: InterdiffFileStatus{ 230 StatusKind: StatusOnlyInTwo, 231 }, 232 }) 233 } 234 235 return &result 236}