diff options
| author | Russ Cox <rsc@golang.org> | 2009-11-04 17:55:06 -0800 | 
|---|---|---|
| committer | Russ Cox <rsc@golang.org> | 2009-11-04 17:55:06 -0800 | 
| commit | ce9b3deaa4fa22e9d5580df05e9762b988793baa (patch) | |
| tree | eacfeb41baaf6752deba91909b8f96d90dea476e /src/pkg/patch/textdiff.go | |
| parent | 782c3f76d1c7af511daf0d44ba29737f3e1de655 (diff) | |
| download | golang-ce9b3deaa4fa22e9d5580df05e9762b988793baa.tar.gz | |
package patch
R=r
http://go/go-review/1018043
Diffstat (limited to 'src/pkg/patch/textdiff.go')
| -rw-r--r-- | src/pkg/patch/textdiff.go | 171 | 
1 files changed, 171 insertions, 0 deletions
| diff --git a/src/pkg/patch/textdiff.go b/src/pkg/patch/textdiff.go new file mode 100644 index 000000000..db8527682 --- /dev/null +++ b/src/pkg/patch/textdiff.go @@ -0,0 +1,171 @@ +package patch + +import ( +	"bytes"; +	"os"; +) + +type TextDiff []TextChunk + +// A TextChunk specifies an edit to a section of a file: +// the text beginning at Line, which should be exactly Old, +// is to be replaced with New. +type TextChunk struct { +	Line	int; +	Old	[]byte; +	New	[]byte; +} + +func ParseTextDiff(raw []byte) (TextDiff, os.Error) { +	// Copy raw so it is safe to keep references to slices. +	_, chunks := sections(raw, "@@ -"); +	delta := 0; +	diff := make(TextDiff, len(chunks)); +	for i, raw := range chunks { +		c := &diff[i]; + +		// Parse start line: @@ -oldLine,oldCount +newLine,newCount @@ junk +		chunk := splitLines(raw); +		chunkHeader := chunk[0]; +		var ok bool; +		var oldLine, oldCount, newLine, newCount int; +		s := chunkHeader; +		if oldLine, s, ok = atoi(s, "@@ -", 10); !ok { +		ErrChunkHdr: +			return nil, SyntaxError("unexpected chunk header line: " + string(chunkHeader)); +		} +		if len(s) == 0 || s[0] != ',' { +			oldCount = 1; +		} else if oldCount, s, ok = atoi(s, ",", 10); !ok { +			goto ErrChunkHdr; +		} +		if newLine, s, ok = atoi(s, " +", 10); !ok { +			goto ErrChunkHdr; +		} +		if len(s) == 0 || s[0] != ',' { +			newCount = 1; +		} else if newCount, s, ok = atoi(s, ",", 10); !ok { +			goto ErrChunkHdr; +		} +		if !hasPrefix(s, " @@") { +			goto ErrChunkHdr; +		} + +		// Special case: for created or deleted files, the empty half +		// is given as starting at line 0.  Translate to line 1. +		if oldCount == 0 && oldLine == 0 { +			oldLine = 1; +		} +		if newCount == 0 && newLine == 0 { +			newLine = 1; +		} + +		// Count lines in text +		var dropOldNL, dropNewNL bool; +		var nold, nnew int; +		var lastch byte; +		chunk = chunk[1:len(chunk)]; +		for _, l := range chunk { +			if nold == oldCount && nnew == newCount && (len(l) == 0 || l[0] != '\\') { +				if len(bytes.TrimSpace(l)) != 0 { +					return nil, SyntaxError("too many chunk lines"); +				} +				continue; +			} +			if len(l) == 0 { +				return nil, SyntaxError("empty chunk line"); +			} +			switch l[0] { +			case '+': +				nnew++; +			case '-': +				nold++; +			case ' ': +				nnew++; +				nold++; +			case '\\': +				if _, ok := skip(l, "\\ No newline at end of file"); ok { +					switch lastch { +					case '-': +						dropOldNL = true; +					case '+': +						dropNewNL = true; +					case ' ': +						dropOldNL = true; +						dropNewNL = true; +					default: +						return nil, SyntaxError("message `\\ No newline at end of file' out of context"); +					} +					break; +				} +				fallthrough; +			default: +				return nil, SyntaxError("unexpected chunk line: " + string(l)); +			} +			lastch = l[0]; +		} + +		// Does it match the header? +		if nold != oldCount || nnew != newCount { +			return nil, SyntaxError("chunk header does not match line count: " + string(chunkHeader)); +		} +		if oldLine+delta != newLine { +			return nil, SyntaxError("chunk delta is out of sync with previous chunks"); +		} +		delta += nnew-nold; +		c.Line = oldLine; + +		var old, new bytes.Buffer; +		nold = 0; +		nnew = 0; +		for _, l := range chunk { +			if nold == oldCount && nnew == newCount { +				break; +			} +			ch, l := l[0], l[1:len(l)]; +			if ch == '\\' { +				continue; +			} +			if ch != '+' { +				old.Write(l); +				nold++; +			} +			if ch != '-' { +				new.Write(l); +				nnew++; +			} +		} +		c.Old = old.Bytes(); +		c.New = new.Bytes(); +		if dropOldNL { +			c.Old = c.Old[0 : len(c.Old)-1]; +		} +		if dropNewNL { +			c.New = c.New[0 : len(c.New)-1]; +		} +	} +	return diff, nil; +} + +var ErrPatchFailure = os.NewError("patch did not apply cleanly") + +// Apply applies the changes listed in the diff +// to the data, returning the new version. +func (d TextDiff) Apply(data []byte) ([]byte, os.Error) { +	var buf bytes.Buffer; +	line := 1; +	for _, c := range d { +		var ok bool; +		var prefix []byte; +		prefix, data, ok = getLine(data, c.Line - line); +		if !ok || !bytes.HasPrefix(data, c.Old) { +			return nil, ErrPatchFailure; +		} +		buf.Write(prefix); +		data = data[len(c.Old):len(data)]; +		buf.Write(c.New); +		line = c.Line + bytes.Count(c.Old, newline); +	} +	buf.Write(data); +	return buf.Bytes(), nil; +} | 
