fork
Configure Feed
Select the types of activity you want to include in your feed.
Monorepo for Tangled
tangled.org
fork
Configure Feed
Select the types of activity you want to include in your feed.
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}