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