forked from tangled.org/core
this repo has no description
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}