fork of go-gitdiff with jj support
1package gitdiff
2
3import (
4 "errors"
5 "io"
6)
7
8// LineReaderAt is the interface that wraps the ReadLinesAt method.
9//
10// ReadLinesAt reads len(lines) into lines starting at line offset in the
11// input source. It returns number of full lines read (0 <= n <= len(lines))
12// and any error encountered. Line numbers are zero-indexed.
13//
14// If n < len(lines), ReadLinesAt returns a non-nil error explaining why more
15// lines were not returned.
16//
17// Each full line includes the line ending character(s). If the last line of
18// the input does not have a line ending character, ReadLinesAt returns the
19// content of the line and io.EOF.
20//
21// If the content of the input source changes after the first call to
22// ReadLinesAt, the behavior of future calls is undefined.
23type LineReaderAt interface {
24 ReadLinesAt(lines [][]byte, offset int64) (n int, err error)
25}
26
27type lineReaderAt struct {
28 r io.ReaderAt
29 index []int64
30 eof bool
31}
32
33func (r *lineReaderAt) ReadLinesAt(lines [][]byte, offset int64) (n int, err error) {
34 if offset < 0 {
35 return 0, errors.New("ReadLinesAt: negative offset")
36 }
37 if len(lines) == 0 {
38 return 0, nil
39 }
40
41 count := len(lines)
42 startLine := offset
43 endLine := startLine + int64(count)
44
45 if endLine > int64(len(r.index)) && !r.eof {
46 if err := r.indexTo(endLine); err != nil {
47 return 0, err
48 }
49 }
50 if startLine >= int64(len(r.index)) {
51 return 0, io.EOF
52 }
53
54 buf, byteOffset, err := r.readBytes(startLine, int64(count))
55 if err != nil {
56 return 0, err
57 }
58
59 for n = 0; n < count && startLine+int64(n) < int64(len(r.index)); n++ {
60 lineno := startLine + int64(n)
61 start, end := int64(0), r.index[lineno]-byteOffset
62 if lineno > 0 {
63 start = r.index[lineno-1] - byteOffset
64 }
65 lines[n] = buf[start:end]
66 }
67
68 if n < count || buf[len(buf)-1] != '\n' {
69 return n, io.EOF
70 }
71 return n, nil
72}
73
74// indexTo reads data and computes the line index until there is information
75// for line or a read returns io.EOF. It returns an error if and only if there
76// is an error reading data.
77func (r *lineReaderAt) indexTo(line int64) error {
78 var buf [1024]byte
79
80 var offset int64
81 if len(r.index) > 0 {
82 offset = r.index[len(r.index)-1]
83 }
84
85 for int64(len(r.index)) < line {
86 n, err := r.r.ReadAt(buf[:], offset)
87 if err != nil && err != io.EOF {
88 return err
89 }
90 for _, b := range buf[:n] {
91 offset++
92 if b == '\n' {
93 r.index = append(r.index, offset)
94 }
95 }
96 if err == io.EOF {
97 if n > 0 && buf[n-1] != '\n' {
98 r.index = append(r.index, offset)
99 }
100 r.eof = true
101 break
102 }
103 }
104 return nil
105}
106
107// readBytes reads the bytes of the n lines starting at line and returns the
108// bytes and the offset of the first byte in the underlying source.
109func (r *lineReaderAt) readBytes(line, n int64) (b []byte, offset int64, err error) {
110 indexLen := int64(len(r.index))
111
112 var size int64
113 if line > indexLen {
114 offset = r.index[indexLen-1]
115 } else if line > 0 {
116 offset = r.index[line-1]
117 }
118 if n > 0 {
119 if line+n > indexLen {
120 size = r.index[indexLen-1] - offset
121 } else {
122 size = r.index[line+n-1] - offset
123 }
124 }
125
126 b = make([]byte, size)
127 if _, err := r.r.ReadAt(b, offset); err != nil {
128 if err == io.EOF {
129 err = errors.New("ReadLinesAt: corrupt line index or changed source data")
130 }
131 return nil, 0, err
132 }
133 return b, offset, nil
134}
135
136func isLen(r io.ReaderAt, n int64) (bool, error) {
137 off := n - 1
138 if off < 0 {
139 off = 0
140 }
141
142 var b [2]byte
143 nr, err := r.ReadAt(b[:], off)
144 if err == io.EOF {
145 return (n == 0 && nr == 0) || (n > 0 && nr == 1), nil
146 }
147 return false, err
148}
149
150const (
151 byteBufferSize = 32 * 1024 // from io.Copy
152 lineBufferSize = 32
153)
154
155// copyFrom writes bytes starting from offset off in src to dst stopping at the
156// end of src or at the first error. copyFrom returns the number of bytes
157// written and any error.
158func copyFrom(dst io.Writer, src io.ReaderAt, off int64) (written int64, err error) {
159 buf := make([]byte, byteBufferSize)
160 for {
161 nr, rerr := src.ReadAt(buf, off)
162 if nr > 0 {
163 nw, werr := dst.Write(buf[0:nr])
164 if nw > 0 {
165 written += int64(nw)
166 }
167 if werr != nil {
168 err = werr
169 break
170 }
171 if nr != nw {
172 err = io.ErrShortWrite
173 break
174 }
175 off += int64(nr)
176 }
177 if rerr != nil {
178 if rerr != io.EOF {
179 err = rerr
180 }
181 break
182 }
183 }
184 return written, err
185}
186
187// copyLinesFrom writes lines starting from line off in src to dst stopping at
188// the end of src or at the first error. copyLinesFrom returns the number of
189// lines written and any error.
190func copyLinesFrom(dst io.Writer, src LineReaderAt, off int64) (written int64, err error) {
191 buf := make([][]byte, lineBufferSize)
192ReadLoop:
193 for {
194 nr, rerr := src.ReadLinesAt(buf, off)
195 if nr > 0 {
196 for _, line := range buf[0:nr] {
197 nw, werr := dst.Write(line)
198 if nw > 0 {
199 written++
200 }
201 if werr != nil {
202 err = werr
203 break ReadLoop
204 }
205 if len(line) != nw {
206 err = io.ErrShortWrite
207 break ReadLoop
208 }
209 }
210 off += int64(nr)
211 }
212 if rerr != nil {
213 if rerr != io.EOF {
214 err = rerr
215 }
216 break
217 }
218 }
219 return written, err
220}