Select the types of activity you want to include in your feed.
Implement text application using LineReaderAt
This is functionally equivalent to the previous version (except for one error case), but uses the new interface. I think the code is simpler overall because it removes the line tracking.
···3939// additional location information, if it is available.
4040type ApplyError struct {
4141 // Line is the one-indexed line number in the source data
4242- Line int
4242+ Line int64
4343 // Fragment is the one-indexed fragment number in the file
4444 Fragment int
4545 // FragmentLine is the one-indexed line number in the fragment
···7575 for _, arg := range args {
7676 switch v := arg.(type) {
7777 case lineNum:
7878- e.Line = int(v) + 1
7878+ e.Line = int64(v) + 1
7979 case fragNum:
8080 e.Fragment = int(v) + 1
8181 case fragLineNum:
···9292// If the apply fails, ApplyStrict returns an *ApplyError wrapping the cause.
9393// Partial data may be written to dst in this case.
9494func (f *File) ApplyStrict(dst io.Writer, src io.Reader) error {
9595+ // TODO(bkeyes): take an io.ReaderAt and avoid this!
9696+ data, err := ioutil.ReadAll(src)
9797+ if err != nil {
9898+ return applyError(err)
9999+ }
100100+95101 if f.IsBinary {
9696- data, err := ioutil.ReadAll(src)
9797- if err != nil {
9898- return applyError(err)
9999- }
100102 if f.BinaryFragment != nil {
101103 return f.BinaryFragment.Apply(dst, bytes.NewReader(data))
102104 }
···104106 return applyError(err)
105107 }
106108107107- lr, ok := src.(LineReader)
108108- if !ok {
109109- lr = NewLineReader(src, 0)
110110- }
109109+ // TODO(bkeyes): check for this conflict case
110110+ // &Conflict{"cannot create new file from non-empty src"}
111111+112112+ lra := NewLineReaderAt(bytes.NewReader(data))
111113114114+ var next int64
112115 for i, frag := range f.TextFragments {
113113- if err := frag.ApplyStrict(dst, lr); err != nil {
116116+ next, err = frag.ApplyStrict(dst, lra, next)
117117+ if err != nil {
114118 return applyError(err, fragNum(i))
115119 }
116120 }
117121118118- _, err := io.Copy(dst, unwrapLineReader(lr))
119119- return applyError(err)
122122+ // TODO(bkeyes): extract this to a utility
123123+ buf := make([][]byte, 64)
124124+ for {
125125+ n, err := lra.ReadLinesAt(buf, next)
126126+ if err != nil && err != io.EOF {
127127+ return applyError(err, lineNum(next+int64(n)))
128128+ }
129129+130130+ for i := 0; i < n; i++ {
131131+ if _, err := dst.Write(buf[n]); err != nil {
132132+ return applyError(err, lineNum(next+int64(n)))
133133+ }
134134+ }
135135+136136+ next += int64(n)
137137+ if n < len(buf) {
138138+ return nil
139139+ }
140140+ }
120141}
121142122122-// ApplyStrict writes data from src to dst, modifying it as described by the
123123-// fragment. The fragment, including all context lines, must exactly match src
124124-// at the expected line number.
125125-//
126126-// If the apply fails, ApplyStrict returns an *ApplyError wrapping the cause.
127127-// Partial data may be written to dst in this case. If there is no error, the
128128-// next read from src returns the line immediately after the last line of the
129129-// fragment.
130130-func (f *TextFragment) ApplyStrict(dst io.Writer, src LineReader) error {
143143+// ApplyStrict copies from src to dst, from line start through then end of the
144144+// fragment, modifying the data as described by the fragment. The fragment,
145145+// including all context lines, must exactly match src at the expected line
146146+// number. ApplyStrict returns the number of the next unprocessed line in src
147147+// and any error. When the error is not non-nil, partial data may be written.
148148+func (f *TextFragment) ApplyStrict(dst io.Writer, src LineReaderAt, start int64) (next int64, err error) {
131149 // application code assumes fragment fields are consistent
132150 if err := f.Validate(); err != nil {
133133- return applyError(err)
151151+ return start, applyError(err)
152152+ }
153153+154154+ // lines are 0-indexed, positions are 1-indexed (but new files have position = 0)
155155+ fragStart := f.OldPosition - 1
156156+ if fragStart < 0 {
157157+ fragStart = 0
158158+ }
159159+ fragEnd := fragStart + f.OldLines
160160+161161+ if fragStart < start {
162162+ return start, applyError(&Conflict{"fragment overlaps with an applied fragment"})
134163 }
135164136136- // line numbers are zero-indexed, positions are one-indexed
137137- limit := f.OldPosition - 1
165165+ preimage := make([][]byte, fragEnd-start)
166166+ n, err := src.ReadLinesAt(preimage, start)
167167+ switch {
168168+ case err == nil:
169169+ case err == io.EOF && n == len(preimage): // last line of frag has no newline character
170170+ default:
171171+ return start, applyError(err, lineNum(start+int64(n)))
172172+ }
138173139139- // io.EOF is acceptable here: the first line of the patch is the last of
140140- // the source and it has no newline character
141141- nextLine, n, err := copyLines(dst, src, limit)
142142- if err != nil && err != io.EOF {
143143- return applyError(err, lineNum(n))
174174+ // copy leading data before the fragment starts
175175+ for i, line := range preimage[:fragStart-start] {
176176+ if _, err := dst.Write(line); err != nil {
177177+ next = start + int64(i)
178178+ return next, applyError(err, lineNum(next))
179179+ }
144180 }
181181+ preimage = preimage[fragStart-start:]
145182183183+ // apply the changes in the fragment
146184 used := int64(0)
147185 for i, line := range f.Lines {
148148- if err := applyTextLine(dst, nextLine, line); err != nil {
149149- return applyError(err, lineNum(n), fragLineNum(i))
186186+ if err := applyTextLine(dst, line, preimage, used); err != nil {
187187+ next = fragStart + used
188188+ return next, applyError(err, lineNum(next), fragLineNum(i))
150189 }
151190 if line.Old() {
152191 used++
153192 }
154154- // advance reader if the next fragment line appears in src and we're behind
155155- if i < len(f.Lines)-1 && f.Lines[i+1].Old() && int64(n)-limit < used {
156156- nextLine, n, err = src.ReadLine()
157157- switch {
158158- case err == io.EOF && f.Lines[i+1].NoEOL():
159159- continue
160160- case err != nil:
161161- return applyError(err, lineNum(n), fragLineNum(i+1)) // report for _next_ line in fragment
162162- }
163163- }
164193 }
165165-166166- return nil
194194+ return fragStart + used, nil
167195}
168196169169-func applyTextLine(dst io.Writer, src string, line Line) (err error) {
170170- switch line.Op {
171171- case OpContext, OpDelete:
172172- if src != line.Line {
173173- return &Conflict{"fragment line does not match src line"}
174174- }
197197+func applyTextLine(dst io.Writer, line Line, preimage [][]byte, i int64) (err error) {
198198+ if line.Old() && string(preimage[i]) != line.Line {
199199+ return &Conflict{"fragment line does not match src line"}
175200 }
176176- switch line.Op {
177177- case OpContext, OpAdd:
201201+ if line.New() {
178202 _, err = io.WriteString(dst, line.Line)
179203 }
180204 return
181181-}
182182-183183-// copyLines copies from src to dst until the line at limit, exclusive. Returns
184184-// the line at limit and the line number. If the error is nil or io.EOF, the
185185-// line number equals limit. A negative limit checks that the source has no
186186-// more lines to read.
187187-func copyLines(dst io.Writer, src LineReader, limit int64) (string, int64, error) {
188188- for {
189189- line, n, err := src.ReadLine()
190190- switch {
191191- case limit < 0 && err == io.EOF && line == "":
192192- return "", limit, nil
193193- case n == limit:
194194- return line, n, err
195195- case n > limit:
196196- if limit < 0 {
197197- return "", n, &Conflict{"cannot create new file from non-empty src"}
198198- }
199199- return "", n, &Conflict{"fragment overlaps with an applied fragment"}
200200- case err != nil:
201201- return line, n, wrapEOF(err)
202202- }
203203-204204- if _, err := io.WriteString(dst, line); err != nil {
205205- return "", n, err
206206- }
207207- }
208205}
209206210207// Apply writes data from src to dst, modifying it as described by the
···139139140140// Old returns true if the line appears in the old content of the fragment.
141141func (fl Line) Old() bool {
142142- return fl.Op != OpAdd
142142+ return fl.Op == OpContext || fl.Op == OpDelete
143143}
144144145145// New returns true if the line appears in the new content of the fragment.
146146func (fl Line) New() bool {
147147- return fl.Op == OpAdd
147147+ return fl.Op == OpContext || fl.Op == OpAdd
148148}
149149150150// NoEOL returns true if the line is missing a trailing newline character.
+5-11
gitdiff/io.go
···128128 return 0, io.EOF
129129 }
130130131131- // TODO(bkeyes): check usage of int / int64
132132- // - interface uses int64 for arbitrarily large files
133133- // - implementation is limited to int lines by index array
134134-135135- // offset <= len(r.index) means that it must fit in int without loss
136136- size, readOffset := lookupLines(r.index, int(offset), len(lines))
131131+ size, readOffset := lookupLines(r.index, offset, int64(len(lines)))
137132138133 b := make([]byte, size)
139134 if _, err := r.r.ReadAt(b, readOffset); err != nil {
···144139 }
145140146141 for n = 0; n < len(lines) && offset+int64(n) < int64(len(r.index)); n++ {
147147- i := int(offset) + n
142142+ i := offset + int64(n)
148143 start, end := readOffset, r.index[i]
149144 if i > 0 {
150145 start = r.index[i-1]
···193188194189// lookupLines gets the byte offset and size of a range of lines from an index
195190// where the value at n is the offset of the first byte after line number n.
196196-func lookupLines(index []int64, start, n int) (size int64, offset int64) {
197197- if start > len(index) {
191191+func lookupLines(index []int64, start, n int64) (size int64, offset int64) {
192192+ if start > int64(len(index)) {
198193 offset = index[len(index)-1]
199194 } else if start > 0 {
200195 offset = index[start-1]
201196 }
202197 if n > 0 {
203203- // TODO(bkeyes): check types for overflow
204204- if start+n > len(index) {
198198+ if start+n > int64(len(index)) {
205199 size = index[len(index)-1] - offset
206200 } else {
207201 size = index[start+n-1] - offset