This commit is contained in:
wxiaoguang
2026-04-14 15:38:13 +08:00
parent 1d2609a65c
commit 381a1e6d6c
4 changed files with 51 additions and 63 deletions

View File

@@ -10,6 +10,7 @@ import (
"errors"
"io"
"math"
"slices"
"strconv"
"strings"
"sync/atomic"
@@ -172,50 +173,46 @@ headerLoop:
return id, DiscardFull(rd, size-n+1)
}
// git tree files are a list:
// <mode-in-ascii> SP <fname> NUL <binary Hash>
//
// Unfortunately this 20-byte notation is somewhat in conflict to all other git tools
// Therefore we need some method to convert these binary hashes to hex hashes
// ParseCatFileTreeLine reads an entry from a tree in a cat-file --batch stream
//
// Each line is composed of:
// <mode-in-ascii-dropping-initial-zeros> SP <fname> NUL <binary HASH>
//
// We don't attempt to convert the raw HASH to save a lot of time
func ParseCatFileTreeLine(objectFormat ObjectFormat, rd BufferedReader) (mode, fname, sha []byte, n int, err error) {
// Read the mode and fname up to and including the NUL separator.
// ReadBytes (unlike ReadSlice) always returns a freshly allocated slice,
// so mode and fname can safely reference into it across subsequent reads.
readBytes, err := rd.ReadBytes('\x00')
if err != nil {
return mode, fname, sha, n, err
}
idx := bytes.IndexByte(readBytes, ' ')
if idx < 0 {
log.Debug("missing space in readBytes ParseCatFileTreeLine: %s", readBytes)
return mode, fname, sha, n, &ErrNotExist{}
}
mode = readBytes[:idx]
fname = readBytes[idx+1 : len(readBytes)-1] // trim the NUL terminator
n = len(readBytes)
// Read the binary hash
length := objectFormat.FullLength() / 2
sha = make([]byte, length)
idx = 0
for idx < length {
var read int
read, err = rd.Read(sha[idx:length])
n += read
if err != nil {
return mode, fname, sha, n, err
// Each entry is composed of:
// <mode-in-ascii-dropping-initial-zeros> SP <name> NUL <binary-hash>
func ParseCatFileTreeLine(objectFormat ObjectFormat, rd BufferedReader) (mode EntryMode, name string, objID ObjectID, n int, err error) {
// use the in-buffer memory as much as possible to avoid extra allocations
bufBytes, err := rd.ReadSlice('\x00')
const maxEntryInfoBytes = 1024 * 1024
if errors.Is(err, bufio.ErrBufferFull) {
bufBytes = slices.Clone(bufBytes)
for len(bufBytes) < maxEntryInfoBytes && errors.Is(err, bufio.ErrBufferFull) {
var tmp []byte
tmp, err = rd.ReadSlice('\x00')
bufBytes = append(bufBytes, tmp...)
}
idx += read
}
return mode, fname, sha, n, err
if err != nil {
return mode, name, objID, len(bufBytes), err
}
idx := bytes.IndexByte(bufBytes, ' ')
if idx < 0 {
return mode, name, objID, len(bufBytes), errors.New("invalid CatFileTreeLine output")
}
mode = ParseEntryMode(util.UnsafeBytesToString(bufBytes[:idx]))
name = string(bufBytes[idx+1 : len(bufBytes)-1]) // trim the NUL terminator
if mode == EntryModeNoEntry {
return mode, name, objID, len(bufBytes), errors.New("invalid entry mode: " + string(bufBytes[:idx]))
}
switch objectFormat {
case Sha1ObjectFormat:
objID = &Sha1Hash{}
case Sha256ObjectFormat:
objID = &Sha256Hash{}
default:
panic("unsupported object format: " + objectFormat.Name())
}
readIDLen, err := io.ReadFull(rd, objID.RawValue())
return mode, name, objID, len(bufBytes) + readIDLen, err
}
func DiscardFull(rd BufferedReader, discard int64) error {

View File

@@ -5,10 +5,7 @@ package git
import (
"bytes"
"fmt"
"io"
"code.gitea.io/gitea/modules/log"
)
// ParseTreeEntries parses the output of a `git ls-tree -l` command.
@@ -51,7 +48,7 @@ func catBatchParseTreeEntries(objectFormat ObjectFormat, ptree *Tree, rd Buffere
loop:
for sz > 0 {
mode, fname, sha, count, err := ParseCatFileTreeLine(objectFormat, rd)
mode, fname, objID, count, err := ParseCatFileTreeLine(objectFormat, rd)
if err != nil {
if err == io.EOF {
break loop
@@ -61,15 +58,9 @@ loop:
sz -= int64(count)
entry := new(TreeEntry)
entry.ptree = ptree
entry.entryMode = ParseEntryMode(string(mode))
if entry.entryMode == EntryModeNoEntry {
log.Debug("Unknown mode: %v", string(mode))
return nil, fmt.Errorf("unknown mode: %v", string(mode))
}
entry.ID = objectFormat.MustID(sha)
entry.name = string(fname)
entry.entryMode = mode
entry.ID = objID
entry.name = fname
entries = append(entries, entry)
}
if _, err := rd.Discard(1); err != nil {

View File

@@ -8,7 +8,6 @@ package pipeline
import (
"bufio"
"bytes"
"encoding/hex"
"io"
"sort"
"strings"
@@ -89,23 +88,23 @@ func findLFSFileFunc(repo *git.Repository, objectID git.ObjectID, revListReader
case "tree":
var n int64
for n < info.Size {
mode, fname, binObjectID, count, err := git.ParseCatFileTreeLine(objectID.Type(), batchReader)
mode, fname, shaID, count, err := git.ParseCatFileTreeLine(objectID.Type(), batchReader)
if err != nil {
return nil, err
}
n += int64(count)
if bytes.Equal(binObjectID, objectID.RawValue()) {
if bytes.Equal(shaID.RawValue(), objectID.RawValue()) {
result := LFSResult{
Name: curPath + string(fname),
Name: curPath + fname,
SHA: curCommit.ID.String(),
Summary: strings.Split(strings.TrimSpace(curCommit.CommitMessage), "\n")[0],
When: curCommit.Author.When,
ParentHashes: curCommit.Parents,
}
resultsMap[curCommit.ID.String()+":"+curPath+string(fname)] = &result
} else if string(mode) == git.EntryModeTree.String() {
trees = append(trees, hex.EncodeToString(binObjectID))
paths = append(paths, curPath+string(fname)+"/")
resultsMap[curCommit.ID.String()+":"+curPath+fname] = &result
} else if mode == git.EntryModeTree {
trees = append(trees, shaID.String())
paths = append(paths, curPath+fname+"/")
}
}
if _, err := batchReader.Discard(1); err != nil {

View File

@@ -66,9 +66,10 @@ func ParseEntryMode(mode string) EntryMode {
return EntryModeSymlink
case "160000":
return EntryModeCommit
case "040000":
case "040000", "40000": // leading-zero is optional
return EntryModeTree
default:
// if the faster path didn't work, try parsing the mode as an integer and masking off the file type bits
// git uses 040000 for tree object, but some users may get 040755 from non-standard git implementations
m, _ := strconv.ParseInt(mode, 8, 32)
modeInt := EntryMode(m)