@@ -39,7 +39,7 @@ func (c *Conflict) Is(other error) bool {
3939// additional location information, if it is available.
4040type ApplyError struct {
4141 // Line is the one-indexed line number in the source data
42- Line int
42+ 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
@@ -75,7 +75,7 @@ func applyError(err error, args ...interface{}) error {
7575 for _ , arg := range args {
7676 switch v := arg .(type ) {
7777 case lineNum :
78- e .Line = int (v ) + 1
78+ e .Line = int64 (v ) + 1
7979 case fragNum :
8080 e .Fragment = int (v ) + 1
8181 case fragLineNum :
@@ -92,121 +92,118 @@ func applyError(err error, args ...interface{}) error {
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 {
95+ // TODO(bkeyes): take an io.ReaderAt and avoid this!
96+ data , err := ioutil .ReadAll (src )
97+ if err != nil {
98+ return applyError (err )
99+ }
100+
95101 if f .IsBinary {
96- data , err := ioutil .ReadAll (src )
97- if err != nil {
98- return applyError (err )
99- }
100102 if f .BinaryFragment != nil {
101103 return f .BinaryFragment .Apply (dst , bytes .NewReader (data ))
102104 }
103105 _ , err = dst .Write (data )
104106 return applyError (err )
105107 }
106108
107- lr , ok := src .(LineReader )
108- if ! ok {
109- lr = NewLineReader (src , 0 )
110- }
109+ // TODO(bkeyes): check for this conflict case
110+ // &Conflict{"cannot create new file from non-empty src"}
111111
112+ lra := NewLineReaderAt (bytes .NewReader (data ))
113+
114+ var next int64
112115 for i , frag := range f .TextFragments {
113- if err := frag .ApplyStrict (dst , lr ); err != nil {
116+ next , err = frag .ApplyStrict (dst , lra , next )
117+ if err != nil {
114118 return applyError (err , fragNum (i ))
115119 }
116120 }
117121
118- _ , err := io .Copy (dst , unwrapLineReader (lr ))
119- return applyError (err )
122+ // TODO(bkeyes): extract this to a utility
123+ buf := make ([][]byte , 64 )
124+ for {
125+ n , err := lra .ReadLinesAt (buf , next )
126+ if err != nil && err != io .EOF {
127+ return applyError (err , lineNum (next + int64 (n )))
128+ }
129+
130+ for i := 0 ; i < n ; i ++ {
131+ if _ , err := dst .Write (buf [n ]); err != nil {
132+ return applyError (err , lineNum (next + int64 (n )))
133+ }
134+ }
135+
136+ next += int64 (n )
137+ if n < len (buf ) {
138+ return nil
139+ }
140+ }
120141}
121142
122- // ApplyStrict writes data from src to dst, modifying it as described by the
123- // fragment. The fragment, including all context lines, must exactly match src
124- // at the expected line number.
125- //
126- // If the apply fails, ApplyStrict returns an *ApplyError wrapping the cause.
127- // Partial data may be written to dst in this case. If there is no error, the
128- // next read from src returns the line immediately after the last line of the
129- // fragment.
130- func (f * TextFragment ) ApplyStrict (dst io.Writer , src LineReader ) error {
143+ // ApplyStrict copies from src to dst, from line start through then end of the
144+ // fragment, modifying the data as described by the fragment. The fragment,
145+ // including all context lines, must exactly match src at the expected line
146+ // number. ApplyStrict returns the number of the next unprocessed line in src
147+ // and any error. When the error is not non-nil, partial data may be written.
148+ 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 {
133- return applyError (err )
151+ return start , applyError (err )
134152 }
135153
136- // line numbers are zero-indexed, positions are one-indexed
137- limit := f .OldPosition - 1
154+ // lines are 0-indexed, positions are 1-indexed (but new files have position = 0)
155+ fragStart := f .OldPosition - 1
156+ if fragStart < 0 {
157+ fragStart = 0
158+ }
159+ fragEnd := fragStart + f .OldLines
138160
139- // io.EOF is acceptable here: the first line of the patch is the last of
140- // the source and it has no newline character
141- nextLine , n , err := copyLines (dst , src , limit )
142- if err != nil && err != io .EOF {
143- return applyError (err , lineNum (n ))
161+ if fragStart < start {
162+ return start , applyError (& Conflict {"fragment overlaps with an applied fragment" })
163+ }
164+
165+ preimage := make ([][]byte , fragEnd - start )
166+ n , err := src .ReadLinesAt (preimage , start )
167+ switch {
168+ case err == nil :
169+ case err == io .EOF && n == len (preimage ): // last line of frag has no newline character
170+ default :
171+ return start , applyError (err , lineNum (start + int64 (n )))
144172 }
145173
174+ // copy leading data before the fragment starts
175+ for i , line := range preimage [:fragStart - start ] {
176+ if _ , err := dst .Write (line ); err != nil {
177+ next = start + int64 (i )
178+ return next , applyError (err , lineNum (next ))
179+ }
180+ }
181+ preimage = preimage [fragStart - start :]
182+
183+ // apply the changes in the fragment
146184 used := int64 (0 )
147185 for i , line := range f .Lines {
148- if err := applyTextLine (dst , nextLine , line ); err != nil {
149- return applyError (err , lineNum (n ), fragLineNum (i ))
186+ if err := applyTextLine (dst , line , preimage , used ); err != nil {
187+ next = fragStart + used
188+ return next , applyError (err , lineNum (next ), fragLineNum (i ))
150189 }
151190 if line .Old () {
152191 used ++
153192 }
154- // advance reader if the next fragment line appears in src and we're behind
155- if i < len (f .Lines )- 1 && f .Lines [i + 1 ].Old () && int64 (n )- limit < used {
156- nextLine , n , err = src .ReadLine ()
157- switch {
158- case err == io .EOF && f .Lines [i + 1 ].NoEOL ():
159- continue
160- case err != nil :
161- return applyError (err , lineNum (n ), fragLineNum (i + 1 )) // report for _next_ line in fragment
162- }
163- }
164193 }
165-
166- return nil
194+ return fragStart + used , nil
167195}
168196
169- func applyTextLine (dst io.Writer , src string , line Line ) (err error ) {
170- switch line .Op {
171- case OpContext , OpDelete :
172- if src != line .Line {
173- return & Conflict {"fragment line does not match src line" }
174- }
197+ func applyTextLine (dst io.Writer , line Line , preimage [][]byte , i int64 ) (err error ) {
198+ if line .Old () && string (preimage [i ]) != line .Line {
199+ return & Conflict {"fragment line does not match src line" }
175200 }
176- switch line .Op {
177- case OpContext , OpAdd :
201+ if line .New () {
178202 _ , err = io .WriteString (dst , line .Line )
179203 }
180204 return
181205}
182206
183- // copyLines copies from src to dst until the line at limit, exclusive. Returns
184- // the line at limit and the line number. If the error is nil or io.EOF, the
185- // line number equals limit. A negative limit checks that the source has no
186- // more lines to read.
187- func copyLines (dst io.Writer , src LineReader , limit int64 ) (string , int64 , error ) {
188- for {
189- line , n , err := src .ReadLine ()
190- switch {
191- case limit < 0 && err == io .EOF && line == "" :
192- return "" , limit , nil
193- case n == limit :
194- return line , n , err
195- case n > limit :
196- if limit < 0 {
197- return "" , n , & Conflict {"cannot create new file from non-empty src" }
198- }
199- return "" , n , & Conflict {"fragment overlaps with an applied fragment" }
200- case err != nil :
201- return line , n , wrapEOF (err )
202- }
203-
204- if _ , err := io .WriteString (dst , line ); err != nil {
205- return "" , n , err
206- }
207- }
208- }
209-
210207// Apply writes data from src to dst, modifying it as described by the
211208// fragment.
212209//
0 commit comments