mirror of
https://github.com/bluekeyes/go-gitdiff
synced 2026-06-08 13:18:30 +00:00
09f3004fce
copyFrom and copyLinesFrom did not increment the start offset after reading, so the same lines were copied over and over again.
221 lines
4.9 KiB
Go
221 lines
4.9 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) {
|
|
if offset < 0 {
|
|
return 0, errors.New("ReadLinesAt: negative offset")
|
|
}
|
|
if len(lines) == 0 {
|
|
return 0, nil
|
|
}
|
|
|
|
count := len(lines)
|
|
startLine := offset
|
|
endLine := startLine + int64(count)
|
|
|
|
if endLine > int64(len(r.index)) && !r.eof {
|
|
if err := r.indexTo(endLine); err != nil {
|
|
return 0, err
|
|
}
|
|
}
|
|
if startLine >= int64(len(r.index)) {
|
|
return 0, io.EOF
|
|
}
|
|
|
|
buf, byteOffset, err := r.readBytes(startLine, int64(count))
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
for n = 0; n < count && startLine+int64(n) < int64(len(r.index)); n++ {
|
|
lineno := startLine + int64(n)
|
|
start, end := int64(0), r.index[lineno]-byteOffset
|
|
if lineno > 0 {
|
|
start = r.index[lineno-1] - byteOffset
|
|
}
|
|
lines[n] = buf[start:end]
|
|
}
|
|
|
|
if n < count || buf[len(buf)-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
|
|
}
|
|
|
|
// readBytes reads the bytes of the n lines starting at line and returns the
|
|
// bytes and the offset of the first byte in the underlying source.
|
|
func (r *lineReaderAt) readBytes(line, n int64) (b []byte, offset int64, err error) {
|
|
indexLen := int64(len(r.index))
|
|
|
|
var size int64
|
|
if line > indexLen {
|
|
offset = r.index[indexLen-1]
|
|
} else if line > 0 {
|
|
offset = r.index[line-1]
|
|
}
|
|
if n > 0 {
|
|
if line+n > indexLen {
|
|
size = r.index[indexLen-1] - offset
|
|
} else {
|
|
size = r.index[line+n-1] - offset
|
|
}
|
|
}
|
|
|
|
b = make([]byte, size)
|
|
if _, err := r.r.ReadAt(b, offset); err != nil {
|
|
if err == io.EOF {
|
|
err = errors.New("ReadLinesAt: corrupt line index or changed source data")
|
|
}
|
|
return nil, 0, err
|
|
}
|
|
return b, offset, nil
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
const (
|
|
byteBufferSize = 32 * 1024 // from io.Copy
|
|
lineBufferSize = 32
|
|
)
|
|
|
|
// 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, byteBufferSize)
|
|
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
|
|
}
|
|
off += int64(nr)
|
|
}
|
|
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, lineBufferSize)
|
|
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
|
|
}
|
|
}
|
|
off += int64(nr)
|
|
}
|
|
if rerr != nil {
|
|
if rerr != io.EOF {
|
|
err = rerr
|
|
}
|
|
break
|
|
}
|
|
}
|
|
return written, err
|
|
}
|