@@ -11,6 +11,9 @@ import (
11
11
"math"
12
12
"strconv"
13
13
"strings"
14
+
15
+ "github.com/djherbis/buffer"
16
+ "github.com/djherbis/nio/v3"
14
17
)
15
18
16
19
// WriteCloserError wraps an io.WriteCloser with an additional CloseWithError function
@@ -42,7 +45,7 @@ func CatFileBatchCheck(repoPath string) (WriteCloserError, *bufio.Reader, func()
42
45
}
43
46
}()
44
47
45
- // For simplicities sake we'll us a buffered reader to read from the cat-file --batch
48
+ // For simplicities sake we'll use a buffered reader to read from the cat-file --batch-check
46
49
batchReader := bufio .NewReader (batchStdoutReader )
47
50
48
51
return batchStdinWriter , batchReader , cancel
@@ -53,7 +56,7 @@ func CatFileBatch(repoPath string) (WriteCloserError, *bufio.Reader, func()) {
53
56
// We often want to feed the commits in order into cat-file --batch, followed by their trees and sub trees as necessary.
54
57
// so let's create a batch stdin and stdout
55
58
batchStdinReader , batchStdinWriter := io .Pipe ()
56
- batchStdoutReader , batchStdoutWriter := io .Pipe ()
59
+ batchStdoutReader , batchStdoutWriter := nio .Pipe (buffer . New ( 32 * 1024 ) )
57
60
cancel := func () {
58
61
_ = batchStdinReader .Close ()
59
62
_ = batchStdinWriter .Close ()
@@ -74,7 +77,7 @@ func CatFileBatch(repoPath string) (WriteCloserError, *bufio.Reader, func()) {
74
77
}()
75
78
76
79
// For simplicities sake we'll us a buffered reader to read from the cat-file --batch
77
- batchReader := bufio .NewReader (batchStdoutReader )
80
+ batchReader := bufio .NewReaderSize (batchStdoutReader , 32 * 1024 )
78
81
79
82
return batchStdinWriter , batchReader , cancel
80
83
}
@@ -84,22 +87,31 @@ func CatFileBatch(repoPath string) (WriteCloserError, *bufio.Reader, func()) {
84
87
// <sha> SP <type> SP <size> LF
85
88
// sha is a 40byte not 20byte here
86
89
func ReadBatchLine (rd * bufio.Reader ) (sha []byte , typ string , size int64 , err error ) {
87
- sha , err = rd .ReadBytes ( ' ' )
90
+ typ , err = rd .ReadString ( '\n ' )
88
91
if err != nil {
89
92
return
90
93
}
91
- sha = sha [:len (sha )- 1 ]
92
-
93
- typ , err = rd .ReadString ('\n' )
94
- if err != nil {
94
+ if len (typ ) == 1 {
95
+ typ , err = rd .ReadString ('\n' )
96
+ if err != nil {
97
+ return
98
+ }
99
+ }
100
+ idx := strings .IndexByte (typ , ' ' )
101
+ if idx < 0 {
102
+ log ("missing space typ: %s" , typ )
103
+ err = ErrNotExist {ID : string (sha )}
95
104
return
96
105
}
106
+ sha = []byte (typ [:idx ])
107
+ typ = typ [idx + 1 :]
97
108
98
- idx : = strings .Index (typ , " " )
109
+ idx = strings .IndexByte (typ , ' ' )
99
110
if idx < 0 {
100
111
err = ErrNotExist {ID : string (sha )}
101
112
return
102
113
}
114
+
103
115
sizeStr := typ [idx + 1 : len (typ )- 1 ]
104
116
typ = typ [:idx ]
105
117
@@ -130,7 +142,7 @@ headerLoop:
130
142
}
131
143
132
144
// Discard the rest of the tag
133
- discard := size - n
145
+ discard := size - n + 1
134
146
for discard > math .MaxInt32 {
135
147
_ , err := rd .Discard (math .MaxInt32 )
136
148
if err != nil {
@@ -200,85 +212,42 @@ func To40ByteSHA(sha, out []byte) []byte {
200
212
return out
201
213
}
202
214
203
- // ParseTreeLineSkipMode reads an entry from a tree in a cat-file --batch stream
204
- // This simply skips the mode - saving a substantial amount of time and carefully avoids allocations - except where fnameBuf is too small.
215
+ // ParseTreeLine reads an entry from a tree in a cat-file --batch stream
216
+ // This carefully avoids allocations - except where fnameBuf is too small.
205
217
// It is recommended therefore to pass in an fnameBuf large enough to avoid almost all allocations
206
218
//
207
219
// Each line is composed of:
208
220
// <mode-in-ascii-dropping-initial-zeros> SP <fname> NUL <20-byte SHA>
209
221
//
210
222
// We don't attempt to convert the 20-byte SHA to 40-byte SHA to save a lot of time
211
- func ParseTreeLineSkipMode (rd * bufio.Reader , fnameBuf , shaBuf []byte ) (fname , sha []byte , n int , err error ) {
223
+ func ParseTreeLine (rd * bufio.Reader , modeBuf , fnameBuf , shaBuf []byte ) (mode , fname , sha []byte , n int , err error ) {
212
224
var readBytes []byte
213
- // Skip the Mode
214
- readBytes , err = rd .ReadSlice (' ' ) // NB: DOES NOT ALLOCATE SIMPLY RETURNS SLICE WITHIN READER BUFFER
215
- if err != nil {
216
- return
217
- }
218
- n += len (readBytes )
219
225
220
- // Deal with the fname
226
+ // Read the Mode & fname
221
227
readBytes , err = rd .ReadSlice ('\x00' )
222
- copy (fnameBuf , readBytes )
223
- if len (fnameBuf ) > len (readBytes ) {
224
- fnameBuf = fnameBuf [:len (readBytes )] // cut the buf the correct size
225
- } else {
226
- fnameBuf = append (fnameBuf , readBytes [len (fnameBuf ):]... ) // extend the buf and copy in the missing bits
227
- }
228
- for err == bufio .ErrBufferFull { // Then we need to read more
229
- readBytes , err = rd .ReadSlice ('\x00' )
230
- fnameBuf = append (fnameBuf , readBytes ... ) // there is little point attempting to avoid allocations here so just extend
231
- }
232
- n += len (fnameBuf )
233
228
if err != nil {
234
229
return
235
230
}
236
- fnameBuf = fnameBuf [:len (fnameBuf )- 1 ] // Drop the terminal NUL
237
- fname = fnameBuf // set the returnable fname to the slice
238
-
239
- // Now deal with the 20-byte SHA
240
- idx := 0
241
- for idx < 20 {
242
- read := 0
243
- read , err = rd .Read (shaBuf [idx :20 ])
244
- n += read
245
- if err != nil {
246
- return
247
- }
248
- idx += read
249
- }
250
- sha = shaBuf
251
- return
252
- }
253
-
254
- // ParseTreeLine reads an entry from a tree in a cat-file --batch stream
255
- // This carefully avoids allocations - except where fnameBuf is too small.
256
- // It is recommended therefore to pass in an fnameBuf large enough to avoid almost all allocations
257
- //
258
- // Each line is composed of:
259
- // <mode-in-ascii-dropping-initial-zeros> SP <fname> NUL <20-byte SHA>
260
- //
261
- // We don't attempt to convert the 20-byte SHA to 40-byte SHA to save a lot of time
262
- func ParseTreeLine (rd * bufio.Reader , modeBuf , fnameBuf , shaBuf []byte ) (mode , fname , sha []byte , n int , err error ) {
263
- var readBytes []byte
231
+ idx := bytes .IndexByte (readBytes , ' ' )
232
+ if idx < 0 {
233
+ log ("missing space in readBytes ParseTreeLine: %s" , readBytes )
264
234
265
- // Read the Mode
266
- readBytes , err = rd .ReadSlice (' ' )
267
- if err != nil {
235
+ err = & ErrNotExist {}
268
236
return
269
237
}
270
- n += len (readBytes )
271
- copy (modeBuf , readBytes )
272
- if len (modeBuf ) > len (readBytes ) {
273
- modeBuf = modeBuf [:len (readBytes )]
274
- } else {
275
- modeBuf = append (modeBuf , readBytes [len (modeBuf ):]... )
276
238
239
+ n += idx + 1
240
+ copy (modeBuf , readBytes [:idx ])
241
+ if len (modeBuf ) >= idx {
242
+ modeBuf = modeBuf [:idx ]
243
+ } else {
244
+ modeBuf = append (modeBuf , readBytes [len (modeBuf ):idx ]... )
277
245
}
278
- mode = modeBuf [:len (modeBuf )- 1 ] // Drop the SP
246
+ mode = modeBuf
247
+
248
+ readBytes = readBytes [idx + 1 :]
279
249
280
250
// Deal with the fname
281
- readBytes , err = rd .ReadSlice ('\x00' )
282
251
copy (fnameBuf , readBytes )
283
252
if len (fnameBuf ) > len (readBytes ) {
284
253
fnameBuf = fnameBuf [:len (readBytes )]
@@ -297,7 +266,7 @@ func ParseTreeLine(rd *bufio.Reader, modeBuf, fnameBuf, shaBuf []byte) (mode, fn
297
266
fname = fnameBuf
298
267
299
268
// Deal with the 20-byte SHA
300
- idx : = 0
269
+ idx = 0
301
270
for idx < 20 {
302
271
read := 0
303
272
read , err = rd .Read (shaBuf [idx :20 ])
0 commit comments