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
150// copyFrom writes bytes starting from offset off in src to dst stopping at the
151// end of src or at the first error. copyFrom returns the number of bytes
152// written and any error.
153func copyFrom(dst io.Writer, src io.ReaderAt, off int64) (written int64, err error) {
154 buf := make([]byte, 32*1024) // stolen from io.Copy
155 for {
156 nr, rerr := src.ReadAt(buf, off)
157 if nr > 0 {
158 nw, werr := dst.Write(buf[0:nr])
159 if nw > 0 {
160 written += int64(nw)
161 }
162 if werr != nil {
163 err = werr
164 break
165 }
166 if nr != nw {
167 err = io.ErrShortWrite
168 break
169 }
170 }
171 if rerr != nil {
172 if rerr != io.EOF {
173 err = rerr
174 }
175 break
176 }
177 }
178 return written, err
179}
180
181// copyLinesFrom writes lines starting from line off in src to dst stopping at
182// the end of src or at the first error. copyLinesFrom returns the number of
183// lines written and any error.
184func copyLinesFrom(dst io.Writer, src LineReaderAt, off int64) (written int64, err error) {
185 buf := make([][]byte, 32)
186ReadLoop:
187 for {
188 nr, rerr := src.ReadLinesAt(buf, off)
189 if nr > 0 {
190 for _, line := range buf[0:nr] {
191 nw, werr := dst.Write(line)
192 if nw > 0 {
193 written++
194 }
195 if werr != nil {
196 err = werr
197 break ReadLoop
198 }
199 if len(line) != nw {
200 err = io.ErrShortWrite
201 break ReadLoop
202 }
203 }
204 }
205 if rerr != nil {
206 if rerr != io.EOF {
207 err = rerr
208 }
209 break
210 }
211 }
212 return written, err
213}