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