mirror of
https://github.com/bluekeyes/go-gitdiff
synced 2026-06-08 13:18:30 +00:00
18058d1e15
This removes the distinction between "strict" and "fuzzy" application by allowing future methods on Applier that control settings. It also avoids state tracking in the text fragment apply signature by moving it into the Applier type. While in practice, an Applier will be used once and discarded, the capability is provided to reset it for multiple uses.
206 lines
4.8 KiB
Go
206 lines
4.8 KiB
Go
package gitdiff
|
|
|
|
import (
|
|
"errors"
|
|
"io"
|
|
)
|
|
|
|
// LineReaderAt is the interface that wraps the ReadLinesAt method.
|
|
//
|
|
// ReadLinesAt reads len(lines) into lines starting at line offset in the
|
|
// input source. It returns number of full lines read (0 <= n <= len(lines))
|
|
// and any error encountered. Line numbers are zero-indexed.
|
|
//
|
|
// If n < len(lines), ReadLinesAt returns a non-nil error explaining why more
|
|
// lines were not returned.
|
|
//
|
|
// Each full line includes the line ending character(s). If the last line of
|
|
// the input does not have a line ending character, ReadLinesAt returns the
|
|
// content of the line and io.EOF.
|
|
//
|
|
// If the content of the input source changes after the first call to
|
|
// ReadLinesAt, the behavior of future calls is undefined.
|
|
type LineReaderAt interface {
|
|
ReadLinesAt(lines [][]byte, offset int64) (n int, err error)
|
|
}
|
|
|
|
type lineReaderAt struct {
|
|
r io.ReaderAt
|
|
index []int64
|
|
eof bool
|
|
}
|
|
|
|
func (r *lineReaderAt) ReadLinesAt(lines [][]byte, offset int64) (n int, err error) {
|
|
// TODO(bkeyes): revisit variable names
|
|
// - it's generally not clear when something is bytes vs lines
|
|
// - offset is a good example of this
|
|
|
|
if len(lines) == 0 {
|
|
return 0, nil
|
|
}
|
|
|
|
endLine := offset + int64(len(lines))
|
|
if endLine > int64(len(r.index)) && !r.eof {
|
|
if err := r.indexTo(endLine); err != nil {
|
|
return 0, err
|
|
}
|
|
}
|
|
if offset > int64(len(r.index)) {
|
|
return 0, io.EOF
|
|
}
|
|
|
|
size, readOffset := lookupLines(r.index, offset, int64(len(lines)))
|
|
|
|
b := make([]byte, size)
|
|
if _, err := r.r.ReadAt(b, readOffset); err != nil {
|
|
if err == io.EOF {
|
|
err = errors.New("ReadLinesAt: corrupt line index or changed source data")
|
|
}
|
|
return 0, err
|
|
}
|
|
|
|
for n = 0; n < len(lines) && offset+int64(n) < int64(len(r.index)); n++ {
|
|
i := offset + int64(n)
|
|
start, end := readOffset, r.index[i]
|
|
if i > 0 {
|
|
start = r.index[i-1]
|
|
}
|
|
lines[n] = b[start-readOffset : end-readOffset]
|
|
}
|
|
|
|
if n < len(lines) || b[size-1] != '\n' {
|
|
return n, io.EOF
|
|
}
|
|
return n, nil
|
|
}
|
|
|
|
// indexTo reads data and computes the line index until there is information
|
|
// for line or a read returns io.EOF. It returns an error if and only if there
|
|
// is an error reading data.
|
|
func (r *lineReaderAt) indexTo(line int64) error {
|
|
var buf [1024]byte
|
|
|
|
var offset int64
|
|
if len(r.index) > 0 {
|
|
offset = r.index[len(r.index)-1]
|
|
}
|
|
|
|
for int64(len(r.index)) < line {
|
|
n, err := r.r.ReadAt(buf[:], offset)
|
|
if err != nil && err != io.EOF {
|
|
return err
|
|
}
|
|
for _, b := range buf[:n] {
|
|
offset++
|
|
if b == '\n' {
|
|
r.index = append(r.index, offset)
|
|
}
|
|
}
|
|
if err == io.EOF {
|
|
if n > 0 && buf[n-1] != '\n' {
|
|
r.index = append(r.index, offset)
|
|
}
|
|
r.eof = true
|
|
break
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// lookupLines gets the byte offset and size of a range of lines from an index
|
|
// where the value at n is the offset of the first byte after line number n.
|
|
func lookupLines(index []int64, start, n int64) (size int64, offset int64) {
|
|
if start > int64(len(index)) {
|
|
offset = index[len(index)-1]
|
|
} else if start > 0 {
|
|
offset = index[start-1]
|
|
}
|
|
if n > 0 {
|
|
if start+n > int64(len(index)) {
|
|
size = index[len(index)-1] - offset
|
|
} else {
|
|
size = index[start+n-1] - offset
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
func isLen(r io.ReaderAt, n int64) (bool, error) {
|
|
off := n - 1
|
|
if off < 0 {
|
|
off = 0
|
|
}
|
|
|
|
var b [2]byte
|
|
nr, err := r.ReadAt(b[:], off)
|
|
if err == io.EOF {
|
|
return (n == 0 && nr == 0) || (n > 0 && nr == 1), nil
|
|
}
|
|
return false, err
|
|
}
|
|
|
|
// copyFrom writes bytes starting from offset off in src to dst stopping at the
|
|
// end of src or at the first error. copyFrom returns the number of bytes
|
|
// written and any error.
|
|
func copyFrom(dst io.Writer, src io.ReaderAt, off int64) (written int64, err error) {
|
|
buf := make([]byte, 32*1024) // stolen from io.Copy
|
|
for {
|
|
nr, rerr := src.ReadAt(buf, off)
|
|
if nr > 0 {
|
|
nw, werr := dst.Write(buf[0:nr])
|
|
if nw > 0 {
|
|
written += int64(nw)
|
|
}
|
|
if werr != nil {
|
|
err = werr
|
|
break
|
|
}
|
|
if nr != nw {
|
|
err = io.ErrShortWrite
|
|
break
|
|
}
|
|
}
|
|
if rerr != nil {
|
|
if rerr != io.EOF {
|
|
err = rerr
|
|
}
|
|
break
|
|
}
|
|
}
|
|
return written, err
|
|
}
|
|
|
|
// copyLinesFrom writes lines starting from line off in src to dst stopping at
|
|
// the end of src or at the first error. copyLinesFrom returns the number of
|
|
// lines written and any error.
|
|
func copyLinesFrom(dst io.Writer, src LineReaderAt, off int64) (written int64, err error) {
|
|
buf := make([][]byte, 32)
|
|
ReadLoop:
|
|
for {
|
|
nr, rerr := src.ReadLinesAt(buf, off)
|
|
if nr > 0 {
|
|
for _, line := range buf[0:nr] {
|
|
nw, werr := dst.Write(line)
|
|
if nw > 0 {
|
|
written++
|
|
}
|
|
if werr != nil {
|
|
err = werr
|
|
break ReadLoop
|
|
}
|
|
if len(line) != nw {
|
|
err = io.ErrShortWrite
|
|
break ReadLoop
|
|
}
|
|
}
|
|
}
|
|
if rerr != nil {
|
|
if rerr != io.EOF {
|
|
err = rerr
|
|
}
|
|
break
|
|
}
|
|
}
|
|
return written, err
|
|
}
|