Files
Gitea/modules/git/pipeline/lfs_find.go
T
wxiaoguang 4d6e172a0d chore: still keep ctx in git.Repository struct for cat-file batch command (#38684)
Unfortunately, we can't completely remove the ctx from git.Repository,
because the CatFileBatch still heavily depends on a parent context.

If we remove the Repository ctx, the CatFileBatch will become a mess and
create a lot of unnecessary git processes.

http://localhost:3000/-/admin/monitor/perftrace

* Before: open a repo home, dozens of git processes (duplicate cat-file)
* After: only a few (no duplicate cat-file)
2026-07-28 17:04:22 +00:00

150 lines
4.1 KiB
Go

// Copyright 2020 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package pipeline
import (
"bufio"
"bytes"
"context"
"io"
"sort"
"gitea.dev/modules/git"
"gitea.dev/modules/git/gitcmd"
)
// FindLFSFile finds commits that contain a provided pointer file hash
func FindLFSFile(ctx context.Context, repo *git.Repository, objectID git.ObjectID) (results []*LFSResult, _ error) {
cmd := gitcmd.NewCommand("rev-list", "--all")
revListReader, revListReaderClose := cmd.MakeStdoutPipe()
defer revListReaderClose()
err := cmd.WithRepo(repo).
WithPipelineFunc(func(context gitcmd.Context) (err error) {
results, err = findLFSFileFunc(ctx, repo, objectID, revListReader)
return err
}).RunWithStderr(ctx)
return results, err
}
func findLFSFileFunc(ctx context.Context, repo *git.Repository, objectID git.ObjectID, revListReader io.Reader) ([]*LFSResult, error) {
resultsMap := map[string]*LFSResult{}
results := make([]*LFSResult, 0)
// Next feed the commits in order into cat-file --batch, followed by their trees and sub trees as necessary.
// so let's create a batch stdin and stdout
batch, cancel, err := repo.CatFileBatch()
if err != nil {
return nil, err
}
defer cancel()
// We'll use a scanner for the revList because it's simpler than a bufio.Reader
scan := bufio.NewScanner(revListReader)
trees := []string{}
paths := []string{}
for scan.Scan() {
// Get the next commit ID
commitID := scan.Text()
// push the commit to the cat-file --batch process
info, batchReader, err := batch.QueryContent(commitID)
if err != nil {
return nil, err
}
var curCommit *git.Commit
curPath := ""
commitReadingLoop:
for {
switch info.Type {
case "tag":
// This shouldn't happen but if it does well just get the commit and try again
id, err := git.ReadTagObjectID(batchReader, info.Size)
if err != nil {
return nil, err
}
if info, batchReader, err = batch.QueryContent(id); err != nil {
return nil, err
}
continue
case "commit":
// Read in the commit to get its tree and in case this is one of the last used commits
curCommit, err = git.CommitFromReader(git.MustIDFromString(commitID), io.LimitReader(batchReader, info.Size))
if err != nil {
return nil, err
}
if _, err := batchReader.Discard(1); err != nil {
return nil, err
}
if info, _, err = batch.QueryContent(curCommit.TreeID.String()); err != nil {
return nil, err
}
curPath = ""
case "tree":
var n int64
for n < info.Size {
mode, fname, shaID, count, err := git.ParseCatFileTreeLine(objectID.Type(), batchReader)
if err != nil {
return nil, err
}
n += int64(count)
if bytes.Equal(shaID.RawValue(), objectID.RawValue()) {
result := LFSResult{
Name: curPath + fname,
SHA: curCommit.ID.String(),
Summary: curCommit.MessageTitle(),
When: curCommit.Author.When,
ParentHashes: curCommit.Parents,
}
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 {
return nil, err
}
if len(trees) > 0 {
info, _, err = batch.QueryContent(trees[len(trees)-1])
if err != nil {
return nil, err
}
curPath = paths[len(paths)-1]
trees = trees[:len(trees)-1]
paths = paths[:len(paths)-1]
} else {
break commitReadingLoop
}
default:
if err := git.DiscardFull(batchReader, info.Size+1); err != nil {
return nil, err
}
}
}
}
if err := scan.Err(); err != nil {
return nil, err
}
for _, result := range resultsMap {
hasParent := false
for _, parentID := range result.ParentHashes {
if _, hasParent = resultsMap[parentID.String()+":"+result.Name]; hasParent {
break
}
}
if !hasParent {
results = append(results, result)
}
}
sort.Sort(lfsResultSlice(results))
err = fillResultNameRev(ctx, repo, results)
return results, err
}