mirror of
https://github.com/hanzoai/forge.git
synced 2026-10-30 15:00:39 +00:00
Source files keep their SPDX-License-Identifier and Hanzo copyright line; the Gitea, Gogs and LFS Test Server copyrights they used to repeat now live once in NOTICE, alongside LICENSE. The header lint accepts any copyright line above the SPDX line, so it no longer demands an upstream one. Co-authored-by: Hanzo Dev <[email protected]>
151 lines
4.1 KiB
Go
151 lines
4.1 KiB
Go
// Copyright 2026 Hanzo AI, Inc. All rights reserved.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
//go:build !gogit
|
|
|
|
package pipeline
|
|
|
|
import (
|
|
"bufio"
|
|
"bytes"
|
|
"io"
|
|
"sort"
|
|
|
|
"github.com/hanzoai/git/modules/git"
|
|
"github.com/hanzoai/git/modules/git/gitcmd"
|
|
)
|
|
|
|
// FindLFSFile finds commits that contain a provided pointer file hash
|
|
func FindLFSFile(repo *git.Repository, objectID git.ObjectID) (results []*LFSResult, _ error) {
|
|
cmd := gitcmd.NewCommand("rev-list", "--all")
|
|
revListReader, revListReaderClose := cmd.MakeStdoutPipe()
|
|
defer revListReaderClose()
|
|
err := cmd.WithDir(repo.Path).
|
|
WithPipelineFunc(func(context gitcmd.Context) (err error) {
|
|
results, err = findLFSFileFunc(repo, objectID, revListReader)
|
|
return err
|
|
}).RunWithStderr(repo.Ctx)
|
|
return results, err
|
|
}
|
|
|
|
func findLFSFileFunc(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(repo.Ctx)
|
|
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(repo.Ctx, repo.Path, results)
|
|
return results, err
|
|
}
|