Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 0f54d1a

Browse files
authoredJun 20, 2021
Merge branch 'main' into fix-16092
2 parents 5277c8a + 23358bc commit 0f54d1a

40 files changed

+2538
-295
lines changed
 

‎go.mod

+2
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ require (
3030
github.com/couchbase/goutils v0.0.0-20210118111533-e33d3ffb5401 // indirect
3131
github.com/denisenkom/go-mssqldb v0.10.0
3232
github.com/dgrijalva/jwt-go v3.2.0+incompatible
33+
github.com/djherbis/buffer v1.2.0
34+
github.com/djherbis/nio/v3 v3.0.1
3335
github.com/dustin/go-humanize v1.0.0
3436
github.com/editorconfig/editorconfig-core-go/v2 v2.4.2
3537
github.com/emirpasic/gods v1.12.0

‎go.sum

+5
Original file line numberDiff line numberDiff line change
@@ -244,6 +244,11 @@ github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZm
244244
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
245245
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
246246
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
247+
github.com/djherbis/buffer v1.1.0/go.mod h1:VwN8VdFkMY0DCALdY8o00d3IZ6Amz/UNVMWcSaJT44o=
248+
github.com/djherbis/buffer v1.2.0 h1:PH5Dd2ss0C7CRRhQCZ2u7MssF+No9ide8Ye71nPHcrQ=
249+
github.com/djherbis/buffer v1.2.0/go.mod h1:fjnebbZjCUpPinBRD+TDwXSOeNQ7fPQWLfGQqiAiUyE=
250+
github.com/djherbis/nio/v3 v3.0.1 h1:6wxhnuppteMa6RHA4L81Dq7ThkZH8SwnDzXDYy95vB4=
251+
github.com/djherbis/nio/v3 v3.0.1/go.mod h1:Ng4h80pbZFMla1yKzm61cF0tqqilXZYrogmWgZxOcmg=
247252
github.com/dlclark/regexp2 v1.1.6/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
248253
github.com/dlclark/regexp2 v1.2.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
249254
github.com/dlclark/regexp2 v1.4.0 h1:F1rxgk7p4uKjwIQxBs9oAXe5CqrXlCduYEJvrF4u93E=

‎modules/git/batch_reader.go

+40-71
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@ import (
1111
"math"
1212
"strconv"
1313
"strings"
14+
15+
"github.com/djherbis/buffer"
16+
"github.com/djherbis/nio/v3"
1417
)
1518

1619
// WriteCloserError wraps an io.WriteCloser with an additional CloseWithError function
@@ -42,7 +45,7 @@ func CatFileBatchCheck(repoPath string) (WriteCloserError, *bufio.Reader, func()
4245
}
4346
}()
4447

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
4649
batchReader := bufio.NewReader(batchStdoutReader)
4750

4851
return batchStdinWriter, batchReader, cancel
@@ -53,7 +56,7 @@ func CatFileBatch(repoPath string) (WriteCloserError, *bufio.Reader, func()) {
5356
// We often want to feed the commits in order into cat-file --batch, followed by their trees and sub trees as necessary.
5457
// so let's create a batch stdin and stdout
5558
batchStdinReader, batchStdinWriter := io.Pipe()
56-
batchStdoutReader, batchStdoutWriter := io.Pipe()
59+
batchStdoutReader, batchStdoutWriter := nio.Pipe(buffer.New(32 * 1024))
5760
cancel := func() {
5861
_ = batchStdinReader.Close()
5962
_ = batchStdinWriter.Close()
@@ -74,7 +77,7 @@ func CatFileBatch(repoPath string) (WriteCloserError, *bufio.Reader, func()) {
7477
}()
7578

7679
// 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)
7881

7982
return batchStdinWriter, batchReader, cancel
8083
}
@@ -84,22 +87,31 @@ func CatFileBatch(repoPath string) (WriteCloserError, *bufio.Reader, func()) {
8487
// <sha> SP <type> SP <size> LF
8588
// sha is a 40byte not 20byte here
8689
func ReadBatchLine(rd *bufio.Reader) (sha []byte, typ string, size int64, err error) {
87-
sha, err = rd.ReadBytes(' ')
90+
typ, err = rd.ReadString('\n')
8891
if err != nil {
8992
return
9093
}
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)}
95104
return
96105
}
106+
sha = []byte(typ[:idx])
107+
typ = typ[idx+1:]
97108

98-
idx := strings.Index(typ, " ")
109+
idx = strings.IndexByte(typ, ' ')
99110
if idx < 0 {
100111
err = ErrNotExist{ID: string(sha)}
101112
return
102113
}
114+
103115
sizeStr := typ[idx+1 : len(typ)-1]
104116
typ = typ[:idx]
105117

@@ -130,7 +142,7 @@ headerLoop:
130142
}
131143

132144
// Discard the rest of the tag
133-
discard := size - n
145+
discard := size - n + 1
134146
for discard > math.MaxInt32 {
135147
_, err := rd.Discard(math.MaxInt32)
136148
if err != nil {
@@ -200,85 +212,42 @@ func To40ByteSHA(sha, out []byte) []byte {
200212
return out
201213
}
202214

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.
205217
// It is recommended therefore to pass in an fnameBuf large enough to avoid almost all allocations
206218
//
207219
// Each line is composed of:
208220
// <mode-in-ascii-dropping-initial-zeros> SP <fname> NUL <20-byte SHA>
209221
//
210222
// 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) {
212224
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)
219225

220-
// Deal with the fname
226+
// Read the Mode & fname
221227
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)
233228
if err != nil {
234229
return
235230
}
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)
264234

265-
// Read the Mode
266-
readBytes, err = rd.ReadSlice(' ')
267-
if err != nil {
235+
err = &ErrNotExist{}
268236
return
269237
}
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):]...)
276238

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]...)
277245
}
278-
mode = modeBuf[:len(modeBuf)-1] // Drop the SP
246+
mode = modeBuf
247+
248+
readBytes = readBytes[idx+1:]
279249

280250
// Deal with the fname
281-
readBytes, err = rd.ReadSlice('\x00')
282251
copy(fnameBuf, readBytes)
283252
if len(fnameBuf) > len(readBytes) {
284253
fnameBuf = fnameBuf[:len(readBytes)]
@@ -297,7 +266,7 @@ func ParseTreeLine(rd *bufio.Reader, modeBuf, fnameBuf, shaBuf []byte) (mode, fn
297266
fname = fnameBuf
298267

299268
// Deal with the 20-byte SHA
300-
idx := 0
269+
idx = 0
301270
for idx < 20 {
302271
read := 0
303272
read, err = rd.Read(shaBuf[idx:20])

‎modules/git/commit_info_nogogit.go

+17-220
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,11 @@
77
package git
88

99
import (
10-
"bufio"
11-
"bytes"
1210
"context"
1311
"fmt"
1412
"io"
15-
"math"
1613
"path"
1714
"sort"
18-
"strings"
1915
)
2016

2117
// GetCommitsInfo gets information of all commits that are corresponding to these entries
@@ -43,21 +39,16 @@ func (tes Entries) GetCommitsInfo(ctx context.Context, commit *Commit, treePath
4339
return nil, nil, err
4440
}
4541

46-
for i, found := range commits {
47-
if err := cache.Put(commit.ID.String(), path.Join(treePath, unHitPaths[i]), found.ID.String()); err != nil {
42+
for pth, found := range commits {
43+
if err := cache.Put(commit.ID.String(), path.Join(treePath, pth), found.ID.String()); err != nil {
4844
return nil, nil, err
4945
}
50-
revs[unHitPaths[i]] = found
46+
revs[pth] = found
5147
}
5248
}
5349
} else {
5450
sort.Strings(entryPaths)
55-
revs = map[string]*Commit{}
56-
var foundCommits []*Commit
57-
foundCommits, err = GetLastCommitForPaths(ctx, commit, treePath, entryPaths)
58-
for i, found := range foundCommits {
59-
revs[entryPaths[i]] = found
60-
}
51+
revs, err = GetLastCommitForPaths(ctx, commit, treePath, entryPaths)
6152
}
6253
if err != nil {
6354
return nil, nil, err
@@ -86,6 +77,8 @@ func (tes Entries) GetCommitsInfo(ctx context.Context, commit *Commit, treePath
8677
subModuleFile := NewSubModuleFile(entryCommit, subModuleURL, entry.ID.String())
8778
commitsInfo[i].SubModuleFile = subModuleFile
8879
}
80+
} else {
81+
log("missing commit for %s", entry.Name())
8982
}
9083
}
9184

@@ -125,220 +118,24 @@ func getLastCommitForPathsByCache(ctx context.Context, commitID, treePath string
125118
}
126119

127120
// GetLastCommitForPaths returns last commit information
128-
func GetLastCommitForPaths(ctx context.Context, commit *Commit, treePath string, paths []string) ([]*Commit, error) {
121+
func GetLastCommitForPaths(ctx context.Context, commit *Commit, treePath string, paths []string) (map[string]*Commit, error) {
129122
// We read backwards from the commit to obtain all of the commits
130-
131-
// We'll do this by using rev-list to provide us with parent commits in order
132-
revListReader, revListWriter := io.Pipe()
133-
defer func() {
134-
_ = revListWriter.Close()
135-
_ = revListReader.Close()
136-
}()
137-
138-
go func() {
139-
stderr := strings.Builder{}
140-
err := NewCommand("rev-list", "--format=%T", commit.ID.String()).SetParentContext(ctx).RunInDirPipeline(commit.repo.Path, revListWriter, &stderr)
141-
if err != nil {
142-
_ = revListWriter.CloseWithError(ConcatenateError(err, (&stderr).String()))
143-
} else {
144-
_ = revListWriter.Close()
145-
}
146-
}()
123+
revs, err := WalkGitLog(ctx, commit.repo, commit, treePath, paths...)
124+
if err != nil {
125+
return nil, err
126+
}
147127

148128
batchStdinWriter, batchReader, cancel := commit.repo.CatFileBatch()
149129
defer cancel()
150130

151-
mapsize := 4096
152-
if len(paths) > mapsize {
153-
mapsize = len(paths)
154-
}
155-
156-
path2idx := make(map[string]int, mapsize)
157-
for i, path := range paths {
158-
path2idx[path] = i
159-
}
160-
161-
fnameBuf := make([]byte, 4096)
162-
modeBuf := make([]byte, 40)
163-
164-
allShaBuf := make([]byte, (len(paths)+1)*20)
165-
shaBuf := make([]byte, 20)
166-
tmpTreeID := make([]byte, 40)
167-
168-
// commits is the returnable commits matching the paths provided
169-
commits := make([]string, len(paths))
170-
// ids are the blob/tree ids for the paths
171-
ids := make([][]byte, len(paths))
172-
173-
// We'll use a scanner for the revList because it's simpler than a bufio.Reader
174-
scan := bufio.NewScanner(revListReader)
175-
revListLoop:
176-
for scan.Scan() {
177-
// Get the next parent commit ID
178-
commitID := scan.Text()
179-
if !scan.Scan() {
180-
break revListLoop
181-
}
182-
commitID = commitID[7:]
183-
rootTreeID := scan.Text()
184-
185-
// push the tree to the cat-file --batch process
186-
_, err := batchStdinWriter.Write([]byte(rootTreeID + "\n"))
187-
if err != nil {
188-
return nil, err
189-
}
190-
191-
currentPath := ""
192-
193-
// OK if the target tree path is "" and the "" is in the paths just set this now
194-
if treePath == "" && paths[0] == "" {
195-
// If this is the first time we see this set the id appropriate for this paths to this tree and set the last commit to curCommit
196-
if len(ids[0]) == 0 {
197-
ids[0] = []byte(rootTreeID)
198-
commits[0] = string(commitID)
199-
} else if bytes.Equal(ids[0], []byte(rootTreeID)) {
200-
commits[0] = string(commitID)
201-
}
202-
}
203-
204-
treeReadingLoop:
205-
for {
206-
select {
207-
case <-ctx.Done():
208-
return nil, ctx.Err()
209-
default:
210-
}
211-
_, _, size, err := ReadBatchLine(batchReader)
212-
if err != nil {
213-
return nil, err
214-
}
215-
216-
// Handle trees
217-
218-
// n is counter for file position in the tree file
219-
var n int64
220-
221-
// Two options: currentPath is the targetTreepath
222-
if treePath == currentPath {
223-
// We are in the right directory
224-
// Parse each tree line in turn. (don't care about mode here.)
225-
for n < size {
226-
fname, sha, count, err := ParseTreeLineSkipMode(batchReader, fnameBuf, shaBuf)
227-
shaBuf = sha
228-
if err != nil {
229-
return nil, err
230-
}
231-
n += int64(count)
232-
idx, ok := path2idx[string(fname)]
233-
if ok {
234-
// Now if this is the first time round set the initial Blob(ish) SHA ID and the commit
235-
if len(ids[idx]) == 0 {
236-
copy(allShaBuf[20*(idx+1):20*(idx+2)], shaBuf)
237-
ids[idx] = allShaBuf[20*(idx+1) : 20*(idx+2)]
238-
commits[idx] = string(commitID)
239-
} else if bytes.Equal(ids[idx], shaBuf) {
240-
commits[idx] = string(commitID)
241-
}
242-
}
243-
// FIXME: is there any order to the way strings are emitted from cat-file?
244-
// if there is - then we could skip once we've passed all of our data
245-
}
246-
if _, err := batchReader.Discard(1); err != nil {
247-
return nil, err
248-
}
249-
250-
break treeReadingLoop
251-
}
252-
253-
var treeID []byte
254-
255-
// We're in the wrong directory
256-
// Find target directory in this directory
257-
idx := len(currentPath)
258-
if idx > 0 {
259-
idx++
260-
}
261-
target := strings.SplitN(treePath[idx:], "/", 2)[0]
262-
263-
for n < size {
264-
// Read each tree entry in turn
265-
mode, fname, sha, count, err := ParseTreeLine(batchReader, modeBuf, fnameBuf, shaBuf)
266-
if err != nil {
267-
return nil, err
268-
}
269-
n += int64(count)
270-
271-
// if we have found the target directory
272-
if bytes.Equal(fname, []byte(target)) && bytes.Equal(mode, []byte("40000")) {
273-
copy(tmpTreeID, sha)
274-
treeID = tmpTreeID
275-
break
276-
}
277-
}
278-
279-
if n < size {
280-
// Discard any remaining entries in the current tree
281-
discard := size - n
282-
for discard > math.MaxInt32 {
283-
_, err := batchReader.Discard(math.MaxInt32)
284-
if err != nil {
285-
return nil, err
286-
}
287-
discard -= math.MaxInt32
288-
}
289-
_, err := batchReader.Discard(int(discard))
290-
if err != nil {
291-
return nil, err
292-
}
293-
}
294-
if _, err := batchReader.Discard(1); err != nil {
295-
return nil, err
296-
}
297-
298-
// if we haven't found a treeID for the target directory our search is over
299-
if len(treeID) == 0 {
300-
break treeReadingLoop
301-
}
302-
303-
// add the target to the current path
304-
if idx > 0 {
305-
currentPath += "/"
306-
}
307-
currentPath += target
308-
309-
// if we've now found the current path check its sha id and commit status
310-
if treePath == currentPath && paths[0] == "" {
311-
if len(ids[0]) == 0 {
312-
copy(allShaBuf[0:20], treeID)
313-
ids[0] = allShaBuf[0:20]
314-
commits[0] = string(commitID)
315-
} else if bytes.Equal(ids[0], treeID) {
316-
commits[0] = string(commitID)
317-
}
318-
}
319-
treeID = To40ByteSHA(treeID, treeID)
320-
_, err = batchStdinWriter.Write(treeID)
321-
if err != nil {
322-
return nil, err
323-
}
324-
_, err = batchStdinWriter.Write([]byte("\n"))
325-
if err != nil {
326-
return nil, err
327-
}
328-
}
329-
}
330-
if scan.Err() != nil {
331-
return nil, scan.Err()
332-
}
333-
334-
commitsMap := make(map[string]*Commit, len(commits))
131+
commitsMap := map[string]*Commit{}
335132
commitsMap[commit.ID.String()] = commit
336133

337-
commitCommits := make([]*Commit, len(commits))
338-
for i, commitID := range commits {
134+
commitCommits := map[string]*Commit{}
135+
for path, commitID := range revs {
339136
c, ok := commitsMap[commitID]
340137
if ok {
341-
commitCommits[i] = c
138+
commitCommits[path] = c
342139
continue
343140
}
344141

@@ -364,8 +161,8 @@ revListLoop:
364161
if _, err := batchReader.Discard(1); err != nil {
365162
return nil, err
366163
}
367-
commitCommits[i] = c
164+
commitCommits[path] = c
368165
}
369166

370-
return commitCommits, scan.Err()
167+
return commitCommits, nil
371168
}

‎modules/git/last_commit_cache_nogogit.go

+2-3
Original file line numberDiff line numberDiff line change
@@ -88,9 +88,8 @@ func (c *LastCommitCache) recursiveCache(ctx context.Context, commit *Commit, tr
8888
return err
8989
}
9090

91-
for i, entryCommit := range commits {
92-
entry := entryPaths[i]
93-
if err := c.Put(commit.ID.String(), path.Join(treePath, entryPaths[i]), entryCommit.ID.String()); err != nil {
91+
for entry, entryCommit := range commits {
92+
if err := c.Put(commit.ID.String(), path.Join(treePath, entry), entryCommit.ID.String()); err != nil {
9493
return err
9594
}
9695
if entryMap[entry].IsDir() {

‎modules/git/log_name_status.go

+398
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,398 @@
1+
// Copyright 2021 The Gitea Authors. All rights reserved.
2+
// Use of this source code is governed by a MIT-style
3+
// license that can be found in the LICENSE file.
4+
5+
package git
6+
7+
import (
8+
"bufio"
9+
"bytes"
10+
"context"
11+
"io"
12+
"path"
13+
"sort"
14+
"strings"
15+
16+
"github.com/djherbis/buffer"
17+
"github.com/djherbis/nio/v3"
18+
)
19+
20+
// LogNameStatusRepo opens git log --raw in the provided repo and returns a stdin pipe, a stdout reader and cancel function
21+
func LogNameStatusRepo(repository, head, treepath string, paths ...string) (*bufio.Reader, func()) {
22+
// We often want to feed the commits in order into cat-file --batch, followed by their trees and sub trees as necessary.
23+
// so let's create a batch stdin and stdout
24+
stdoutReader, stdoutWriter := nio.Pipe(buffer.New(32 * 1024))
25+
cancel := func() {
26+
_ = stdoutReader.Close()
27+
_ = stdoutWriter.Close()
28+
}
29+
30+
args := make([]string, 0, 8+len(paths))
31+
args = append(args, "log", "--name-status", "-c", "--format=commit%x00%H %P%x00", "--parents", "--no-renames", "-t", "-z", head, "--")
32+
if len(paths) < 70 {
33+
if treepath != "" {
34+
args = append(args, treepath)
35+
for _, pth := range paths {
36+
if pth != "" {
37+
args = append(args, path.Join(treepath, pth))
38+
}
39+
}
40+
} else {
41+
for _, pth := range paths {
42+
if pth != "" {
43+
args = append(args, pth)
44+
}
45+
}
46+
}
47+
} else if treepath != "" {
48+
args = append(args, treepath)
49+
}
50+
51+
go func() {
52+
stderr := strings.Builder{}
53+
err := NewCommand(args...).RunInDirFullPipeline(repository, stdoutWriter, &stderr, nil)
54+
if err != nil {
55+
_ = stdoutWriter.CloseWithError(ConcatenateError(err, (&stderr).String()))
56+
} else {
57+
_ = stdoutWriter.Close()
58+
}
59+
}()
60+
61+
// For simplicities sake we'll us a buffered reader to read from the cat-file --batch
62+
bufReader := bufio.NewReaderSize(stdoutReader, 32*1024)
63+
64+
return bufReader, cancel
65+
}
66+
67+
// LogNameStatusRepoParser parses a git log raw output from LogRawRepo
68+
type LogNameStatusRepoParser struct {
69+
treepath string
70+
paths []string
71+
next []byte
72+
buffull bool
73+
rd *bufio.Reader
74+
cancel func()
75+
}
76+
77+
// NewLogNameStatusRepoParser returns a new parser for a git log raw output
78+
func NewLogNameStatusRepoParser(repository, head, treepath string, paths ...string) *LogNameStatusRepoParser {
79+
rd, cancel := LogNameStatusRepo(repository, head, treepath, paths...)
80+
return &LogNameStatusRepoParser{
81+
treepath: treepath,
82+
paths: paths,
83+
rd: rd,
84+
cancel: cancel,
85+
}
86+
}
87+
88+
// LogNameStatusCommitData represents a commit artefact from git log raw
89+
type LogNameStatusCommitData struct {
90+
CommitID string
91+
ParentIDs []string
92+
Paths []bool
93+
}
94+
95+
// Next returns the next LogStatusCommitData
96+
func (g *LogNameStatusRepoParser) Next(treepath string, paths2ids map[string]int, changed []bool, maxpathlen int) (*LogNameStatusCommitData, error) {
97+
var err error
98+
if g.next == nil || len(g.next) == 0 {
99+
g.buffull = false
100+
g.next, err = g.rd.ReadSlice('\x00')
101+
if err != nil {
102+
if err == bufio.ErrBufferFull {
103+
g.buffull = true
104+
} else if err == io.EOF {
105+
return nil, nil
106+
} else {
107+
return nil, err
108+
}
109+
}
110+
}
111+
112+
ret := LogNameStatusCommitData{}
113+
if bytes.Equal(g.next, []byte("commit\000")) {
114+
g.next, err = g.rd.ReadSlice('\x00')
115+
if err != nil {
116+
if err == bufio.ErrBufferFull {
117+
g.buffull = true
118+
} else if err == io.EOF {
119+
return nil, nil
120+
} else {
121+
return nil, err
122+
}
123+
}
124+
}
125+
126+
// Our "line" must look like: <commitid> SP (<parent> SP) * NUL
127+
ret.CommitID = string(g.next[0:40])
128+
parents := string(g.next[41:])
129+
if g.buffull {
130+
more, err := g.rd.ReadString('\x00')
131+
if err != nil {
132+
return nil, err
133+
}
134+
parents += more
135+
}
136+
parents = parents[:len(parents)-1]
137+
ret.ParentIDs = strings.Split(parents, " ")
138+
139+
// now read the next "line"
140+
g.buffull = false
141+
g.next, err = g.rd.ReadSlice('\x00')
142+
if err != nil {
143+
if err == bufio.ErrBufferFull {
144+
g.buffull = true
145+
} else if err != io.EOF {
146+
return nil, err
147+
}
148+
}
149+
150+
if err == io.EOF || !(g.next[0] == '\n' || g.next[0] == '\000') {
151+
return &ret, nil
152+
}
153+
154+
// Ok we have some changes.
155+
// This line will look like: NL <fname> NUL
156+
//
157+
// Subsequent lines will not have the NL - so drop it here - g.bufffull must also be false at this point too.
158+
if g.next[0] == '\n' {
159+
g.next = g.next[1:]
160+
} else {
161+
g.buffull = false
162+
g.next, err = g.rd.ReadSlice('\x00')
163+
if err != nil {
164+
if err == bufio.ErrBufferFull {
165+
g.buffull = true
166+
} else if err != io.EOF {
167+
return nil, err
168+
}
169+
}
170+
if g.next[0] == '\x00' {
171+
g.buffull = false
172+
g.next, err = g.rd.ReadSlice('\x00')
173+
if err != nil {
174+
if err == bufio.ErrBufferFull {
175+
g.buffull = true
176+
} else if err != io.EOF {
177+
return nil, err
178+
}
179+
}
180+
}
181+
}
182+
183+
fnameBuf := make([]byte, 4096)
184+
185+
diffloop:
186+
for {
187+
if err == io.EOF || bytes.Equal(g.next, []byte("commit\000")) {
188+
return &ret, nil
189+
}
190+
g.next, err = g.rd.ReadSlice('\x00')
191+
if err != nil {
192+
if err == bufio.ErrBufferFull {
193+
g.buffull = true
194+
} else if err == io.EOF {
195+
return &ret, nil
196+
} else {
197+
return nil, err
198+
}
199+
}
200+
copy(fnameBuf, g.next)
201+
if len(fnameBuf) < len(g.next) {
202+
fnameBuf = append(fnameBuf, g.next[len(fnameBuf):]...)
203+
} else {
204+
fnameBuf = fnameBuf[:len(g.next)]
205+
}
206+
if err != nil {
207+
if err != bufio.ErrBufferFull {
208+
return nil, err
209+
}
210+
more, err := g.rd.ReadBytes('\x00')
211+
if err != nil {
212+
return nil, err
213+
}
214+
fnameBuf = append(fnameBuf, more...)
215+
}
216+
217+
// read the next line
218+
g.buffull = false
219+
g.next, err = g.rd.ReadSlice('\x00')
220+
if err != nil {
221+
if err == bufio.ErrBufferFull {
222+
g.buffull = true
223+
} else if err != io.EOF {
224+
return nil, err
225+
}
226+
}
227+
228+
if treepath != "" {
229+
if !bytes.HasPrefix(fnameBuf, []byte(treepath)) {
230+
fnameBuf = fnameBuf[:cap(fnameBuf)]
231+
continue diffloop
232+
}
233+
}
234+
fnameBuf = fnameBuf[len(treepath) : len(fnameBuf)-1]
235+
if len(fnameBuf) > maxpathlen {
236+
fnameBuf = fnameBuf[:cap(fnameBuf)]
237+
continue diffloop
238+
}
239+
if len(fnameBuf) > 0 {
240+
if len(treepath) > 0 {
241+
if fnameBuf[0] != '/' || bytes.IndexByte(fnameBuf[1:], '/') >= 0 {
242+
fnameBuf = fnameBuf[:cap(fnameBuf)]
243+
continue diffloop
244+
}
245+
fnameBuf = fnameBuf[1:]
246+
} else if bytes.IndexByte(fnameBuf, '/') >= 0 {
247+
fnameBuf = fnameBuf[:cap(fnameBuf)]
248+
continue diffloop
249+
}
250+
}
251+
252+
idx, ok := paths2ids[string(fnameBuf)]
253+
if !ok {
254+
fnameBuf = fnameBuf[:cap(fnameBuf)]
255+
continue diffloop
256+
}
257+
if ret.Paths == nil {
258+
ret.Paths = changed
259+
}
260+
changed[idx] = true
261+
}
262+
}
263+
264+
// Close closes the parser
265+
func (g *LogNameStatusRepoParser) Close() {
266+
g.cancel()
267+
}
268+
269+
// WalkGitLog walks the git log --name-status for the head commit in the provided treepath and files
270+
func WalkGitLog(ctx context.Context, repo *Repository, head *Commit, treepath string, paths ...string) (map[string]string, error) {
271+
tree, err := head.SubTree(treepath)
272+
if err != nil {
273+
return nil, err
274+
}
275+
276+
entries, err := tree.ListEntries()
277+
if err != nil {
278+
return nil, err
279+
}
280+
281+
if len(paths) == 0 {
282+
paths = make([]string, 0, len(entries)+1)
283+
paths = append(paths, "")
284+
for _, entry := range entries {
285+
paths = append(paths, entry.Name())
286+
}
287+
} else {
288+
sort.Strings(paths)
289+
if paths[0] != "" {
290+
paths = append([]string{""}, paths...)
291+
}
292+
// remove duplicates
293+
for i := len(paths) - 1; i > 0; i-- {
294+
if paths[i] == paths[i-1] {
295+
paths = append(paths[:i-1], paths[i:]...)
296+
}
297+
}
298+
}
299+
300+
path2idx := map[string]int{}
301+
maxpathlen := len(treepath)
302+
303+
for i := range paths {
304+
path2idx[paths[i]] = i
305+
pthlen := len(paths[i]) + len(treepath) + 1
306+
if pthlen > maxpathlen {
307+
maxpathlen = pthlen
308+
}
309+
}
310+
311+
g := NewLogNameStatusRepoParser(repo.Path, head.ID.String(), treepath, paths...)
312+
defer g.Close()
313+
314+
results := make([]string, len(paths))
315+
remaining := len(paths)
316+
nextRestart := (len(paths) * 3) / 4
317+
if nextRestart > 70 {
318+
nextRestart = 70
319+
}
320+
lastEmptyParent := head.ID.String()
321+
commitSinceLastEmptyParent := uint64(0)
322+
commitSinceNextRestart := uint64(0)
323+
parentRemaining := map[string]bool{}
324+
325+
changed := make([]bool, len(paths))
326+
327+
heaploop:
328+
for {
329+
select {
330+
case <-ctx.Done():
331+
return nil, ctx.Err()
332+
default:
333+
}
334+
current, err := g.Next(treepath, path2idx, changed, maxpathlen)
335+
if err != nil {
336+
g.Close()
337+
return nil, err
338+
}
339+
if current == nil {
340+
break heaploop
341+
}
342+
delete(parentRemaining, current.CommitID)
343+
if current.Paths != nil {
344+
for i, found := range current.Paths {
345+
if !found {
346+
continue
347+
}
348+
changed[i] = false
349+
if results[i] == "" {
350+
results[i] = current.CommitID
351+
delete(path2idx, paths[i])
352+
remaining--
353+
if results[0] == "" {
354+
results[0] = current.CommitID
355+
delete(path2idx, "")
356+
remaining--
357+
}
358+
}
359+
}
360+
}
361+
362+
if remaining <= 0 {
363+
break heaploop
364+
}
365+
commitSinceLastEmptyParent++
366+
if len(parentRemaining) == 0 {
367+
lastEmptyParent = current.CommitID
368+
commitSinceLastEmptyParent = 0
369+
}
370+
if remaining <= nextRestart {
371+
commitSinceNextRestart++
372+
if 4*commitSinceNextRestart > 3*commitSinceLastEmptyParent {
373+
g.Close()
374+
remainingPaths := make([]string, 0, len(paths))
375+
for i, pth := range paths {
376+
if results[i] == "" {
377+
remainingPaths = append(remainingPaths, pth)
378+
}
379+
}
380+
g = NewLogNameStatusRepoParser(repo.Path, lastEmptyParent, treepath, remainingPaths...)
381+
parentRemaining = map[string]bool{}
382+
nextRestart = (remaining * 3) / 4
383+
continue heaploop
384+
}
385+
}
386+
for _, parent := range current.ParentIDs {
387+
parentRemaining[parent] = true
388+
}
389+
}
390+
g.Close()
391+
392+
resultsMap := map[string]string{}
393+
for i, pth := range paths {
394+
resultsMap[pth] = results[i]
395+
}
396+
397+
return resultsMap, nil
398+
}

‎modules/git/notes_nogogit.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ func GetNote(ctx context.Context, repo *Repository, commitID string, note *Note)
6868
if err != nil {
6969
return err
7070
}
71-
note.Commit = lastCommits[0]
71+
note.Commit = lastCommits[path]
7272

7373
return nil
7474
}

‎modules/git/pipeline/lfs_nogogit.go

+6
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,9 @@ func FindLFSFile(repo *git.Repository, hash git.SHA1) ([]*LFSResult, error) {
116116
if err != nil {
117117
return nil, err
118118
}
119+
if _, err := batchReader.Discard(1); err != nil {
120+
return nil, err
121+
}
119122

120123
_, err := batchStdinWriter.Write([]byte(curCommit.Tree.ID.String() + "\n"))
121124
if err != nil {
@@ -146,6 +149,9 @@ func FindLFSFile(repo *git.Repository, hash git.SHA1) ([]*LFSResult, error) {
146149
paths = append(paths, curPath+string(fname)+"/")
147150
}
148151
}
152+
if _, err := batchReader.Discard(1); err != nil {
153+
return nil, err
154+
}
149155
if len(trees) > 0 {
150156
_, err := batchStdinWriter.Write(trees[len(trees)-1])
151157
if err != nil {

‎modules/git/repo_language_stats_nogogit.go

+3
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,9 @@ func (repo *Repository) GetLanguageStats(commitID string) (map[string]int64, err
4949
log("Unable to get commit for: %s. Err: %v", commitID, err)
5050
return nil, err
5151
}
52+
if _, err = batchReader.Discard(1); err != nil {
53+
return nil, err
54+
}
5255

5356
tree := commit.Tree
5457

‎modules/indexer/code/bleve.go

+3
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,9 @@ func (b *BleveIndexer) addUpdate(batchWriter git.WriteCloserError, batchReader *
216216
return nil
217217
}
218218

219+
if _, err = batchReader.Discard(1); err != nil {
220+
return err
221+
}
219222
id := filenameIndexerID(repo.ID, update.Filename)
220223
return batch.Index(id, &RepoIndexerData{
221224
RepoID: repo.ID,

‎modules/indexer/code/elastic_search.go

+3
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,9 @@ func (b *ElasticSearchIndexer) addUpdate(batchWriter git.WriteCloserError, batch
215215
return nil, nil
216216
}
217217

218+
if _, err = batchReader.Discard(1); err != nil {
219+
return nil, err
220+
}
218221
id := filenameIndexerID(repo.ID, update.Filename)
219222

220223
return []elastic.BulkableRequest{

‎vendor/github.com/djherbis/buffer/.travis.yml

+20
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎vendor/github.com/djherbis/buffer/LICENSE.txt

+20
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎vendor/github.com/djherbis/buffer/README.md

+174
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎vendor/github.com/djherbis/buffer/buffer.go

+48
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎vendor/github.com/djherbis/buffer/discard.go

+36
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎vendor/github.com/djherbis/buffer/file.go

+72
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎vendor/github.com/djherbis/buffer/go.mod

+3
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎vendor/github.com/djherbis/buffer/limio/limit.go

+31
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎vendor/github.com/djherbis/buffer/list.go

+47
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎vendor/github.com/djherbis/buffer/list_at.go

+47
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎vendor/github.com/djherbis/buffer/mem.go

+82
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎vendor/github.com/djherbis/buffer/multi.go

+185
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎vendor/github.com/djherbis/buffer/partition.go

+101
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎vendor/github.com/djherbis/buffer/partition_at.go

+187
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎vendor/github.com/djherbis/buffer/pool.go

+111
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎vendor/github.com/djherbis/buffer/pool_at.go

+111
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎vendor/github.com/djherbis/buffer/ring.go

+58
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎vendor/github.com/djherbis/buffer/spill.go

+41
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎vendor/github.com/djherbis/buffer/swap.go

+99
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎vendor/github.com/djherbis/buffer/wrapio/limitwrap.go

+94
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎vendor/github.com/djherbis/buffer/wrapio/wrap.go

+139
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎vendor/github.com/djherbis/nio/v3/.travis.yml

+22
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎vendor/github.com/djherbis/nio/v3/LICENSE.txt

+20
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎vendor/github.com/djherbis/nio/v3/README.md

+65
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎vendor/github.com/djherbis/nio/v3/go.mod

+5
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎vendor/github.com/djherbis/nio/v3/go.sum

+2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎vendor/github.com/djherbis/nio/v3/nio.go

+53
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎vendor/github.com/djherbis/nio/v3/sync.go

+177
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎vendor/modules.txt

+8
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,14 @@ github.com/denisenkom/go-mssqldb/internal/querytext
231231
github.com/dgrijalva/jwt-go
232232
# github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f
233233
github.com/dgryski/go-rendezvous
234+
# github.com/djherbis/buffer v1.2.0
235+
## explicit
236+
github.com/djherbis/buffer
237+
github.com/djherbis/buffer/limio
238+
github.com/djherbis/buffer/wrapio
239+
# github.com/djherbis/nio/v3 v3.0.1
240+
## explicit
241+
github.com/djherbis/nio/v3
234242
# github.com/dlclark/regexp2 v1.4.0
235243
github.com/dlclark/regexp2
236244
github.com/dlclark/regexp2/syntax

0 commit comments

Comments
 (0)
Please sign in to comment.