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)
9
10// original1 -> patch1 -> rev1
11// original2 -> patch2 -> rev2
12//
13// original2 must be equal to rev1, so we can merge them to get maximal context
14//
15// finally,
16// rev2' <- apply(patch2, merged)
17// combineddiff <- diff(rev2', original1)
18func combineFiles(file1, file2 *gitdiff.File) (*gitdiff.File, error) {
19 fileName := bestName(file1)
20
21 o1 := CreatePreImage(file1)
22 r1 := CreatePostImage(file1)
23 o2 := CreatePreImage(file2)
24
25 merged, err := r1.Merge(&o2)
26 if err != nil {
27 return nil, err
28 }
29
30 r2Prime, err := merged.Apply(file2)
31 if err != nil {
32 return nil, err
33 }
34
35 // produce combined diff
36 diff, err := Unified(o1.String(), fileName, r2Prime, fileName)
37 if err != nil {
38 return nil, err
39 }
40
41 parsed, _, err := gitdiff.Parse(strings.NewReader(diff))
42
43 if len(parsed) != 1 {
44 // no diff? the second commit reverted the changes from the first
45 return nil, nil
46 }
47
48 return parsed[0], nil
49}
50
51// use empty lines for lines we are unaware of
52//
53// this raises an error only if the two patches were invalid or non-contiguous
54func mergeLines(old, new string) (string, error) {
55 var i, j int
56
57 // TODO: use strings.Lines
58 linesOld := strings.Split(old, "\n")
59 linesNew := strings.Split(new, "\n")
60
61 result := []string{}
62
63 for i < len(linesOld) || j < len(linesNew) {
64 if i >= len(linesOld) {
65 // rest of the file is populated from `new`
66 result = append(result, linesNew[j])
67 j++
68 continue
69 }
70
71 if j >= len(linesNew) {
72 // rest of the file is populated from `old`
73 result = append(result, linesOld[i])
74 i++
75 continue
76 }
77
78 oldLine := linesOld[i]
79 newLine := linesNew[j]
80
81 if oldLine != newLine && (oldLine != "" && newLine != "") {
82 // context mismatch
83 return "", fmt.Errorf("failed to merge files, found context mismatch at %d; oldLine: `%s`, newline: `%s`", i+1, oldLine, newLine)
84 }
85
86 if oldLine == newLine {
87 result = append(result, oldLine)
88 } else if oldLine == "" {
89 result = append(result, newLine)
90 } else if newLine == "" {
91 result = append(result, oldLine)
92 }
93 i++
94 j++
95 }
96
97 return strings.Join(result, "\n"), nil
98}
99
100func combineTwo(patch1, patch2 []*gitdiff.File) []*gitdiff.File {
101 fileToIdx1 := make(map[string]int)
102 fileToIdx2 := make(map[string]int)
103 visited := make(map[string]struct{})
104 var result []*gitdiff.File
105
106 for idx, f := range patch1 {
107 fileToIdx1[bestName(f)] = idx
108 }
109
110 for idx, f := range patch2 {
111 fileToIdx2[bestName(f)] = idx
112 }
113
114 for _, f1 := range patch1 {
115 fileName := bestName(f1)
116 if idx, ok := fileToIdx2[fileName]; ok {
117 f2 := patch2[idx]
118
119 // we have f1 and f2, combine them
120 combined, err := combineFiles(f1, f2)
121 if err != nil {
122 fmt.Println(err)
123 }
124
125 // combined can be nil commit 2 reverted all changes from commit 1
126 if combined != nil {
127 result = append(result, combined)
128 }
129
130 } else {
131 // only in patch1; add as-is
132 result = append(result, f1)
133 }
134
135 visited[fileName] = struct{}{}
136 }
137
138 // for all files in patch2 that remain unvisited; we can just add them into the output
139 for _, f2 := range patch2 {
140 fileName := bestName(f2)
141 if _, ok := visited[fileName]; ok {
142 continue
143 }
144
145 result = append(result, f2)
146 }
147
148 return result
149}
150
151// pairwise combination from first to last patch
152func CombineDiff(patches ...[]*gitdiff.File) []*gitdiff.File {
153 if len(patches) == 0 {
154 return nil
155 }
156
157 if len(patches) == 1 {
158 return patches[0]
159 }
160
161 combined := combineTwo(patches[0], patches[1])
162
163 newPatches := [][]*gitdiff.File{}
164 newPatches = append(newPatches, combined)
165 for i, p := range patches {
166 if i >= 2 {
167 newPatches = append(newPatches, p)
168 }
169 }
170
171 return CombineDiff(newPatches...)
172}