Merge from mainline (167278:168000).
[official-gcc/graphite-test-results.git] / libgo / go / patch / patch.go
blobd4977dc990203137aeb2afee21b514ec1d8ff458
1 // Copyright 2009 The Go Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style
3 // license that can be found in the LICENSE file.
5 // Package patch implements parsing and execution of the textual and
6 // binary patch descriptions used by version control tools such as
7 // CVS, Git, Mercurial, and Subversion.
8 package patch
10 import (
11 "bytes"
12 "os"
13 "path"
14 "strings"
17 // A Set represents a set of patches to be applied as a single atomic unit.
18 // Patch sets are often preceded by a descriptive header.
19 type Set struct {
20 Header string // free-form text
21 File []*File
24 // A File represents a collection of changes to be made to a single file.
25 type File struct {
26 Verb Verb
27 Src string // source for Verb == Copy, Verb == Rename
28 Dst string
29 OldMode, NewMode int // 0 indicates not used
30 Diff // changes to data; == NoDiff if operation does not edit file
33 // A Verb is an action performed on a file.
34 type Verb string
36 const (
37 Add Verb = "add"
38 Copy Verb = "copy"
39 Delete Verb = "delete"
40 Edit Verb = "edit"
41 Rename Verb = "rename"
44 // A Diff is any object that describes changes to transform
45 // an old byte stream to a new one.
46 type Diff interface {
47 // Apply applies the changes listed in the diff
48 // to the string s, returning the new version of the string.
49 // Note that the string s need not be a text string.
50 Apply(old []byte) (new []byte, err os.Error)
53 // NoDiff is a no-op Diff implementation: it passes the
54 // old data through unchanged.
55 var NoDiff Diff = noDiffType(0)
57 type noDiffType int
59 func (noDiffType) Apply(old []byte) ([]byte, os.Error) {
60 return old, nil
63 // A SyntaxError represents a syntax error encountered while parsing a patch.
64 type SyntaxError string
66 func (e SyntaxError) String() string { return string(e) }
68 var newline = []byte{'\n'}
70 // Parse patches the patch text to create a patch Set.
71 // The patch text typically comprises a textual header and a sequence
72 // of file patches, as would be generated by CVS, Subversion,
73 // Mercurial, or Git.
74 func Parse(text []byte) (*Set, os.Error) {
75 // Split text into files.
76 // CVS and Subversion begin new files with
77 // Index: file name.
78 // ==================
79 // diff -u blah blah
81 // Mercurial and Git use
82 // diff [--git] a/file/path b/file/path.
84 // First look for Index: lines. If none, fall back on diff lines.
85 text, files := sections(text, "Index: ")
86 if len(files) == 0 {
87 text, files = sections(text, "diff ")
90 set := &Set{string(text), make([]*File, len(files))}
92 // Parse file header and then
93 // parse files into patch chunks.
94 // Each chunk begins with @@.
95 for i, raw := range files {
96 p := new(File)
97 set.File[i] = p
99 // First line of hdr is the Index: that
100 // begins the section. After that is the file name.
101 s, raw, _ := getLine(raw, 1)
102 if hasPrefix(s, "Index: ") {
103 p.Dst = string(bytes.TrimSpace(s[7:]))
104 goto HaveName
105 } else if hasPrefix(s, "diff ") {
106 str := string(bytes.TrimSpace(s))
107 i := strings.LastIndex(str, " b/")
108 if i >= 0 {
109 p.Dst = str[i+3:]
110 goto HaveName
113 return nil, SyntaxError("unexpected patch header line: " + string(s))
114 HaveName:
115 p.Dst = path.Clean(p.Dst)
116 if strings.HasPrefix(p.Dst, "../") || strings.HasPrefix(p.Dst, "/") {
117 return nil, SyntaxError("invalid path: " + p.Dst)
120 // Parse header lines giving file information:
121 // new file mode %o - file created
122 // deleted file mode %o - file deleted
123 // old file mode %o - file mode changed
124 // new file mode %o - file mode changed
125 // rename from %s - file renamed from other file
126 // rename to %s
127 // copy from %s - file copied from other file
128 // copy to %s
129 p.Verb = Edit
130 for len(raw) > 0 {
131 oldraw := raw
132 var l []byte
133 l, raw, _ = getLine(raw, 1)
134 l = bytes.TrimSpace(l)
135 if m, s, ok := atoi(l, "new file mode ", 8); ok && len(s) == 0 {
136 p.NewMode = m
137 p.Verb = Add
138 continue
140 if m, s, ok := atoi(l, "deleted file mode ", 8); ok && len(s) == 0 {
141 p.OldMode = m
142 p.Verb = Delete
143 p.Src = p.Dst
144 p.Dst = ""
145 continue
147 if m, s, ok := atoi(l, "old file mode ", 8); ok && len(s) == 0 {
148 // usually implies p.Verb = "rename" or "copy"
149 // but we'll get that from the rename or copy line.
150 p.OldMode = m
151 continue
153 if m, s, ok := atoi(l, "old mode ", 8); ok && len(s) == 0 {
154 p.OldMode = m
155 continue
157 if m, s, ok := atoi(l, "new mode ", 8); ok && len(s) == 0 {
158 p.NewMode = m
159 continue
161 if s, ok := skip(l, "rename from "); ok && len(s) > 0 {
162 p.Src = string(s)
163 p.Verb = Rename
164 continue
166 if s, ok := skip(l, "rename to "); ok && len(s) > 0 {
167 p.Verb = Rename
168 continue
170 if s, ok := skip(l, "copy from "); ok && len(s) > 0 {
171 p.Src = string(s)
172 p.Verb = Copy
173 continue
175 if s, ok := skip(l, "copy to "); ok && len(s) > 0 {
176 p.Verb = Copy
177 continue
179 if s, ok := skip(l, "Binary file "); ok && len(s) > 0 {
180 // Hg prints
181 // Binary file foo has changed
182 // when deleting a binary file.
183 continue
185 if s, ok := skip(l, "RCS file: "); ok && len(s) > 0 {
186 // CVS prints
187 // RCS file: /cvs/plan9/bin/yesterday,v
188 // retrieving revision 1.1
189 // for each file.
190 continue
192 if s, ok := skip(l, "retrieving revision "); ok && len(s) > 0 {
193 // CVS prints
194 // RCS file: /cvs/plan9/bin/yesterday,v
195 // retrieving revision 1.1
196 // for each file.
197 continue
199 if hasPrefix(l, "===") || hasPrefix(l, "---") || hasPrefix(l, "+++") || hasPrefix(l, "diff ") {
200 continue
202 if hasPrefix(l, "@@ -") {
203 diff, err := ParseTextDiff(oldraw)
204 if err != nil {
205 return nil, err
207 p.Diff = diff
208 break
210 if hasPrefix(l, "GIT binary patch") || (hasPrefix(l, "index ") && !hasPrefix(raw, "--- ")) {
211 diff, err := ParseGitBinary(oldraw)
212 if err != nil {
213 return nil, err
215 p.Diff = diff
216 break
218 if hasPrefix(l, "index ") {
219 continue
221 return nil, SyntaxError("unexpected patch header line: " + string(l))
223 if p.Diff == nil {
224 p.Diff = NoDiff
226 if p.Verb == Edit {
227 p.Src = p.Dst
231 return set, nil
234 // getLine returns the first n lines of data and the remainder.
235 // If data has no newline, getLine returns data, nil, false
236 func getLine(data []byte, n int) (first []byte, rest []byte, ok bool) {
237 rest = data
238 ok = true
239 for ; n > 0; n-- {
240 nl := bytes.Index(rest, newline)
241 if nl < 0 {
242 rest = nil
243 ok = false
244 break
246 rest = rest[nl+1:]
248 first = data[0 : len(data)-len(rest)]
249 return
252 // sections returns a collection of file sections,
253 // each of which begins with a line satisfying prefix.
254 // text before the first instance of such a line is
255 // returned separately.
256 func sections(text []byte, prefix string) ([]byte, [][]byte) {
257 n := 0
258 for b := text; ; {
259 if hasPrefix(b, prefix) {
262 nl := bytes.Index(b, newline)
263 if nl < 0 {
264 break
266 b = b[nl+1:]
269 sect := make([][]byte, n+1)
270 n = 0
271 for b := text; ; {
272 if hasPrefix(b, prefix) {
273 sect[n] = text[0 : len(text)-len(b)]
275 text = b
277 nl := bytes.Index(b, newline)
278 if nl < 0 {
279 sect[n] = text
280 break
282 b = b[nl+1:]
284 return sect[0], sect[1:]
287 // if s begins with the prefix t, skip returns
288 // s with that prefix removed and ok == true.
289 func skip(s []byte, t string) (ss []byte, ok bool) {
290 if len(s) < len(t) || string(s[0:len(t)]) != t {
291 return nil, false
293 return s[len(t):], true
296 // if s begins with the prefix t and then is a sequence
297 // of digits in the given base, atoi returns the number
298 // represented by the digits and s with the
299 // prefix and the digits removed.
300 func atoi(s []byte, t string, base int) (n int, ss []byte, ok bool) {
301 if s, ok = skip(s, t); !ok {
302 return
304 var i int
305 for i = 0; i < len(s) && '0' <= s[i] && s[i] <= byte('0'+base-1); i++ {
306 n = n*base + int(s[i]-'0')
308 if i == 0 {
309 return
311 return n, s[i:], true
314 // hasPrefix returns true if s begins with t.
315 func hasPrefix(s []byte, t string) bool {
316 _, ok := skip(s, t)
317 return ok
320 // splitLines returns the result of splitting s into lines.
321 // The \n on each line is preserved.
322 func splitLines(s []byte) [][]byte { return bytes.SplitAfter(s, newline, -1) }