···11+package gitdiff
22+33+import (
44+ "errors"
55+ "io"
66+)
77+88+// ApplyStrict writes data from src to dst, modifying it as described by the
99+// fragments in the file. For text files, each fragment, including all context
1010+// lines, must exactly match src at the expected line number.
1111+//
1212+// If the file contains no fragments, ApplyStrict is equivalent to io.Copy.
1313+func (f *File) ApplyStrict(dst io.Writer, src io.Reader) error {
1414+ if f.IsBinary {
1515+ if f.BinaryFragment != nil {
1616+ return f.BinaryFragment.Apply(dst, src)
1717+ }
1818+ _, err := io.Copy(dst, src)
1919+ return err
2020+ }
2121+2222+ lr, ok := src.(LineReader)
2323+ if !ok {
2424+ lr = NewLineReader(src, 0)
2525+ }
2626+2727+ for _, frag := range f.TextFragments {
2828+ if err := frag.ApplyStrict(dst, lr); err != nil {
2929+ return err
3030+ }
3131+ }
3232+3333+ _, err := io.Copy(dst, unwrapLineReader(lr))
3434+ return err
3535+}
3636+3737+// ApplyStrict writes data from src to dst, modifying it as described by the
3838+// fragment. The fragment, including all context lines, must exactly match src
3939+// at the expected line number.
4040+//
4141+// If there is no error, the next read from src returns the line immediately
4242+// after the last line of the fragment.
4343+func (f *TextFragment) ApplyStrict(dst io.Writer, src LineReader) error {
4444+ // application code assumes fragment fields are consistent
4545+ if err := f.Validate(); err != nil {
4646+ // TODO(bkeyes): wrap with additional context
4747+ return err
4848+ }
4949+5050+ // line numbers are zero-indexed, positions are one-indexed
5151+ limit := f.OldPosition - 1
5252+5353+ // an EOF is allowed here: the fragment applies to the last line of the
5454+ // source but it does not have a newline character
5555+ nextLine, err := copyLines(dst, src, limit)
5656+ if err != nil && err != io.EOF {
5757+ // TODO(bkeyes): wrap with additional context
5858+ return err
5959+ }
6060+6161+ for i, line := range f.Lines {
6262+ fromSrc, err := applyTextLine(dst, nextLine, line)
6363+ if err != nil {
6464+ // TODO(bkeyes): wrap with additional context
6565+ return err
6666+ }
6767+6868+ if fromSrc && i < len(f.Lines)-1 {
6969+ nextLine, _, err = src.ReadLine()
7070+ if err != nil {
7171+ if err == io.EOF {
7272+ err = io.ErrUnexpectedEOF
7373+ }
7474+ // TODO(bkeyes): wrap with additional context
7575+ return err
7676+ }
7777+ }
7878+ }
7979+8080+ return nil
8181+}
8282+8383+func applyTextLine(dst io.Writer, srcLine string, line Line) (fromSrc bool, err error) {
8484+ switch line.Op {
8585+ case OpContext, OpDelete:
8686+ fromSrc = true
8787+ if srcLine != line.Line {
8888+ // TODO(bkeyes): use special error type here
8989+ // TODO(bkeyes): include line number information, etc.
9090+ return fromSrc, errors.New("apply: fragment match failed: line does not match")
9191+ }
9292+ }
9393+9494+ switch line.Op {
9595+ case OpContext, OpAdd:
9696+ // TODO(bkeyes): wrap with additional context
9797+ _, err = io.WriteString(dst, line.Line)
9898+ }
9999+ return
100100+}
101101+102102+// copyLines copies from src to dst until the line at limit, exclusive. The
103103+// line at limit is returned. A negative limit means the first read should
104104+// return io.EOF and no data.
105105+func copyLines(dst io.Writer, src LineReader, limit int64) (string, error) {
106106+ for {
107107+ line, n, err := src.ReadLine()
108108+ switch {
109109+ case limit < 0 && err == io.EOF && line == "":
110110+ return "", nil
111111+ case int64(n) == limit:
112112+ return line, err
113113+ case int64(n) > limit:
114114+ if limit < 0 {
115115+ return "", errors.New("src is not empty")
116116+ }
117117+ return "", errors.New("overlapping fragments")
118118+ case err != nil:
119119+ if err == io.EOF {
120120+ err = io.ErrUnexpectedEOF
121121+ }
122122+ return line, err
123123+ }
124124+125125+ if _, err := io.WriteString(dst, line); err != nil {
126126+ return "", err
127127+ }
128128+ }
129129+}
130130+131131+// Apply writes data from src to dst, modifying it as described by the
132132+// fragment.
133133+//
134134+// Unlike text fragments, binary fragments do not distinguish between strict
135135+// and non-strict application.
136136+func (f *BinaryFragment) Apply(dst io.Writer, src io.Reader) error {
137137+ panic("TODO(bkeyes): unimplemented")
138138+}
+4-2
gitdiff/io.go
···2727// ReadLine reads the next full line in the input, returing the the data
2828// including the line ending character(s) and the zero-indexed line number. If
2929// ReadLine encounters an error before reaching the end of the line, it returns
3030-// the data read before the error and the error itself (often io.EOF). ReadLine
3131-// returns err != nil if and only if the returned data is not a complete line.
3030+// the data read before the error, the number of the line, and the error itself
3131+// (often io.EOF). ReadLine returns err != nil if and only if the returned data
3232+// is not a complete line.
3233//
3334// If an implementation defines other methods for reading the same input, line
3435// numbers may be incorrect if calls to ReadLine are mixed with calls to other
3536// read methods.
3637type LineReader interface {
3838+ // TODO(bkeyes): consider making lineno int64 to match fragment types
3739 ReadLine() (string, int, error)
3840}
3941