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