+6
-2
appview/pages/templates/repo/fragments/interdiff.html
+6
-2
appview/pages/templates/repo/fragments/interdiff.html
···
11
<div class="overflow-x-auto">
12
<ul class="dark:text-gray-200">
13
{{ range $diff }}
14
-
<li><a href="#file-{{ .Name }}" class="dark:hover:text-gray-300">{{ .Name }} ({{ .Status.StatusKind }})</a></li>
15
{{ end }}
16
</ul>
17
</div>
···
24
<section class="mt-6 border border-gray-200 dark:border-gray-700 w-full mx-auto rounded bg-white dark:bg-gray-800 drop-shadow-sm">
25
<div id="file-{{ .Name }}">
26
<div id="diff-file">
27
-
<details open>
28
<summary class="list-none cursor-pointer sticky top-0">
29
<div id="diff-file-header" class="rounded cursor-pointer bg-white dark:bg-gray-800 flex justify-between">
30
<div id="left-side-items" class="p-2 flex gap-2 items-center overflow-x-auto">
···
73
{{ if .Status.IsUnchanged }}
74
<p class="text-center text-gray-400 dark:text-gray-500 p-4">
75
This file has not been changed.
76
</p>
77
{{ else if .Status.IsError }}
78
<p class="text-center text-gray-400 dark:text-gray-500 p-4">
···
11
<div class="overflow-x-auto">
12
<ul class="dark:text-gray-200">
13
{{ range $diff }}
14
+
<li><a href="#file-{{ .Name }}" class="dark:hover:text-gray-300">{{ .Name }}</a></li>
15
{{ end }}
16
</ul>
17
</div>
···
24
<section class="mt-6 border border-gray-200 dark:border-gray-700 w-full mx-auto rounded bg-white dark:bg-gray-800 drop-shadow-sm">
25
<div id="file-{{ .Name }}">
26
<div id="diff-file">
27
+
<details {{ if not (.Status.IsOnlyInOne) }}open{{end}}>
28
<summary class="list-none cursor-pointer sticky top-0">
29
<div id="diff-file-header" class="rounded cursor-pointer bg-white dark:bg-gray-800 flex justify-between">
30
<div id="left-side-items" class="p-2 flex gap-2 items-center overflow-x-auto">
···
73
{{ if .Status.IsUnchanged }}
74
<p class="text-center text-gray-400 dark:text-gray-500 p-4">
75
This file has not been changed.
76
+
</p>
77
+
{{ else if .Status.IsRebased }}
78
+
<p class="text-center text-gray-400 dark:text-gray-500 p-4">
79
+
This patch was likely rebased, as context lines do not match.
80
</p>
81
{{ else if .Status.IsError }}
82
<p class="text-center text-gray-400 dark:text-gray-500 p-4">
+1
appview/pages/templates/repo/pulls/pull.html
+1
appview/pages/templates/repo/pulls/pull.html
+13
-5
appview/state/pull.go
+13
-5
appview/state/pull.go
···
351
}
352
}
353
354
-
currentPatch, _, _ := gitdiff.Parse(strings.NewReader(pull.Submissions[roundIdInt].Patch))
355
-
previousPatch, _, _ := gitdiff.Parse(strings.NewReader(pull.Submissions[roundIdInt-1].Patch))
356
357
interdiff := interdiff.Interdiff(previousPatch, currentPatch)
358
359
-
for _, f := range interdiff.Files {
360
-
fmt.Printf("%s, %+v\n-----", f.Name, f.File)
361
-
}
362
s.pages.RepoPullInterdiffPage(w, pages.RepoPullInterdiffParams{
363
LoggedInUser: s.auth.GetUser(r),
364
RepoInfo: f.RepoInfo(s, user),
···
351
}
352
}
353
354
+
currentPatch, _, err := gitdiff.Parse(strings.NewReader(pull.Submissions[roundIdInt].Patch))
355
+
if err != nil {
356
+
log.Println("failed to interdiff; current patch malformed")
357
+
s.pages.Notice(w, fmt.Sprintf("interdiff-error-%d", roundIdInt), "Failed to calculate interdiff; current patch is invalid.")
358
+
return
359
+
}
360
+
361
+
previousPatch, _, err := gitdiff.Parse(strings.NewReader(pull.Submissions[roundIdInt-1].Patch))
362
+
if err != nil {
363
+
log.Println("failed to interdiff; previous patch malformed")
364
+
s.pages.Notice(w, fmt.Sprintf("interdiff-error-%d", roundIdInt), "Failed to calculate interdiff; previous patch is invalid.")
365
+
return
366
+
}
367
368
interdiff := interdiff.Interdiff(previousPatch, currentPatch)
369
370
s.pages.RepoPullInterdiffPage(w, pages.RepoPullInterdiffParams{
371
LoggedInUser: s.auth.GetUser(r),
372
RepoInfo: f.RepoInfo(s, user),
+7
-2
cmd/interdiff/main.go
+7
-2
cmd/interdiff/main.go
···
9
)
10
11
func main() {
12
+
if len(os.Args) != 3 {
13
+
fmt.Println("Usage: interdiff <patch1> <patch2>")
14
+
os.Exit(1)
15
+
}
16
+
17
+
patch1, err := os.Open(os.Args[1])
18
if err != nil {
19
fmt.Println(err)
20
}
21
+
patch2, err := os.Open(os.Args[2])
22
if err != nil {
23
fmt.Println(err)
24
}
+57
-27
interdiff/interdiff.go
+57
-27
interdiff/interdiff.go
···
64
}
65
}
66
67
// rebuild the original file from a patch
68
func CreateOriginal(file *gitdiff.File) ReconstructedFile {
69
rf := ReconstructedFile{
···
92
}
93
94
type MergeError struct {
95
-
msg string
96
-
mismatchingLines []int64
97
}
98
99
func (m MergeError) Error() string {
100
-
return fmt.Sprintf("%s: %v", m.msg, m.mismatchingLines)
101
}
102
103
// best effort merging of two reconstructed files
104
func (this *ReconstructedFile) Merge(other *ReconstructedFile) (*ReconstructedFile, error) {
105
-
mismatchingLines := []int64{}
106
mergedFile := ReconstructedFile{}
107
108
var i, j int64
···
127
128
if line1.LineNumber == line2.LineNumber {
129
if line1.Content != line2.Content {
130
-
mismatchingLines = append(mismatchingLines, line1.LineNumber)
131
} else {
132
mergedFile.AddLine(line1)
133
-
i++
134
-
j++
135
}
136
} else if line1.LineNumber < line2.LineNumber {
137
mergedFile.AddLine(line1)
138
i++
···
142
}
143
}
144
145
-
if len(mismatchingLines) > 0 {
146
-
return nil, MergeError{
147
-
msg: "mismatching lines; this patch might have undergone rebase",
148
-
mismatchingLines: mismatchingLines,
149
-
}
150
-
} else {
151
-
return &mergedFile, nil
152
-
}
153
}
154
155
func (r *ReconstructedFile) Apply(patch *gitdiff.File) (string, error) {
···
302
func interdiffFiles(f1, f2 *gitdiff.File) *InterdiffFile {
303
re1 := CreateOriginal(f1)
304
re2 := CreateOriginal(f2)
305
-
var interdiffFile InterdiffFile
306
-
var status InterdiffFileStatus
307
308
merged, err := re1.Merge(&re2)
309
if err != nil {
310
-
status = InterdiffFileStatus{
311
StatusKind: StatusRebased,
312
Error: err,
313
}
314
}
315
316
rev1, err := merged.Apply(f1)
317
if err != nil {
318
-
status = InterdiffFileStatus{
319
StatusKind: StatusError,
320
Error: err,
321
}
322
}
323
324
rev2, err := merged.Apply(f2)
325
if err != nil {
326
-
status = InterdiffFileStatus{
327
StatusKind: StatusError,
328
Error: err,
329
}
330
}
331
332
diff, err := Unified(rev1, bestName(f1), rev2, bestName(f2))
333
if err != nil {
334
-
status = InterdiffFileStatus{
335
StatusKind: StatusError,
336
Error: err,
337
}
338
}
339
340
parsed, _, err := gitdiff.Parse(strings.NewReader(diff))
341
if err != nil {
342
-
status = InterdiffFileStatus{
343
StatusKind: StatusError,
344
Error: err,
345
}
346
}
347
348
if len(parsed) != 1 {
349
// files are identical?
350
-
status = InterdiffFileStatus{
351
StatusKind: StatusUnchanged,
352
}
353
}
354
355
-
interdiffFile.Status = status
356
-
interdiffFile.Name = bestName(f1)
357
-
358
if interdiffFile.Status.StatusKind == StatusOk {
359
interdiffFile.File = parsed[0]
360
}
···
386
// we have f1 and f2, calculate interdiff
387
interdiffFile = interdiffFiles(f1, f2)
388
} else {
389
-
// only in patch 1
390
interdiffFile = &InterdiffFile{
391
File: f1,
392
Status: InterdiffFileStatus{
393
StatusKind: StatusOnlyInOne,
394
},
···
408
409
result.Files = append(result.Files, &InterdiffFile{
410
File: f2,
411
Status: InterdiffFileStatus{
412
StatusKind: StatusOnlyInTwo,
413
},
···
64
}
65
}
66
67
+
// in-place reverse of a diff
68
+
func reverseDiff(file *gitdiff.File) {
69
+
file.OldName, file.NewName = file.NewName, file.OldName
70
+
file.OldMode, file.NewMode = file.NewMode, file.OldMode
71
+
file.BinaryFragment, file.ReverseBinaryFragment = file.ReverseBinaryFragment, file.BinaryFragment
72
+
73
+
for _, fragment := range file.TextFragments {
74
+
// swap postions
75
+
fragment.OldPosition, fragment.NewPosition = fragment.NewPosition, fragment.OldPosition
76
+
fragment.OldLines, fragment.NewLines = fragment.NewLines, fragment.OldLines
77
+
fragment.LinesAdded, fragment.LinesDeleted = fragment.LinesDeleted, fragment.LinesAdded
78
+
79
+
for i := range fragment.Lines {
80
+
switch fragment.Lines[i].Op {
81
+
case gitdiff.OpAdd:
82
+
fragment.Lines[i].Op = gitdiff.OpDelete
83
+
case gitdiff.OpDelete:
84
+
fragment.Lines[i].Op = gitdiff.OpAdd
85
+
default:
86
+
// do nothing
87
+
}
88
+
}
89
+
}
90
+
}
91
+
92
// rebuild the original file from a patch
93
func CreateOriginal(file *gitdiff.File) ReconstructedFile {
94
rf := ReconstructedFile{
···
117
}
118
119
type MergeError struct {
120
+
msg string
121
+
mismatchingLine int64
122
}
123
124
func (m MergeError) Error() string {
125
+
return fmt.Sprintf("%s: %v", m.msg, m.mismatchingLine)
126
}
127
128
// best effort merging of two reconstructed files
129
func (this *ReconstructedFile) Merge(other *ReconstructedFile) (*ReconstructedFile, error) {
130
mergedFile := ReconstructedFile{}
131
132
var i, j int64
···
151
152
if line1.LineNumber == line2.LineNumber {
153
if line1.Content != line2.Content {
154
+
return nil, MergeError{
155
+
msg: "mismatching lines, this patch might have undergone rebase",
156
+
mismatchingLine: line1.LineNumber,
157
+
}
158
} else {
159
mergedFile.AddLine(line1)
160
}
161
+
i++
162
+
j++
163
} else if line1.LineNumber < line2.LineNumber {
164
mergedFile.AddLine(line1)
165
i++
···
169
}
170
}
171
172
+
return &mergedFile, nil
173
}
174
175
func (r *ReconstructedFile) Apply(patch *gitdiff.File) (string, error) {
···
322
func interdiffFiles(f1, f2 *gitdiff.File) *InterdiffFile {
323
re1 := CreateOriginal(f1)
324
re2 := CreateOriginal(f2)
325
+
326
+
interdiffFile := InterdiffFile{
327
+
Name: bestName(f1),
328
+
}
329
330
merged, err := re1.Merge(&re2)
331
if err != nil {
332
+
interdiffFile.Status = InterdiffFileStatus{
333
StatusKind: StatusRebased,
334
Error: err,
335
}
336
+
return &interdiffFile
337
}
338
339
rev1, err := merged.Apply(f1)
340
if err != nil {
341
+
interdiffFile.Status = InterdiffFileStatus{
342
StatusKind: StatusError,
343
Error: err,
344
}
345
+
return &interdiffFile
346
}
347
348
rev2, err := merged.Apply(f2)
349
if err != nil {
350
+
interdiffFile.Status = InterdiffFileStatus{
351
StatusKind: StatusError,
352
Error: err,
353
}
354
+
return &interdiffFile
355
}
356
357
diff, err := Unified(rev1, bestName(f1), rev2, bestName(f2))
358
if err != nil {
359
+
interdiffFile.Status = InterdiffFileStatus{
360
StatusKind: StatusError,
361
Error: err,
362
}
363
+
return &interdiffFile
364
}
365
366
parsed, _, err := gitdiff.Parse(strings.NewReader(diff))
367
if err != nil {
368
+
interdiffFile.Status = InterdiffFileStatus{
369
StatusKind: StatusError,
370
Error: err,
371
}
372
+
return &interdiffFile
373
}
374
375
if len(parsed) != 1 {
376
// files are identical?
377
+
interdiffFile.Status = InterdiffFileStatus{
378
StatusKind: StatusUnchanged,
379
}
380
+
return &interdiffFile
381
}
382
383
if interdiffFile.Status.StatusKind == StatusOk {
384
interdiffFile.File = parsed[0]
385
}
···
411
// we have f1 and f2, calculate interdiff
412
interdiffFile = interdiffFiles(f1, f2)
413
} else {
414
+
// only in patch 1, this change would have to be "inverted" to dissapear
415
+
// from patch 2, so we reverseDiff(f1)
416
+
reverseDiff(f1)
417
+
418
interdiffFile = &InterdiffFile{
419
File: f1,
420
+
Name: fileName,
421
Status: InterdiffFileStatus{
422
StatusKind: StatusOnlyInOne,
423
},
···
437
438
result.Files = append(result.Files, &InterdiffFile{
439
File: f2,
440
+
Name: fileName,
441
Status: InterdiffFileStatus{
442
StatusKind: StatusOnlyInTwo,
443
},