Merge from mainline (167278:168000).
[official-gcc/graphite-test-results.git] / libgo / go / patch / textdiff.go
blobc7e693fc6691c755b8192c5ddeec54f8049c2185
1 package patch
3 import (
4 "bytes"
5 "os"
8 type TextDiff []TextChunk
10 // A TextChunk specifies an edit to a section of a file:
11 // the text beginning at Line, which should be exactly Old,
12 // is to be replaced with New.
13 type TextChunk struct {
14 Line int
15 Old []byte
16 New []byte
19 func ParseTextDiff(raw []byte) (TextDiff, os.Error) {
20 // Copy raw so it is safe to keep references to slices.
21 _, chunks := sections(raw, "@@ -")
22 delta := 0
23 diff := make(TextDiff, len(chunks))
24 for i, raw := range chunks {
25 c := &diff[i]
27 // Parse start line: @@ -oldLine,oldCount +newLine,newCount @@ junk
28 chunk := splitLines(raw)
29 chunkHeader := chunk[0]
30 var ok bool
31 var oldLine, oldCount, newLine, newCount int
32 s := chunkHeader
33 if oldLine, s, ok = atoi(s, "@@ -", 10); !ok {
34 ErrChunkHdr:
35 return nil, SyntaxError("unexpected chunk header line: " + string(chunkHeader))
37 if len(s) == 0 || s[0] != ',' {
38 oldCount = 1
39 } else if oldCount, s, ok = atoi(s, ",", 10); !ok {
40 goto ErrChunkHdr
42 if newLine, s, ok = atoi(s, " +", 10); !ok {
43 goto ErrChunkHdr
45 if len(s) == 0 || s[0] != ',' {
46 newCount = 1
47 } else if newCount, s, ok = atoi(s, ",", 10); !ok {
48 goto ErrChunkHdr
50 if !hasPrefix(s, " @@") {
51 goto ErrChunkHdr
54 // Special case: for created or deleted files, the empty half
55 // is given as starting at line 0. Translate to line 1.
56 if oldCount == 0 && oldLine == 0 {
57 oldLine = 1
59 if newCount == 0 && newLine == 0 {
60 newLine = 1
63 // Count lines in text
64 var dropOldNL, dropNewNL bool
65 var nold, nnew int
66 var lastch byte
67 chunk = chunk[1:]
68 for _, l := range chunk {
69 if nold == oldCount && nnew == newCount && (len(l) == 0 || l[0] != '\\') {
70 if len(bytes.TrimSpace(l)) != 0 {
71 return nil, SyntaxError("too many chunk lines")
73 continue
75 if len(l) == 0 {
76 return nil, SyntaxError("empty chunk line")
78 switch l[0] {
79 case '+':
80 nnew++
81 case '-':
82 nold++
83 case ' ':
84 nnew++
85 nold++
86 case '\\':
87 if _, ok := skip(l, "\\ No newline at end of file"); ok {
88 switch lastch {
89 case '-':
90 dropOldNL = true
91 case '+':
92 dropNewNL = true
93 case ' ':
94 dropOldNL = true
95 dropNewNL = true
96 default:
97 return nil, SyntaxError("message `\\ No newline at end of file' out of context")
99 break
101 fallthrough
102 default:
103 return nil, SyntaxError("unexpected chunk line: " + string(l))
105 lastch = l[0]
108 // Does it match the header?
109 if nold != oldCount || nnew != newCount {
110 return nil, SyntaxError("chunk header does not match line count: " + string(chunkHeader))
112 if oldLine+delta != newLine {
113 return nil, SyntaxError("chunk delta is out of sync with previous chunks")
115 delta += nnew - nold
116 c.Line = oldLine
118 var old, new bytes.Buffer
119 nold = 0
120 nnew = 0
121 for _, l := range chunk {
122 if nold == oldCount && nnew == newCount {
123 break
125 ch, l := l[0], l[1:]
126 if ch == '\\' {
127 continue
129 if ch != '+' {
130 old.Write(l)
131 nold++
133 if ch != '-' {
134 new.Write(l)
135 nnew++
138 c.Old = old.Bytes()
139 c.New = new.Bytes()
140 if dropOldNL {
141 c.Old = c.Old[0 : len(c.Old)-1]
143 if dropNewNL {
144 c.New = c.New[0 : len(c.New)-1]
147 return diff, nil
150 var ErrPatchFailure = os.NewError("patch did not apply cleanly")
152 // Apply applies the changes listed in the diff
153 // to the data, returning the new version.
154 func (d TextDiff) Apply(data []byte) ([]byte, os.Error) {
155 var buf bytes.Buffer
156 line := 1
157 for _, c := range d {
158 var ok bool
159 var prefix []byte
160 prefix, data, ok = getLine(data, c.Line-line)
161 if !ok || !bytes.HasPrefix(data, c.Old) {
162 return nil, ErrPatchFailure
164 buf.Write(prefix)
165 data = data[len(c.Old):]
166 buf.Write(c.New)
167 line = c.Line + bytes.Count(c.Old, newline)
169 buf.Write(data)
170 return buf.Bytes(), nil