forked from
tangled.org/core
fork
Configure Feed
Select the types of activity you want to include in your feed.
this repo has no description
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 result = append(result, combined)
126 } else {
127 // only in patch1; add as-is
128 result = append(result, f1)
129 }
130
131 visited[fileName] = struct{}{}
132 }
133
134 // for all files in patch2 that remain unvisited; we can just add them into the output
135 for _, f2 := range patch2 {
136 fileName := bestName(f2)
137 if _, ok := visited[fileName]; ok {
138 continue
139 }
140
141 result = append(result, f2)
142 }
143
144 return result
145}
146
147// pairwise combination from first to last patch
148func CombineDiff(patches ...[]*gitdiff.File) []*gitdiff.File {
149 if len(patches) == 0 {
150 return nil
151 }
152
153 if len(patches) == 1 {
154 return patches[0]
155 }
156
157 combined := combineTwo(patches[0], patches[1])
158
159 newPatches := [][]*gitdiff.File{}
160 newPatches = append(newPatches, combined)
161 for i, p := range patches {
162 if i >= 2 {
163 newPatches = append(newPatches, p)
164 }
165 }
166
167 return CombineDiff(newPatches...)
168}