refactor: decouple git.Repository(ctx) from git.Commit & git.Tree (#38464)

1. Storing "ctx" in a long-living object is wrong
2. Make the commit & tree cacheable (for the future performance
optimization)
3. Also fix some bad designs like `// FIXME: bad design, this field can
be nil if the commit is from "last commit cache"`

ref:
* #33893
This commit is contained in:
wxiaoguang
2026-07-16 01:30:01 +08:00
committed by GitHub
parent ed678b9d45
commit 82edc3da01
115 changed files with 732 additions and 864 deletions

View File

@@ -17,17 +17,17 @@ import (
// Commit represents a git commit.
type Commit struct {
Tree // FIXME: bad design, this field can be nil if the commit is from "last commit cache"
CommitMessage
ID ObjectID
TreeID ObjectID
Parents []ObjectID
Author *Signature // never nil
Committer *Signature // never nil
Signature *CommitSignature
Parents []ObjectID // ID strings
submoduleCache *ObjectCache[*SubModule]
treeCache *Tree
}
// CommitSignature represents a git commit signature part.
@@ -46,12 +46,12 @@ func (c *Commit) ParentID(n int) (ObjectID, error) {
}
// Parent returns n-th parent (0-based index) of the commit.
func (c *Commit) Parent(n int) (*Commit, error) {
func (c *Commit) Parent(gitRepo *Repository, n int) (*Commit, error) {
id, err := c.ParentID(n)
if err != nil {
return nil, err
}
parent, err := c.repo.getCommit(id)
parent, err := gitRepo.getCommit(id)
if err != nil {
return nil, err
}
@@ -65,25 +65,44 @@ func (c *Commit) ParentCount() int {
}
// GetCommitByPath return the commit of relative path object.
func (c *Commit) GetCommitByPath(relpath string) (*Commit, error) {
if c.repo.LastCommitCache != nil {
return c.repo.LastCommitCache.GetCommitByPath(c.ID.String(), relpath)
func (c *Commit) GetCommitByPath(gitRepo *Repository, relpath string) (*Commit, error) {
if gitRepo.LastCommitCache != nil {
return gitRepo.LastCommitCache.GetCommitByPath(c.ID.String(), relpath)
}
return c.repo.getCommitByPathWithID(c.ID, relpath)
return gitRepo.getCommitByPathWithID(c.ID, relpath)
}
func (c *Commit) Tree() *Tree {
if c.treeCache == nil {
c.treeCache = newTree(c.TreeID)
}
return c.treeCache
}
func (c *Commit) GetBlobByPath(ctx context.Context, gitRepo *Repository, relpath string) (*Blob, error) {
return c.Tree().GetBlobByPath(ctx, gitRepo, relpath)
}
func (c *Commit) GetTreeEntryByPath(ctx context.Context, gitRepo *Repository, relpath string) (_ *TreeEntry, err error) {
return c.Tree().GetTreeEntryByPath(ctx, gitRepo, relpath)
}
func (c *Commit) SubTree(ctx context.Context, gitRepo *Repository, relpath string) (*Tree, error) {
return c.Tree().SubTree(ctx, gitRepo, relpath)
}
// CommitsByRange returns the specific page commits before current revision, every page's number default by CommitsRangeSize
func (c *Commit) CommitsByRange(page, pageSize int, not, since, until string) ([]*Commit, error) {
return c.repo.commitsByRangeWithTime(c.ID, page, pageSize, not, since, until)
func (c *Commit) CommitsByRange(gitRepo *Repository, page, pageSize int, not, since, until string) ([]*Commit, error) {
return gitRepo.commitsByRangeWithTime(c.ID, page, pageSize, not, since, until)
}
// CommitsBefore returns all the commits before current revision
func (c *Commit) CommitsBefore() ([]*Commit, error) {
return c.repo.getCommitsBefore(c.ID)
func (c *Commit) CommitsBefore(gitRepo *Repository) ([]*Commit, error) {
return gitRepo.getCommitsBefore(c.ID)
}
// HasPreviousCommit returns true if a given commitHash is contained in commit's parents
func (c *Commit) HasPreviousCommit(objectID ObjectID) (bool, error) {
func (c *Commit) HasPreviousCommit(ctx context.Context, gitRepo *Repository, objectID ObjectID) (bool, error) {
this := c.ID.String()
that := objectID.String()
@@ -93,8 +112,8 @@ func (c *Commit) HasPreviousCommit(objectID ObjectID) (bool, error) {
_, _, err := gitcmd.NewCommand("merge-base", "--is-ancestor").
AddDynamicArguments(that, this).
WithDir(c.repo.Path).
RunStdString(c.repo.Ctx)
WithDir(gitRepo.Path).
RunStdString(ctx)
if err == nil {
return true, nil
}
@@ -108,8 +127,8 @@ func (c *Commit) HasPreviousCommit(objectID ObjectID) (bool, error) {
}
// IsForcePush returns true if a push from oldCommitHash to this is a force push
func (c *Commit) IsForcePush(oldCommitID string) (bool, error) {
objectFormat, err := c.repo.GetObjectFormat()
func (c *Commit) IsForcePush(ctx context.Context, gitRepo *Repository, oldCommitID string) (bool, error) {
objectFormat, err := gitRepo.GetObjectFormat()
if err != nil {
return false, err
}
@@ -117,22 +136,22 @@ func (c *Commit) IsForcePush(oldCommitID string) (bool, error) {
return false, nil
}
oldCommit, err := c.repo.GetCommit(oldCommitID)
oldCommit, err := gitRepo.GetCommit(oldCommitID)
if err != nil {
return false, err
}
hasPreviousCommit, err := c.HasPreviousCommit(oldCommit.ID)
hasPreviousCommit, err := c.HasPreviousCommit(ctx, gitRepo, oldCommit.ID)
return !hasPreviousCommit, err
}
// CommitsBeforeLimit returns num commits before current revision
func (c *Commit) CommitsBeforeLimit(num int) ([]*Commit, error) {
return c.repo.getCommitsBeforeLimit(c.ID, num)
func (c *Commit) CommitsBeforeLimit(gitRepo *Repository, num int) ([]*Commit, error) {
return gitRepo.getCommitsBeforeLimit(c.ID, num)
}
// CommitsBeforeUntil returns the commits in range "[cur, ref)"
func (c *Commit) CommitsBeforeUntil(ref RefName) ([]*Commit, error) {
return c.repo.CommitsBetween(c.ID.RefName(), ref, -1)
func (c *Commit) CommitsBeforeUntil(gitRepo *Repository, ref RefName) ([]*Commit, error) {
return gitRepo.CommitsBetween(c.ID.RefName(), ref, -1)
}
// SearchCommitsOptions specify the parameters for SearchCommits
@@ -175,39 +194,29 @@ func NewSearchCommitsOptions(searchString string, forAllRefs bool) SearchCommits
}
// SearchCommits returns the commits match the keyword before current revision
func (c *Commit) SearchCommits(opts SearchCommitsOptions) ([]*Commit, error) {
return c.repo.searchCommits(c.ID, opts)
func (c *Commit) SearchCommits(gitRepo *Repository, opts SearchCommitsOptions) ([]*Commit, error) {
return gitRepo.searchCommits(c.ID, opts)
}
// GetFilesChangedSinceCommit get all changed file names between pastCommit to current revision
func (c *Commit) GetFilesChangedSinceCommit(pastCommit string) ([]string, error) {
return c.repo.GetFilesChangedBetween(pastCommit, c.ID.String())
func (c *Commit) GetFilesChangedSinceCommit(gitRepo *Repository, pastCommit string) ([]string, error) {
return gitRepo.GetFilesChangedBetween(pastCommit, c.ID.String())
}
// FileChangedSinceCommit Returns true if the file given has changed since the past commit
// YOU MUST ENSURE THAT pastCommit is a valid commit ID.
func (c *Commit) FileChangedSinceCommit(filename, pastCommit string) (bool, error) {
return c.repo.FileChangedBetweenCommits(filename, pastCommit, c.ID.String())
}
// HasFile returns true if the file given exists on this commit
// This does only mean it's there - it does not mean the file was changed during the commit.
func (c *Commit) HasFile(filename string) (bool, error) {
_, err := c.GetBlobByPath(filename)
if err != nil {
return false, err
}
return true, nil
func (c *Commit) FileChangedSinceCommit(gitRepo *Repository, filename, pastCommit string) (bool, error) {
return gitRepo.FileChangedBetweenCommits(filename, pastCommit, c.ID.String())
}
// GetFileContent reads a file content as a string or returns false if this was not possible
func (c *Commit) GetFileContent(filename string, limit int) (string, error) {
entry, err := c.GetTreeEntryByPath(filename)
func (c *Commit) GetFileContent(ctx context.Context, gitRepo *Repository, filename string, limit int) (string, error) {
entry, err := c.GetTreeEntryByPath(ctx, gitRepo, filename)
if err != nil {
return "", err
}
r, err := entry.Blob().DataAsync()
r, err := entry.Blob(gitRepo).DataAsync()
if err != nil {
return "", err
}

View File

@@ -3,6 +3,8 @@
package git
import "context"
// CommitInfo describes the first commit with the provided entry
type CommitInfo struct {
Entry *TreeEntry
@@ -10,8 +12,8 @@ type CommitInfo struct {
SubmoduleFile *CommitSubmoduleFile
}
func GetCommitInfoSubmoduleFile(repoLink, fullPath string, commit *Commit, refCommitID ObjectID) (*CommitSubmoduleFile, error) {
submodule, err := commit.GetSubModule(fullPath)
func GetCommitInfoSubmoduleFile(ctx context.Context, repoLink, fullPath string, gitRepo *Repository, commit *Commit, refCommitID ObjectID) (*CommitSubmoduleFile, error) {
submodule, err := commit.GetSubModule(ctx, gitRepo, fullPath)
if err != nil {
return nil, err
}

View File

@@ -17,7 +17,7 @@ import (
)
// GetCommitsInfo gets information of all commits that are corresponding to these entries
func (tes Entries) GetCommitsInfo(ctx context.Context, repoLink string, commit *Commit, treePath string) ([]CommitInfo, *Commit, error) {
func (tes Entries) GetCommitsInfo(ctx context.Context, repoLink string, gitRepo *Repository, commit *Commit, treePath string) ([]CommitInfo, *Commit, error) {
entryPaths := make([]string, len(tes)+1)
// Get the commit for the treePath itself
entryPaths[0] = ""
@@ -25,25 +25,16 @@ func (tes Entries) GetCommitsInfo(ctx context.Context, repoLink string, commit *
entryPaths[i+1] = entry.Name()
}
commitNodeIndex, commitGraphFile := commit.repo.CommitNodeIndex()
if commitGraphFile != nil {
defer commitGraphFile.Close()
}
c, err := commitNodeIndex.Get(plumbing.Hash(commit.ID.RawValue()))
if err != nil {
return nil, nil, err
}
var revs map[string]*Commit
if commit.repo.LastCommitCache != nil {
var err error
if gitRepo.LastCommitCache != nil {
var unHitPaths []string
revs, unHitPaths, err = getLastCommitForPathsByCache(commit.ID.String(), treePath, entryPaths, commit.repo.LastCommitCache)
revs, unHitPaths, err = getLastCommitForPathsByCache(commit.ID.String(), treePath, entryPaths, gitRepo.LastCommitCache)
if err != nil {
return nil, nil, err
}
if len(unHitPaths) > 0 {
revs2, err := GetLastCommitForPaths(ctx, commit.repo.LastCommitCache, c, treePath, unHitPaths)
revs2, err := GetLastCommitForPaths(ctx, gitRepo, commit, treePath, unHitPaths)
if err != nil {
return nil, nil, err
}
@@ -51,13 +42,13 @@ func (tes Entries) GetCommitsInfo(ctx context.Context, repoLink string, commit *
maps.Copy(revs, revs2)
}
} else {
revs, err = GetLastCommitForPaths(ctx, nil, c, treePath, entryPaths)
revs, err = GetLastCommitForPaths(ctx, gitRepo, commit, treePath, entryPaths)
}
if err != nil {
return nil, nil, err
}
commit.repo.gogitStorage.Close()
gitRepo.gogitStorage.Close()
commitsInfo := make([]CommitInfo, len(tes))
for i, entry := range tes {
@@ -72,7 +63,7 @@ func (tes Entries) GetCommitsInfo(ctx context.Context, repoLink string, commit *
// If the entry is a submodule, add a submodule file for this
if entry.IsSubModule() {
commitsInfo[i].SubmoduleFile, err = GetCommitInfoSubmoduleFile(repoLink, path.Join(treePath, entry.Name()), commit, entry.ID)
commitsInfo[i].SubmoduleFile, err = GetCommitInfoSubmoduleFile(ctx, repoLink, path.Join(treePath, entry.Name()), gitRepo, commit, entry.ID)
if err != nil {
return nil, nil, err
}
@@ -83,11 +74,10 @@ func (tes Entries) GetCommitsInfo(ctx context.Context, repoLink string, commit *
// get it for free during the tree traversal and it's used for listing
// pages to display information about newest commit for a given path.
var treeCommit *Commit
var ok bool
if treePath == "" {
treeCommit = commit
} else if treeCommit, ok = revs[""]; ok {
treeCommit.repo = commit.repo
} else {
treeCommit = revs[""]
}
return commitsInfo, treeCommit, nil
}
@@ -162,7 +152,20 @@ func getLastCommitForPathsByCache(commitID, treePath string, paths []string, cac
}
// GetLastCommitForPaths returns last commit information
func GetLastCommitForPaths(ctx context.Context, cache *LastCommitCache, c cgobject.CommitNode, treePath string, paths []string) (map[string]*Commit, error) {
func GetLastCommitForPaths(ctx context.Context, gitRepo *Repository, commit *Commit, treePath string, paths []string) (map[string]*Commit, error) {
commitNodeIndex, commitGraphFile := gitRepo.CommitNodeIndex()
if commitGraphFile != nil {
defer commitGraphFile.Close()
}
c, err := commitNodeIndex.Get(plumbing.Hash(commit.ID.RawValue()))
if err != nil {
return nil, err
}
return getLastCommitForPathsByCommitNode(ctx, gitRepo, c, treePath, paths)
}
func getLastCommitForPathsByCommitNode(ctx context.Context, gitRepo *Repository, c cgobject.CommitNode, treePath string, paths []string) (map[string]*Commit, error) {
refSha := c.ID().String()
// We do a tree traversal with nodes sorted by commit time
@@ -243,7 +246,7 @@ heaploop:
// match any of the hashes being merged. This is more common for directories,
// but it can also happen if a file is changed through conflict resolution.
resultNodes[pth] = current.commit
if err := cache.Put(refSha, path.Join(treePath, pth), current.commit.ID().String()); err != nil {
if err := gitRepo.LastCommitCache.Put(refSha, path.Join(treePath, pth), current.commit.ID().String()); err != nil {
return nil, err
}
}

View File

@@ -15,7 +15,7 @@ import (
)
// GetCommitsInfo gets information of all commits that are corresponding to these entries
func (tes Entries) GetCommitsInfo(ctx context.Context, repoLink string, commit *Commit, treePath string) ([]CommitInfo, *Commit, error) {
func (tes Entries) GetCommitsInfo(ctx context.Context, repoLink string, gitRepo *Repository, commit *Commit, treePath string) ([]CommitInfo, *Commit, error) {
entryPaths := make([]string, len(tes)+1)
// Get the commit for the treePath itself
entryPaths[0] = ""
@@ -26,15 +26,15 @@ func (tes Entries) GetCommitsInfo(ctx context.Context, repoLink string, commit *
var err error
var revs map[string]*Commit
if commit.repo.LastCommitCache != nil {
if gitRepo.LastCommitCache != nil {
var unHitPaths []string
revs, unHitPaths, err = getLastCommitForPathsByCache(commit.ID.String(), treePath, entryPaths, commit.repo.LastCommitCache)
revs, unHitPaths, err = getLastCommitForPathsByCache(commit.ID.String(), treePath, entryPaths, gitRepo.LastCommitCache)
if err != nil {
return nil, nil, err
}
if len(unHitPaths) > 0 {
sort.Strings(unHitPaths)
commits, err := GetLastCommitForPaths(ctx, commit, treePath, unHitPaths)
commits, err := GetLastCommitForPaths(ctx, gitRepo, commit, treePath, unHitPaths)
if err != nil {
return nil, nil, err
}
@@ -43,7 +43,7 @@ func (tes Entries) GetCommitsInfo(ctx context.Context, repoLink string, commit *
}
} else {
sort.Strings(entryPaths)
revs, err = GetLastCommitForPaths(ctx, commit, treePath, entryPaths)
revs, err = GetLastCommitForPaths(ctx, gitRepo, commit, treePath, entryPaths)
}
if err != nil {
return nil, nil, err
@@ -64,7 +64,7 @@ func (tes Entries) GetCommitsInfo(ctx context.Context, repoLink string, commit *
// If the entry is a submodule, add a submodule file for this
if entry.IsSubModule() {
commitsInfo[i].SubmoduleFile, err = GetCommitInfoSubmoduleFile(repoLink, path.Join(treePath, entry.Name()), commit, entry.ID)
commitsInfo[i].SubmoduleFile, err = GetCommitInfoSubmoduleFile(ctx, repoLink, path.Join(treePath, entry.Name()), gitRepo, commit, entry.ID)
if err != nil {
return nil, nil, err
}
@@ -75,11 +75,10 @@ func (tes Entries) GetCommitsInfo(ctx context.Context, repoLink string, commit *
// get it for free during the tree traversal, and it's used for listing
// pages to display information about the newest commit for a given path.
var treeCommit *Commit
var ok bool
if treePath == "" {
treeCommit = commit
} else if treeCommit, ok = revs[""]; ok {
treeCommit.repo = commit.repo
} else {
treeCommit = revs[""]
}
return commitsInfo, treeCommit, nil
}
@@ -104,9 +103,9 @@ func getLastCommitForPathsByCache(commitID, treePath string, paths []string, cac
}
// GetLastCommitForPaths returns last commit information
func GetLastCommitForPaths(ctx context.Context, commit *Commit, treePath string, paths []string) (map[string]*Commit, error) {
func GetLastCommitForPaths(ctx context.Context, gitRepo *Repository, commit *Commit, treePath string, paths []string) (map[string]*Commit, error) {
// We read backwards from the commit to obtain all of the commits
revs, err := walkGitLog(ctx, commit.repo, commit, treePath, paths...)
revs, err := walkGitLog(ctx, gitRepo, commit, treePath, paths...)
if err != nil {
return nil, err
}
@@ -126,7 +125,7 @@ func GetLastCommitForPaths(ctx context.Context, commit *Commit, treePath string,
continue
}
c, err := commit.repo.GetCommit(commitID) // Ensure the commit exists in the repository
c, err := gitRepo.GetCommit(commitID) // Ensure the commit exists in the repository
if err != nil {
return nil, err
}

View File

@@ -24,7 +24,7 @@ func TestEntries_GetCommitsInfo_ContextErr(t *testing.T) {
commit, err := repo.GetCommit("feaf4ba6bc635fec442f46ddd4512416ec43c2c2")
require.NoError(t, err)
entries, err := commit.Tree.ListEntries()
entries, err := commit.Tree().ListEntries(t.Context(), repo)
require.NoError(t, err)
countCommitInfosCommit := func(infos []CommitInfo) (nilCommits, nonNilCommits int) {
@@ -39,14 +39,14 @@ func TestEntries_GetCommitsInfo_ContextErr(t *testing.T) {
defer test.MockVariableValue(&walkGitLogDebugBeforeNext)()
walkGitLogDebugBeforeNext = cancel
commitInfos, _, err := entries.GetCommitsInfo(ctx, "/any/repo-link", commit, "")
commitInfos, _, err := entries.GetCommitsInfo(ctx, "/any/repo-link", repo, commit, "")
assert.NoError(t, err)
nilCommits, nonNilCommits := countCommitInfosCommit(commitInfos)
assert.Equal(t, 0, nonNilCommits) // no commit info due to canceled (or deadline-exceeded) context
assert.Equal(t, 3, nilCommits)
walkGitLogDebugBeforeNext = nil
commitInfos, _, err = entries.GetCommitsInfo(t.Context(), "/any/repo-link", commit, "")
commitInfos, _, err = entries.GetCommitsInfo(t.Context(), "/any/repo-link", repo, commit, "")
assert.NoError(t, err)
nilCommits, nonNilCommits = countCommitInfosCommit(commitInfos)
assert.Equal(t, 3, nonNilCommits)

View File

@@ -91,10 +91,9 @@ func testGetCommitsInfo(t *testing.T, repo1 *Repository) {
continue
}
assert.NotNil(t, commit)
assert.NotNil(t, commit.Tree)
assert.NotNil(t, commit.Tree.repo)
assert.NotNil(t, commit.TreeID)
tree, err := commit.Tree.SubTree(testCase.Path)
tree, err := commit.SubTree(t.Context(), repo1, testCase.Path)
if err != nil {
assert.NoError(t, err, "Unable to get subtree: %s of commit: %s from testcase due to error: %v", testCase.Path, testCase.CommitID, err)
// no point trying to do anything else for this test.
@@ -102,9 +101,8 @@ func testGetCommitsInfo(t *testing.T, repo1 *Repository) {
}
assert.NotNil(t, tree, "tree is nil for testCase CommitID %s in Path %s", testCase.CommitID, testCase.Path)
assert.NotNil(t, tree.repo, "repo is nil for testCase CommitID %s in Path %s", testCase.CommitID, testCase.Path)
entries, err := tree.ListEntries()
entries, err := tree.ListEntries(t.Context(), repo1)
if err != nil {
assert.NoError(t, err, "Unable to get entries of subtree: %s in commit: %s from testcase due to error: %v", testCase.Path, testCase.CommitID, err)
// no point trying to do anything else for this test.
@@ -112,7 +110,7 @@ func testGetCommitsInfo(t *testing.T, repo1 *Repository) {
}
// FIXME: Context.TODO() - if graceful has started we should use its Shutdown context otherwise use install signals in TestMain.
commitsInfo, treeCommit, err := entries.GetCommitsInfo(t.Context(), "/any/repo-link", commit, testCase.Path)
commitsInfo, treeCommit, err := entries.GetCommitsInfo(t.Context(), "/any/repo-link", repo1, commit, testCase.Path)
assert.NoError(t, err, "Unable to get commit information for entries of subtree: %s in commit: %s from testcase due to error: %v", testCase.Path, testCase.CommitID, err)
if err != nil {
t.FailNow()
@@ -127,7 +125,7 @@ func testGetCommitsInfo(t *testing.T, repo1 *Repository) {
continue
}
assert.Equal(t, expectedInfo.CommitID, commit.ID.String())
assert.Equal(t, expectedInfo.Size, entry.Size(), entry.Name())
assert.Equal(t, expectedInfo.Size, entry.GetSize(t.Context(), repo1), entry.Name())
}
}
}
@@ -155,9 +153,9 @@ func TestEntries_GetCommitsInfo(t *testing.T) {
t.Run("NonExistingSubmoduleAsNil", func(t *testing.T) {
commit, err := bareRepo1.GetCommit("HEAD")
require.NoError(t, err)
treeEntry, err := commit.GetTreeEntryByPath("file1.txt")
treeEntry, err := commit.GetTreeEntryByPath(t.Context(), bareRepo1, "file1.txt")
require.NoError(t, err)
cisf, err := GetCommitInfoSubmoduleFile("/any/repo-link", "file1.txt", commit, treeEntry.ID)
cisf, err := GetCommitInfoSubmoduleFile(t.Context(), "/any/repo-link", "file1.txt", bareRepo1, commit, treeEntry.ID)
require.NoError(t, err)
assert.Equal(t, &CommitSubmoduleFile{
repoLink: "/any/repo-link",
@@ -169,52 +167,3 @@ func TestEntries_GetCommitsInfo(t *testing.T) {
assert.Nil(t, cisf.SubmoduleWebLinkTree(t.Context()))
})
}
func BenchmarkEntries_GetCommitsInfo(b *testing.B) {
type benchmarkType struct {
url string
name string
}
benchmarks := []benchmarkType{
{url: "https://github.com/go-gitea/gitea.git", name: "gitea"},
{url: "https://github.com/ethantkoenig/manyfiles.git", name: "manyfiles"},
{url: "https://github.com/moby/moby.git", name: "moby"},
{url: "https://github.com/golang/go.git", name: "go"},
{url: "https://github.com/torvalds/linux.git", name: "linux"},
}
doBenchmark := func(benchmark benchmarkType) {
var commit *Commit
var entries Entries
var repo *Repository
repoPath, err := cloneRepo(b, benchmark.url)
if err != nil {
b.Fatal(err)
}
if repo, err = OpenRepository(b.Context(), repoPath); err != nil {
b.Fatal(err)
}
defer repo.Close()
if commit, err = repo.GetBranchCommit("master"); err != nil {
b.Fatal(err)
} else if entries, err = commit.Tree.ListEntries(); err != nil {
b.Fatal(err)
}
b.ResetTimer()
b.Run(benchmark.name, func(b *testing.B) {
for b.Loop() {
_, _, err := entries.GetCommitsInfo(b.Context(), "/any/repo-link", commit, "")
if err != nil {
b.Fatal(err)
}
}
})
}
for _, benchmark := range benchmarks {
doBenchmark(benchmark)
}
}

View File

@@ -15,7 +15,7 @@ const (
commitHeaderGpgsigSha256 = "gpgsig-sha256"
)
func assignCommitFields(gitRepo *Repository, commit *Commit, headerKey string, headerValue []byte) error {
func assignCommitFields(commit *Commit, headerKey string, headerValue []byte) error {
if len(headerValue) > 0 && headerValue[len(headerValue)-1] == '\n' {
headerValue = headerValue[:len(headerValue)-1] // remove trailing newline
}
@@ -25,7 +25,7 @@ func assignCommitFields(gitRepo *Repository, commit *Commit, headerKey string, h
if err != nil {
return fmt.Errorf("invalid tree ID %q: %w", string(headerValue), err)
}
commit.Tree = *NewTree(gitRepo, objID)
commit.TreeID = objID
case "parent":
objID, err := NewIDFromString(string(headerValue))
if err != nil {
@@ -48,7 +48,7 @@ func assignCommitFields(gitRepo *Repository, commit *Commit, headerKey string, h
// We need this to interpret commits from cat-file or cat-file --batch
//
// If used as part of a cat-file --batch stream you need to limit the reader to the correct size
func CommitFromReader(gitRepo *Repository, objectID ObjectID, reader io.Reader) (*Commit, error) {
func CommitFromReader(objectID ObjectID, reader io.Reader) (*Commit, error) {
commit := &Commit{
ID: objectID,
Author: &Signature{},
@@ -74,7 +74,7 @@ func CommitFromReader(gitRepo *Repository, objectID ObjectID, reader io.Reader)
k, v, _ := bytes.Cut(line, []byte{' '})
if len(k) != 0 || !inHeader {
if headerKey != "" {
if err = assignCommitFields(gitRepo, commit, headerKey, headerValue); err != nil {
if err = assignCommitFields(commit, headerKey, headerValue); err != nil {
return nil, fmt.Errorf("unable to parse commit %q: %w", objectID.String(), err)
}
}

View File

@@ -65,7 +65,7 @@ signed commit`
assert.NotNil(t, gitRepo)
defer gitRepo.Close()
commitFromReader, err := CommitFromReader(gitRepo, sha, strings.NewReader(commitString))
commitFromReader, err := CommitFromReader(sha, strings.NewReader(commitString))
assert.NoError(t, err)
require.NotNil(t, commitFromReader)
assert.EqualValues(t, sha, commitFromReader.ID)
@@ -93,7 +93,7 @@ committer Adam Majer <amajer@suse.de> 1698676906 +0100
signed commit`, commitFromReader.Signature.Payload)
assert.Equal(t, "Adam Majer <amajer@suse.de>", commitFromReader.Author.String())
commitFromReader2, err := CommitFromReader(gitRepo, sha, strings.NewReader(commitString+"\n\n"))
commitFromReader2, err := CommitFromReader(sha, strings.NewReader(commitString+"\n\n"))
assert.NoError(t, err)
commitFromReader.CommitMessage.MessageRaw += "\n\n"
commitFromReader.Signature.Payload += "\n\n"
@@ -118,15 +118,15 @@ func TestHasPreviousCommitSha256(t *testing.T) {
assert.Equal(t, objectFormat, parentSHA.Type())
assert.Equal(t, "sha256", objectFormat.Name())
haz, err := commit.HasPreviousCommit(parentSHA)
haz, err := commit.HasPreviousCommit(t.Context(), repo, parentSHA)
assert.NoError(t, err)
assert.True(t, haz)
hazNot, err := commit.HasPreviousCommit(notParentSHA)
hazNot, err := commit.HasPreviousCommit(t.Context(), repo, notParentSHA)
assert.NoError(t, err)
assert.False(t, hazNot)
selfNot, err := commit.HasPreviousCommit(commit.ID)
selfNot, err := commit.HasPreviousCommit(t.Context(), repo, commit.ID)
assert.NoError(t, err)
assert.False(t, selfNot)
}

View File

@@ -3,17 +3,19 @@
package git
import "context"
type SubmoduleWebLink struct {
RepoWebLink, CommitWebLink string
}
// GetSubModules get all the submodules of current revision git tree
func (c *Commit) GetSubModules() (*ObjectCache[*SubModule], error) {
func (c *Commit) GetSubModules(ctx context.Context, gitRepo *Repository) (*ObjectCache[*SubModule], error) {
if c.submoduleCache != nil {
return c.submoduleCache, nil
}
entry, err := c.GetTreeEntryByPath(".gitmodules")
entry, err := c.GetTreeEntryByPath(ctx, gitRepo, ".gitmodules")
if err != nil {
if _, ok := err.(ErrNotExist); ok {
return nil, nil //nolint:nilnil // return nil to indicate that the submodule does not exist
@@ -21,7 +23,7 @@ func (c *Commit) GetSubModules() (*ObjectCache[*SubModule], error) {
return nil, err
}
rd, err := entry.Blob().DataAsync()
rd, err := entry.Blob(gitRepo).DataAsync()
if err != nil {
return nil, err
}
@@ -37,8 +39,8 @@ func (c *Commit) GetSubModules() (*ObjectCache[*SubModule], error) {
// GetSubModule gets the submodule by the entry name.
// It returns "nil, nil" if the submodule does not exist, caller should always remember to check the "nil"
func (c *Commit) GetSubModule(entryName string) (*SubModule, error) {
modules, err := c.GetSubModules()
func (c *Commit) GetSubModule(ctx context.Context, gitRepo *Repository, entryName string) (*SubModule, error) {
modules, err := c.GetSubModules(ctx, gitRepo)
if err != nil {
return nil, err
}

View File

@@ -61,7 +61,7 @@ empty commit`
assert.NotNil(t, gitRepo)
defer gitRepo.Close()
commitFromReader, err := CommitFromReader(gitRepo, sha, strings.NewReader(commitString))
commitFromReader, err := CommitFromReader(sha, strings.NewReader(commitString))
assert.NoError(t, err)
require.NotNil(t, commitFromReader)
assert.EqualValues(t, sha, commitFromReader.ID)
@@ -89,7 +89,7 @@ committer silverwind <me@silverwind.io> 1563741793 +0200
empty commit`, commitFromReader.Signature.Payload)
assert.Equal(t, "silverwind <me@silverwind.io>", commitFromReader.Author.String())
commitFromReader2, err := CommitFromReader(gitRepo, sha, strings.NewReader(commitString+"\n\n"))
commitFromReader2, err := CommitFromReader(sha, strings.NewReader(commitString+"\n\n"))
assert.NoError(t, err)
commitFromReader.CommitMessage.MessageRaw += "\n\n"
commitFromReader.Signature.Payload += "\n\n"
@@ -125,7 +125,7 @@ ISO-8859-1`
assert.NotNil(t, gitRepo)
defer gitRepo.Close()
commitFromReader, err := CommitFromReader(gitRepo, sha, strings.NewReader(commitString))
commitFromReader, err := CommitFromReader(sha, strings.NewReader(commitString))
assert.NoError(t, err)
require.NotNil(t, commitFromReader)
assert.EqualValues(t, sha, commitFromReader.ID)
@@ -152,7 +152,7 @@ encoding ISO-8859-1
ISO-8859-1`, commitFromReader.Signature.Payload)
assert.Equal(t, "KN4CK3R <admin@oldschoolhack.me>", commitFromReader.Author.String())
commitFromReader2, err := CommitFromReader(gitRepo, sha, strings.NewReader(commitString+"\n\n"))
commitFromReader2, err := CommitFromReader(sha, strings.NewReader(commitString+"\n\n"))
assert.NoError(t, err)
commitFromReader.CommitMessage.MessageRaw += "\n\n"
commitFromReader.Signature.Payload += "\n\n"
@@ -172,15 +172,15 @@ func TestHasPreviousCommit(t *testing.T) {
parentSHA := MustIDFromString("8d92fc957a4d7cfd98bc375f0b7bb189a0d6c9f2")
notParentSHA := MustIDFromString("2839944139e0de9737a044f78b0e4b40d989a9e3")
haz, err := commit.HasPreviousCommit(parentSHA)
haz, err := commit.HasPreviousCommit(t.Context(), repo, parentSHA)
assert.NoError(t, err)
assert.True(t, haz)
hazNot, err := commit.HasPreviousCommit(notParentSHA)
hazNot, err := commit.HasPreviousCommit(t.Context(), repo, notParentSHA)
assert.NoError(t, err)
assert.False(t, hazNot)
selfNot, err := commit.HasPreviousCommit(commit.ID)
selfNot, err := commit.HasPreviousCommit(t.Context(), repo, commit.ID)
assert.NoError(t, err)
assert.False(t, selfNot)
}

View File

@@ -75,7 +75,7 @@ func getRepoRawDiffForFileCmd(_ context.Context, repo *Repository, startCommit,
} else if commit.ParentCount() == 0 {
cmd.AddArguments("show").AddDynamicArguments(endCommit).AddDashesAndList(files...)
} else {
c, err := commit.Parent(0)
c, err := commit.Parent(repo, 0)
if err != nil {
return nil, err
}
@@ -90,7 +90,7 @@ func getRepoRawDiffForFileCmd(_ context.Context, repo *Repository, startCommit,
} else if commit.ParentCount() == 0 {
cmd.AddArguments("format-patch", "--no-signature", "--stdout", "--root").AddDynamicArguments(endCommit).AddDashesAndList(files...)
} else {
c, err := commit.Parent(0)
c, err := commit.Parent(repo, 0)
if err != nil {
return nil, err
}

View File

@@ -7,6 +7,7 @@ package languagestats
import (
"bytes"
"context"
"io"
"gitea.dev/modules/analyze"
@@ -21,7 +22,7 @@ import (
)
// GetLanguageStats calculates language stats for git repository at specified commit
func GetLanguageStats(repo *git_module.Repository, commitID string) (map[string]int64, error) {
func GetLanguageStats(_ context.Context, repo *git_module.Repository, commitID string) (map[string]int64, error) {
r, err := git.PlainOpen(repo.Path)
if err != nil {
return nil, err

View File

@@ -7,6 +7,7 @@ package languagestats
import (
"bytes"
"context"
"io"
"gitea.dev/modules/analyze"
@@ -19,10 +20,10 @@ import (
)
// GetLanguageStats calculates language stats for git repository at specified commit
func GetLanguageStats(repo *git.Repository, commitID string) (map[string]int64, error) {
func GetLanguageStats(ctx context.Context, repo *git.Repository, commitID string) (map[string]int64, error) {
// We will feed the commit IDs in order into cat-file --batch, followed by blobs as necessary.
// so let's create a batch stdin and stdout
batch, cancel, err := repo.CatFileBatch(repo.Ctx)
batch, cancel, err := repo.CatFileBatch(ctx)
if err != nil {
return nil, err
}
@@ -43,7 +44,7 @@ func GetLanguageStats(repo *git.Repository, commitID string) (map[string]int64,
return nil, git.ErrNotExist{ID: commitID}
}
commit, err := git.CommitFromReader(repo, sha, io.LimitReader(batchReader, commitInfo.Size))
commit, err := git.CommitFromReader(sha, io.LimitReader(batchReader, commitInfo.Size))
if err != nil {
log.Debug("Unable to get commit for: %s. Err: %v", commitID, err)
return nil, err
@@ -52,9 +53,7 @@ func GetLanguageStats(repo *git.Repository, commitID string) (map[string]int64,
return nil, err
}
tree := commit.Tree
entries, err := tree.ListEntriesRecursiveWithSize()
entries, err := commit.Tree().ListEntriesRecursiveWithSize(ctx, repo)
if err != nil {
return nil, err
}
@@ -81,13 +80,15 @@ func GetLanguageStats(repo *git.Repository, commitID string) (map[string]int64,
select {
case <-repo.Ctx.Done():
return sizes, repo.Ctx.Err()
case <-ctx.Done():
return sizes, ctx.Err()
default:
}
contentBuf.Reset()
content = contentBuf.Bytes()
if f.Size() == 0 {
if f.GetSize(ctx, repo) == 0 {
continue
}
@@ -124,7 +125,7 @@ func GetLanguageStats(repo *git.Repository, commitID string) (map[string]int64,
}
// this language will always be added to the size
sizes[language] += f.Size()
sizes[language] += f.GetSize(ctx, repo)
continue
}
}
@@ -138,7 +139,7 @@ func GetLanguageStats(repo *git.Repository, commitID string) (map[string]int64,
// If content can not be read or file is too big just do detection by filename
if f.Size() <= bigFileSize {
if f.GetSize(ctx, repo) <= bigFileSize {
info, _, err := batch.QueryContent(f.ID.String())
if err != nil {
return nil, err
@@ -192,10 +193,10 @@ func GetLanguageStats(repo *git.Repository, commitID string) (map[string]int64,
includedLanguage[language] = included
}
if included || isDetectable.ValueOrDefault(false) {
sizes[language] += f.Size()
sizes[language] += f.GetSize(ctx, repo)
} else if len(sizes) == 0 && (firstExcludedLanguage == "" || firstExcludedLanguage == language) {
firstExcludedLanguage = language
firstExcludedLanguageSize += f.Size()
firstExcludedLanguageSize += f.GetSize(ctx, repo)
}
}

View File

@@ -22,7 +22,7 @@ func TestRepository_GetLanguageStats(t *testing.T) {
require.NoError(t, err)
defer gitRepo.Close()
stats, err := GetLanguageStats(gitRepo, "8fee858da5796dfb37704761701bb8e800ad9ef3")
stats, err := GetLanguageStats(t.Context(), gitRepo, "8fee858da5796dfb37704761701bb8e800ad9ef3")
require.NoError(t, err)
assert.Equal(t, map[string]int64{

View File

@@ -13,26 +13,26 @@ import (
)
// CacheCommit will cache the commit from the gitRepository
func (c *Commit) CacheCommit(ctx context.Context) error {
if c.repo.LastCommitCache == nil {
func (c *Commit) CacheCommit(ctx context.Context, gitRepo *Repository) error {
if gitRepo.LastCommitCache == nil {
return nil
}
commitNodeIndex, _ := c.repo.CommitNodeIndex()
commitNodeIndex, _ := gitRepo.CommitNodeIndex()
index, err := commitNodeIndex.Get(plumbing.Hash(c.ID.RawValue()))
if err != nil {
return err
}
return c.recursiveCache(ctx, index, &c.Tree, "", 1)
return c.recursiveCache(ctx, gitRepo, index, c.Tree(), "", 1)
}
func (c *Commit) recursiveCache(ctx context.Context, index cgobject.CommitNode, tree *Tree, treePath string, level int) error {
func (c *Commit) recursiveCache(ctx context.Context, gitRepo *Repository, index cgobject.CommitNode, tree *Tree, treePath string, level int) error {
if level == 0 {
return nil
}
entries, err := tree.ListEntries()
entries, err := tree.ListEntries(ctx, gitRepo)
if err != nil {
return err
}
@@ -44,18 +44,18 @@ func (c *Commit) recursiveCache(ctx context.Context, index cgobject.CommitNode,
entryMap[entry.Name()] = entry
}
commits, err := GetLastCommitForPaths(ctx, c.repo.LastCommitCache, index, treePath, entryPaths)
commits, err := getLastCommitForPathsByCommitNode(ctx, gitRepo, index, treePath, entryPaths)
if err != nil {
return err
}
for entry := range commits {
if entryMap[entry].IsDir() {
subTree, err := tree.SubTree(entry)
subTree, err := tree.SubTree(ctx, gitRepo, entry)
if err != nil {
return err
}
if err := c.recursiveCache(ctx, index, subTree, entry, level-1); err != nil {
if err := c.recursiveCache(ctx, gitRepo, index, subTree, entry, level-1); err != nil {
return err
}
}

View File

@@ -10,19 +10,18 @@ import (
)
// CacheCommit will cache the commit from the gitRepository
func (c *Commit) CacheCommit(ctx context.Context) error {
if c.repo.LastCommitCache == nil {
func (c *Commit) CacheCommit(ctx context.Context, gitRepo *Repository) error {
if gitRepo.LastCommitCache == nil {
return nil
}
return c.recursiveCache(ctx, &c.Tree, "", 1)
return c.recursiveCache(ctx, gitRepo, c.Tree(), "", 1)
}
func (c *Commit) recursiveCache(ctx context.Context, tree *Tree, treePath string, level int) error {
func (c *Commit) recursiveCache(ctx context.Context, gitRepo *Repository, tree *Tree, treePath string, level int) error {
if level == 0 {
return nil
}
entries, err := tree.ListEntries()
entries, err := tree.ListEntries(ctx, gitRepo)
if err != nil {
return err
}
@@ -32,7 +31,7 @@ func (c *Commit) recursiveCache(ctx context.Context, tree *Tree, treePath string
entryPaths[i] = entry.Name()
}
_, err = walkGitLog(ctx, c.repo, c, treePath, entryPaths...)
_, err = walkGitLog(ctx, gitRepo, c, treePath, entryPaths...)
if err != nil {
return err
}
@@ -40,11 +39,11 @@ func (c *Commit) recursiveCache(ctx context.Context, tree *Tree, treePath string
for _, treeEntry := range entries {
// entryMap won't contain "" therefore skip this.
if treeEntry.IsDir() {
subTree, err := tree.SubTree(treeEntry.Name())
subTree, err := tree.SubTree(ctx, gitRepo, treeEntry.Name())
if err != nil {
return err
}
if err := c.recursiveCache(ctx, subTree, treeEntry.Name(), level-1); err != nil {
if err := c.recursiveCache(ctx, gitRepo, subTree, treeEntry.Name(), level-1); err != nil {
return err
}
}

View File

@@ -263,13 +263,12 @@ var walkGitLogDebugBeforeNext func() // is used to simulate various edge git pro
// walkGitLog walks the git log --name-status for the head commit in the provided treepath and files
func walkGitLog(ctx context.Context, repo *Repository, head *Commit, treepath string, paths ...string) (map[string]string, error) {
headRef := head.ID.String()
tree, err := head.SubTree(treepath)
tree, err := head.SubTree(ctx, repo, treepath)
if err != nil {
return nil, err
}
entries, err := tree.ListEntries()
entries, err := tree.ListEntries(ctx, repo)
if err != nil {
return nil, err
}

View File

@@ -3,6 +3,14 @@
package git
import (
"context"
"io"
"strings"
"gitea.dev/modules/log"
)
// NotesRef is the git ref where Gitea will look for git-notes data.
// The value ("refs/notes/commits") is the default ref used by git-notes.
const NotesRef = "refs/notes/commits"
@@ -12,3 +20,80 @@ type Note struct {
Message []byte
Commit *Commit
}
// GetNote retrieves the git-notes data for a given commit.
// FIXME: Add LastCommitCache support
func GetNote(ctx context.Context, repo *Repository, commitID string, note *Note) error {
log.Trace("Searching for git note corresponding to the commit %q in the repository %q", commitID, repo.Path)
notes, err := repo.GetCommit(NotesRef)
if err != nil {
if IsErrNotExist(err) {
return err
}
log.Error("Unable to get commit from ref %q. Error: %v", NotesRef, err)
return err
}
path := ""
tree := notes.Tree()
log.Trace("Found tree with ID %q while searching for git note corresponding to the commit %q", tree.ID, commitID)
var entry *TreeEntry
originalCommitID := commitID
for len(commitID) > 2 {
entry, err = tree.GetTreeEntryByPath(ctx, repo, commitID)
if err == nil {
path += commitID
break
}
if IsErrNotExist(err) {
tree, err = tree.SubTree(ctx, repo, commitID[0:2])
path += commitID[0:2] + "/"
commitID = commitID[2:]
}
if err != nil {
// Err may have been updated by the SubTree we need to recheck if it's again an ErrNotExist
if !IsErrNotExist(err) {
log.Error("Unable to find git note corresponding to the commit %q. Error: %v", originalCommitID, err)
}
return err
}
}
blob := entry.Blob(repo)
dataRc, err := blob.DataAsync()
if err != nil {
log.Error("Unable to read blob with ID %q. Error: %v", blob.ID, err)
return err
}
closed := false
defer func() {
if !closed {
_ = dataRc.Close()
}
}()
d, err := io.ReadAll(dataRc)
if err != nil {
log.Error("Unable to read blob with ID %q. Error: %v", blob.ID, err)
return err
}
_ = dataRc.Close()
closed = true
note.Message = d
treePath := ""
if idx := strings.LastIndex(path, "/"); idx > -1 {
treePath = path[:idx]
path = path[idx+1:]
}
lastCommits, err := GetLastCommitForPaths(ctx, repo, notes, treePath, []string{path})
if err != nil {
log.Error("Unable to get the commit for the path %q. Error: %v", treePath, err)
return err
}
note.Commit = lastCommits[path]
return nil
}

View File

@@ -1,95 +0,0 @@
// Copyright 2019 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
//go:build gogit
package git
import (
"context"
"fmt"
"io"
"strings"
"gitea.dev/modules/log"
"github.com/go-git/go-git/v5/plumbing"
"github.com/go-git/go-git/v5/plumbing/object"
)
// GetNote retrieves the git-notes data for a given commit.
// FIXME: Add LastCommitCache support
func GetNote(ctx context.Context, repo *Repository, commitID string, note *Note) error {
log.Trace("Searching for git note corresponding to the commit %q in the repository %q", commitID, repo.Path)
notes, err := repo.GetCommit(NotesRef)
if err != nil {
if IsErrNotExist(err) {
return err
}
log.Error("Unable to get commit from ref %q. Error: %v", NotesRef, err)
return err
}
remainingCommitID := commitID
var path strings.Builder
currentTree, err := notes.Tree.gogitTreeObject()
if err != nil {
return fmt.Errorf("unable to get tree object for notes commit %q: %w", notes.ID.String(), err)
}
log.Trace("Found tree with ID %q while searching for git note corresponding to the commit %q", currentTree.Entries[0].Name, commitID)
var file *object.File
for len(remainingCommitID) > 2 {
file, err = currentTree.File(remainingCommitID)
if err == nil {
path.WriteString(remainingCommitID)
break
}
if err == object.ErrFileNotFound {
currentTree, err = currentTree.Tree(remainingCommitID[0:2])
path.WriteString(remainingCommitID[0:2] + "/")
remainingCommitID = remainingCommitID[2:]
}
if err != nil {
if err == object.ErrDirectoryNotFound {
return ErrNotExist{ID: remainingCommitID, RelPath: path.String()}
}
log.Error("Unable to find git note corresponding to the commit %q. Error: %v", commitID, err)
return err
}
}
blob := file.Blob
dataRc, err := blob.Reader()
if err != nil {
log.Error("Unable to read blob with ID %q. Error: %v", blob.ID, err)
return err
}
defer dataRc.Close()
d, err := io.ReadAll(dataRc)
if err != nil {
log.Error("Unable to read blob with ID %q. Error: %v", blob.ID, err)
return err
}
note.Message = d
commitNodeIndex, commitGraphFile := repo.CommitNodeIndex()
if commitGraphFile != nil {
defer commitGraphFile.Close()
}
commitNode, err := commitNodeIndex.Get(plumbing.Hash(notes.ID.RawValue()))
if err != nil {
return err
}
lastCommits, err := GetLastCommitForPaths(ctx, nil, commitNode, "", []string{path.String()})
if err != nil {
log.Error("Unable to get the commit for the path %q. Error: %v", path.String(), err)
return err
}
note.Commit = lastCommits[path.String()]
return nil
}

View File

@@ -1,91 +0,0 @@
// Copyright 2019 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
//go:build !gogit
package git
import (
"context"
"io"
"strings"
"gitea.dev/modules/log"
)
// GetNote retrieves the git-notes data for a given commit.
// FIXME: Add LastCommitCache support
func GetNote(ctx context.Context, repo *Repository, commitID string, note *Note) error {
log.Trace("Searching for git note corresponding to the commit %q in the repository %q", commitID, repo.Path)
notes, err := repo.GetCommit(NotesRef)
if err != nil {
if IsErrNotExist(err) {
return err
}
log.Error("Unable to get commit from ref %q. Error: %v", NotesRef, err)
return err
}
path := ""
tree := &notes.Tree
log.Trace("Found tree with ID %q while searching for git note corresponding to the commit %q", tree.ID, commitID)
var entry *TreeEntry
originalCommitID := commitID
for len(commitID) > 2 {
entry, err = tree.GetTreeEntryByPath(commitID)
if err == nil {
path += commitID
break
}
if IsErrNotExist(err) {
tree, err = tree.SubTree(commitID[0:2])
path += commitID[0:2] + "/"
commitID = commitID[2:]
}
if err != nil {
// Err may have been updated by the SubTree we need to recheck if it's again an ErrNotExist
if !IsErrNotExist(err) {
log.Error("Unable to find git note corresponding to the commit %q. Error: %v", originalCommitID, err)
}
return err
}
}
blob := entry.Blob()
dataRc, err := blob.DataAsync()
if err != nil {
log.Error("Unable to read blob with ID %q. Error: %v", blob.ID, err)
return err
}
closed := false
defer func() {
if !closed {
_ = dataRc.Close()
}
}()
d, err := io.ReadAll(dataRc)
if err != nil {
log.Error("Unable to read blob with ID %q. Error: %v", blob.ID, err)
return err
}
_ = dataRc.Close()
closed = true
note.Message = d
treePath := ""
if idx := strings.LastIndex(path, "/"); idx > -1 {
treePath = path[:idx]
path = path[idx+1:]
}
lastCommits, err := GetLastCommitForPaths(ctx, notes, treePath, []string{path})
if err != nil {
log.Error("Unable to get the commit for the path %q. Error: %v", treePath, err)
return err
}
note.Commit = lastCommits[path]
return nil
}

View File

@@ -72,7 +72,7 @@ func findLFSFileFunc(repo *git.Repository, objectID git.ObjectID, revListReader
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(repo, git.MustIDFromString(commitID), io.LimitReader(batchReader, info.Size))
curCommit, err = git.CommitFromReader(git.MustIDFromString(commitID), io.LimitReader(batchReader, info.Size))
if err != nil {
return nil, err
}
@@ -80,7 +80,7 @@ func findLFSFileFunc(repo *git.Repository, objectID git.ObjectID, revListReader
return nil, err
}
if info, _, err = batch.QueryContent(curCommit.Tree.ID.String()); err != nil {
if info, _, err = batch.QueryContent(curCommit.TreeID.String()); err != nil {
return nil, err
}
curPath = ""

View File

@@ -92,15 +92,14 @@ func (repo *Repository) getCommit(id ObjectID) (*Commit, error) {
}
commit := convertCommit(gogitCommit)
commit.repo = repo
tree, err := gogitCommit.Tree()
if err != nil {
return nil, err
}
commit.Tree.ID = ParseGogitHash(tree.Hash)
commit.Tree.resolvedGogitTreeObject = tree
commit.TreeID = ParseGogitHash(tree.Hash)
commit.Tree().resolvedGogitTreeObject = tree
return commit, nil
}

View File

@@ -88,7 +88,7 @@ func (repo *Repository) getCommitWithBatch(batch CatFileBatch, id ObjectID) (*Co
}
return repo.getCommitWithBatch(batch, tag.Object)
case "commit":
commit, err := CommitFromReader(repo, id, io.LimitReader(rd, info.Size))
commit, err := CommitFromReader(id, io.LimitReader(rd, info.Size))
if err != nil {
return nil, err
}

View File

@@ -149,5 +149,5 @@ func (repo *Repository) WriteTree() (*Tree, error) {
if err != nil {
return nil, err
}
return NewTree(repo, id), nil
return newTree(id), nil
}

View File

@@ -25,7 +25,7 @@ func (repo *Repository) getTree(id ObjectID) (*Tree, error) {
return nil, err
}
tree := NewTree(repo, id)
tree := newTree(id)
tree.resolvedGogitTreeObject = gogitTree
return tree, nil
}
@@ -53,7 +53,6 @@ func (repo *Repository) GetTree(idStr string) (*Tree, error) {
if err != nil {
return nil, err
}
resolvedID := id
commitObject, err := repo.gogitRepo.CommitObject(plumbing.Hash(id.RawValue()))
if err == nil {
id = ParseGogitHash(commitObject.TreeHash)
@@ -62,6 +61,5 @@ func (repo *Repository) GetTree(idStr string) (*Tree, error) {
if err != nil {
return nil, err
}
treeObject.ResolvedID = resolvedID
return treeObject, nil
}

View File

@@ -23,7 +23,6 @@ func (repo *Repository) getTree(id ObjectID) (*Tree, error) {
switch info.Type {
case "tag":
resolvedID := id
data, err := io.ReadAll(io.LimitReader(rd, info.Size))
if err != nil {
return nil, err
@@ -37,21 +36,20 @@ func (repo *Repository) getTree(id ObjectID) (*Tree, error) {
if err != nil {
return nil, err
}
commit.Tree.ResolvedID = resolvedID
return &commit.Tree, nil
tree := commit.Tree()
return tree, nil
case "commit":
commit, err := CommitFromReader(repo, id, io.LimitReader(rd, info.Size))
commit, err := CommitFromReader(id, io.LimitReader(rd, info.Size))
if err != nil {
return nil, err
}
if _, err := rd.Discard(1); err != nil {
return nil, err
}
commit.Tree.ResolvedID = commit.ID
return &commit.Tree, nil
tree := commit.Tree()
return tree, nil
case "tree":
tree := NewTree(repo, id)
tree.ResolvedID = id
tree := newTree(id)
objectFormat, err := repo.GetObjectFormat()
if err != nil {
return nil, err

View File

@@ -6,31 +6,22 @@ package git
import (
"bytes"
"context"
"strings"
"gitea.dev/modules/git/gitcmd"
)
type TreeCommon struct {
ID ObjectID
ResolvedID ObjectID
repo *Repository
ptree *Tree // parent tree
ID ObjectID
}
// NewTree create a new tree according the repository and tree id
func NewTree(repo *Repository, id ObjectID) *Tree {
return &Tree{
TreeCommon: TreeCommon{
ID: id,
repo: repo,
},
}
func newTree(id ObjectID) *Tree {
return &Tree{TreeCommon: TreeCommon{ID: id}}
}
// SubTree get a subtree by the sub dir path
func (t *Tree) SubTree(rpath string) (*Tree, error) {
func (t *Tree) SubTree(ctx context.Context, gitRepo *Repository, rpath string) (*Tree, error) {
if len(rpath) == 0 {
return t, nil
}
@@ -43,16 +34,15 @@ func (t *Tree) SubTree(rpath string) (*Tree, error) {
te *TreeEntry
)
for _, name := range paths {
te, err = p.GetTreeEntryByPath(name)
te, err = p.GetTreeEntryByPath(ctx, gitRepo, name)
if err != nil {
return nil, err
}
g, err = t.repo.getTree(te.ID)
g, err = gitRepo.getTree(te.ID)
if err != nil {
return nil, err
}
g.ptree = p
p = g
}
return g, nil

View File

@@ -4,15 +4,17 @@
package git
import "context"
// GetBlobByPath get the blob object according the path
func (t *Tree) GetBlobByPath(relpath string) (*Blob, error) {
entry, err := t.GetTreeEntryByPath(relpath)
func (t *Tree) GetBlobByPath(ctx context.Context, gitRepo *Repository, relpath string) (*Blob, error) {
entry, err := t.GetTreeEntryByPath(ctx, gitRepo, relpath)
if err != nil {
return nil, err
}
if !entry.IsDir() && !entry.IsSubModule() {
return entry.Blob(), nil
return entry.Blob(gitRepo), nil
}
return nil, ErrNotExist{"", relpath}

View File

@@ -7,6 +7,7 @@
package git
import (
"context"
"path"
"strings"
@@ -14,7 +15,7 @@ import (
)
// GetTreeEntryByPath get the tree entries according the sub dir
func (t *Tree) GetTreeEntryByPath(relpath string) (*TreeEntry, error) {
func (t *Tree) GetTreeEntryByPath(ctx context.Context, gitRepo *Repository, relpath string) (*TreeEntry, error) {
if len(relpath) == 0 {
return &TreeEntry{
ID: t.ID,
@@ -30,7 +31,7 @@ func (t *Tree) GetTreeEntryByPath(relpath string) (*TreeEntry, error) {
tree := t
for i, name := range parts {
if i == len(parts)-1 {
entries, err := tree.ListEntries()
entries, err := tree.ListEntries(ctx, gitRepo)
if err != nil {
if err == plumbing.ErrObjectNotFound {
return nil, ErrNotExist{
@@ -45,7 +46,7 @@ func (t *Tree) GetTreeEntryByPath(relpath string) (*TreeEntry, error) {
}
}
} else {
tree, err = tree.SubTree(name)
tree, err = tree.SubTree(ctx, gitRepo, name)
if err != nil {
if err == plumbing.ErrObjectNotFound {
return nil, ErrNotExist{

View File

@@ -6,12 +6,13 @@
package git
import (
"context"
"path"
"strings"
)
// GetTreeEntryByPath get the tree entries according the sub dir
func (t *Tree) GetTreeEntryByPath(relpath string) (_ *TreeEntry, err error) {
func (t *Tree) GetTreeEntryByPath(ctx context.Context, gitRepo *Repository, relpath string) (_ *TreeEntry, err error) {
if len(relpath) == 0 {
return &TreeEntry{
ptree: t,
@@ -26,14 +27,14 @@ func (t *Tree) GetTreeEntryByPath(relpath string) (_ *TreeEntry, err error) {
tree := t
for _, name := range parts[:len(parts)-1] {
tree, err = tree.SubTree(name)
tree, err = tree.SubTree(ctx, gitRepo, name)
if err != nil {
return nil, err
}
}
name := parts[len(parts)-1]
entries, err := tree.ListEntries()
entries, err := tree.ListEntries(ctx, gitRepo)
if err != nil {
return nil, err
}

View File

@@ -5,6 +5,7 @@
package git
import (
"context"
"path"
"slices"
"strings"
@@ -78,18 +79,18 @@ type EntryFollowResult struct {
TargetEntry *TreeEntry
}
func EntryFollowLink(commit *Commit, fullPath string, te *TreeEntry) (*EntryFollowResult, error) {
func EntryFollowLink(ctx context.Context, gitRepo *Repository, commit *Commit, fullPath string, te *TreeEntry) (*EntryFollowResult, error) {
if !te.IsLink() {
return nil, util.ErrorWrap(util.ErrUnprocessableContent, "%q is not a symlink", fullPath)
}
// git's filename max length is 4096, hopefully a link won't be longer than multiple of that
const maxSymlinkSize = 20 * 4096
if te.Blob().Size() > maxSymlinkSize {
if te.Blob(gitRepo).Size() > maxSymlinkSize {
return nil, util.ErrorWrap(util.ErrUnprocessableContent, "%q content exceeds symlink limit", fullPath)
}
link, err := te.Blob().GetBlobContent(maxSymlinkSize)
link, err := te.Blob(gitRepo).GetBlobContent(maxSymlinkSize)
if err != nil {
return nil, err
}
@@ -99,18 +100,18 @@ func EntryFollowLink(commit *Commit, fullPath string, te *TreeEntry) (*EntryFoll
}
targetFullPath := path.Join(path.Dir(fullPath), link)
targetEntry, err := commit.GetTreeEntryByPath(targetFullPath)
targetEntry, err := commit.GetTreeEntryByPath(ctx, gitRepo, targetFullPath)
if err != nil {
return &EntryFollowResult{SymlinkContent: link}, err
}
return &EntryFollowResult{SymlinkContent: link, TargetFullPath: targetFullPath, TargetEntry: targetEntry}, nil
}
func EntryFollowLinks(commit *Commit, firstFullPath string, firstTreeEntry *TreeEntry, optLimit ...int) (res *EntryFollowResult, err error) {
func EntryFollowLinks(ctx context.Context, gitRepo *Repository, commit *Commit, firstFullPath string, firstTreeEntry *TreeEntry, optLimit ...int) (res *EntryFollowResult, err error) {
limit := util.OptionalArg(optLimit, 10)
treeEntry, fullPath := firstTreeEntry, firstFullPath
for range limit {
res, err = EntryFollowLink(commit, fullPath, treeEntry)
res, err = EntryFollowLink(ctx, gitRepo, commit, fullPath, treeEntry)
if err != nil {
return res, err
}
@@ -125,28 +126,26 @@ func EntryFollowLinks(commit *Commit, firstFullPath string, firstTreeEntry *Tree
return res, nil
}
// returns the Tree pointed to by this TreeEntry, or nil if this is not a tree
func (te *TreeEntry) Tree() *Tree {
t, err := te.ptree.repo.getTree(te.ID)
func (te *TreeEntry) Tree(gitRepo *Repository) *Tree {
t, err := gitRepo.getTree(te.ID)
if err != nil {
return nil
}
t.ptree = te.ptree
return t
}
// GetSubJumpablePathName return the full path of subdirectory jumpable ( contains only one directory )
func (te *TreeEntry) GetSubJumpablePathName() string {
func (te *TreeEntry) GetSubJumpablePathName(ctx context.Context, gitRepo *Repository) string {
if te.IsSubModule() || !te.IsDir() {
return ""
}
tree, err := te.ptree.SubTree(te.Name())
tree, err := te.ptree.SubTree(ctx, gitRepo, te.Name())
if err != nil {
return te.Name()
}
entries, _ := tree.ListEntries()
entries, _ := tree.ListEntries(ctx, gitRepo)
if len(entries) == 1 && entries[0].IsDir() {
name := entries[0].GetSubJumpablePathName()
name := entries[0].GetSubJumpablePathName(ctx, gitRepo)
if name != "" {
return te.Name() + "/" + name
}

View File

@@ -23,12 +23,12 @@ func TestFollowLink(t *testing.T) {
// get the symlink
{
lnkFullPath := "foo/bar/link_to_hello"
lnk, err := commit.Tree.GetTreeEntryByPath("foo/bar/link_to_hello")
lnk, err := commit.Tree().GetTreeEntryByPath(t.Context(), r, "foo/bar/link_to_hello")
require.NoError(t, err)
assert.True(t, lnk.IsLink())
// should be able to dereference to target
res, err := EntryFollowLink(commit, lnkFullPath, lnk)
res, err := EntryFollowLink(t.Context(), r, commit, lnkFullPath, lnk)
require.NoError(t, err)
assert.Equal(t, "hello", res.TargetEntry.Name())
assert.Equal(t, "foo/nar/hello", res.TargetFullPath)
@@ -38,38 +38,38 @@ func TestFollowLink(t *testing.T) {
{
// should error when called on a normal file
entry, err := commit.Tree.GetTreeEntryByPath("file1.txt")
entry, err := commit.Tree().GetTreeEntryByPath(t.Context(), r, "file1.txt")
require.NoError(t, err)
res, err := EntryFollowLink(commit, "file1.txt", entry)
res, err := EntryFollowLink(t.Context(), r, commit, "file1.txt", entry)
assert.ErrorIs(t, err, util.ErrUnprocessableContent)
assert.Nil(t, res)
}
{
// should error for broken links
entry, err := commit.Tree.GetTreeEntryByPath("foo/broken_link")
entry, err := commit.Tree().GetTreeEntryByPath(t.Context(), r, "foo/broken_link")
require.NoError(t, err)
assert.True(t, entry.IsLink())
res, err := EntryFollowLink(commit, "foo/broken_link", entry)
res, err := EntryFollowLink(t.Context(), r, commit, "foo/broken_link", entry)
assert.ErrorIs(t, err, util.ErrNotExist)
assert.Equal(t, "nar/broken_link", res.SymlinkContent)
}
{
// should error for external links
entry, err := commit.Tree.GetTreeEntryByPath("foo/outside_repo")
entry, err := commit.Tree().GetTreeEntryByPath(t.Context(), r, "foo/outside_repo")
require.NoError(t, err)
assert.True(t, entry.IsLink())
res, err := EntryFollowLink(commit, "foo/outside_repo", entry)
res, err := EntryFollowLink(t.Context(), r, commit, "foo/outside_repo", entry)
assert.ErrorIs(t, err, util.ErrNotExist)
assert.Equal(t, "../../outside_repo", res.SymlinkContent)
}
{
// testing fix for short link bug
entry, err := commit.Tree.GetTreeEntryByPath("foo/link_short")
entry, err := commit.Tree().GetTreeEntryByPath(t.Context(), r, "foo/link_short")
require.NoError(t, err)
res, err := EntryFollowLink(commit, "foo/link_short", entry)
res, err := EntryFollowLink(t.Context(), r, commit, "foo/link_short", entry)
assert.ErrorIs(t, err, util.ErrNotExist)
assert.Equal(t, "a", res.SymlinkContent)
}

View File

@@ -7,6 +7,8 @@
package git
import (
"context"
"github.com/go-git/go-git/v5/plumbing"
"github.com/go-git/go-git/v5/plumbing/filemode"
"github.com/go-git/go-git/v5/plumbing/object"
@@ -29,15 +31,15 @@ func (te *TreeEntry) toGogitTreeEntry() *object.TreeEntry {
}
}
// Size returns the size of the entry
func (te *TreeEntry) Size() int64 {
// GetSize returns the size of the entry
func (te *TreeEntry) GetSize(ctx context.Context, gitRepo *Repository) int64 {
if te.IsDir() {
return 0
} else if te.sized {
return te.size
}
ptreeGogitTree, err := te.ptree.gogitTreeObject()
ptreeGogitTree, err := te.ptree.gogitTreeObject(gitRepo)
if err != nil {
return 0
}
@@ -52,10 +54,10 @@ func (te *TreeEntry) Size() int64 {
}
// Blob returns the blob object the entry
func (te *TreeEntry) Blob() *Blob {
func (te *TreeEntry) Blob(gitRepo *Repository) *Blob {
return &Blob{
ID: te.ID,
repo: te.ptree.repo,
repo: gitRepo,
name: te.Name(),
}
}

View File

@@ -5,25 +5,28 @@
package git
import "gitea.dev/modules/log"
import (
"context"
// Size returns the size of the entry
func (te *TreeEntry) Size() int64 {
"gitea.dev/modules/log"
)
func (te *TreeEntry) GetSize(ctx context.Context, gitRepo *Repository) int64 {
if te.IsDir() {
return 0
} else if te.sized {
return te.size
}
batch, cancel, err := te.ptree.repo.CatFileBatch(te.ptree.repo.Ctx)
batch, cancel, err := gitRepo.CatFileBatch(ctx)
if err != nil {
log.Debug("error whilst reading size for %s in %s. Error: %v", te.ID.String(), te.ptree.repo.Path, err)
log.Debug("error whilst reading size for %s in %s. Error: %v", te.ID.String(), gitRepo.Path, err)
return 0
}
defer cancel()
info, err := batch.QueryInfo(te.ID.String())
if err != nil {
log.Debug("error whilst reading size for %s in %s. Error: %v", te.ID.String(), te.ptree.repo.Path, err)
log.Debug("error whilst reading size for %s in %s. Error: %v", te.ID.String(), gitRepo.Path, err)
return 0
}
@@ -33,12 +36,12 @@ func (te *TreeEntry) Size() int64 {
}
// Blob returns the blob object the entry
func (te *TreeEntry) Blob() *Blob {
func (te *TreeEntry) Blob(gitRepo *Repository) *Blob {
return &Blob{
ID: te.ID,
name: te.Name(),
size: te.size,
gotSize: te.sized,
repo: te.ptree.repo,
repo: gitRepo,
}
}

View File

@@ -7,6 +7,7 @@
package git
import (
"context"
"io"
"github.com/go-git/go-git/v5/plumbing"
@@ -20,9 +21,9 @@ type Tree struct {
resolvedGogitTreeObject *object.Tree
}
func (t *Tree) gogitTreeObject() (_ *object.Tree, err error) {
func (t *Tree) gogitTreeObject(gitRepo *Repository) (_ *object.Tree, err error) {
if t.resolvedGogitTreeObject == nil {
t.resolvedGogitTreeObject, err = t.repo.gogitRepo.TreeObject(plumbing.Hash(t.ID.RawValue()))
t.resolvedGogitTreeObject, err = gitRepo.gogitRepo.TreeObject(plumbing.Hash(t.ID.RawValue()))
if err != nil {
return nil, err
}
@@ -31,8 +32,8 @@ func (t *Tree) gogitTreeObject() (_ *object.Tree, err error) {
}
// ListEntries returns all entries of current tree.
func (t *Tree) ListEntries() (Entries, error) {
gogitTree, err := t.gogitTreeObject()
func (t *Tree) ListEntries(_ context.Context, gitRepo *Repository) (Entries, error) {
gogitTree, err := t.gogitTreeObject(gitRepo)
if err != nil {
return nil, err
}
@@ -50,8 +51,8 @@ func (t *Tree) ListEntries() (Entries, error) {
}
// ListEntriesRecursiveWithSize returns all entries of current tree recursively including all subtrees
func (t *Tree) ListEntriesRecursiveWithSize() (entries Entries, _ error) {
gogitTree, err := t.gogitTreeObject()
func (t *Tree) ListEntriesRecursiveWithSize(_ context.Context, gitRepo *Repository) (entries Entries, _ error) {
gogitTree, err := t.gogitTreeObject(gitRepo)
if err != nil {
return nil, err
}
@@ -76,6 +77,6 @@ func (t *Tree) ListEntriesRecursiveWithSize() (entries Entries, _ error) {
}
// ListEntriesRecursiveFast is the alias of ListEntriesRecursiveWithSize for the gogit version
func (t *Tree) ListEntriesRecursiveFast() (Entries, error) {
return t.ListEntriesRecursiveWithSize()
func (t *Tree) ListEntriesRecursiveFast(ctx context.Context, gitRepo *Repository) (Entries, error) {
return t.ListEntriesRecursiveWithSize(ctx, gitRepo)
}

View File

@@ -6,6 +6,7 @@
package git
import (
"context"
"io"
"gitea.dev/modules/git/gitcmd"
@@ -20,49 +21,47 @@ type Tree struct {
}
// ListEntries returns all entries of current tree.
func (t *Tree) ListEntries() (Entries, error) {
func (t *Tree) ListEntries(ctx context.Context, gitRepo *Repository) (Entries, error) {
if t.entriesParsed {
return t.entries, nil
}
if t.repo != nil {
batch, cancel, err := t.repo.CatFileBatch(t.repo.Ctx)
if err != nil {
batch, cancel, err := gitRepo.CatFileBatch(ctx)
if err != nil {
return nil, err
}
defer cancel()
info, rd, err := batch.QueryContent(t.ID.String())
if err != nil {
return nil, err
}
if info.Type == "commit" {
treeID, err := ReadTreeID(rd, info.Size)
if err != nil && err != io.EOF {
return nil, err
}
defer cancel()
info, rd, err := batch.QueryContent(t.ID.String())
info, rd, err = batch.QueryContent(treeID)
if err != nil {
return nil, err
}
if info.Type == "commit" {
treeID, err := ReadTreeID(rd, info.Size)
if err != nil && err != io.EOF {
return nil, err
}
info, rd, err = batch.QueryContent(treeID)
if err != nil {
return nil, err
}
}
if info.Type == "tree" {
t.entries, err = catBatchParseTreeEntries(t.ID.Type(), t, rd, info.Size)
if err != nil {
return nil, err
}
t.entriesParsed = true
return t.entries, nil
}
// Not a tree just use ls-tree instead
if err := DiscardFull(rd, info.Size+1); err != nil {
return nil, err
}
}
if info.Type == "tree" {
t.entries, err = catBatchParseTreeEntries(t.ID.Type(), t, rd, info.Size)
if err != nil {
return nil, err
}
t.entriesParsed = true
return t.entries, nil
}
stdout, _, runErr := gitcmd.NewCommand("ls-tree", "-l").AddDynamicArguments(t.ID.String()).WithDir(t.repo.Path).RunStdBytes(t.repo.Ctx)
// Not a tree just use ls-tree instead
if err := DiscardFull(rd, info.Size+1); err != nil {
return nil, err
}
stdout, _, runErr := gitcmd.NewCommand("ls-tree", "-l").AddDynamicArguments(t.ID.String()).WithDir(gitRepo.Path).RunStdBytes(ctx)
if runErr != nil {
if gitcmd.IsStderr(runErr, gitcmd.StderrNotValidObjectName) || gitcmd.IsStderr(runErr, gitcmd.StderrNotTreeObject) {
return nil, ErrNotExist{
@@ -72,7 +71,6 @@ func (t *Tree) ListEntries() (Entries, error) {
return nil, runErr
}
var err error
t.entries, err = parseTreeEntries(stdout, t)
if err == nil {
t.entriesParsed = true
@@ -83,12 +81,12 @@ func (t *Tree) ListEntries() (Entries, error) {
// listEntriesRecursive returns all entries of current tree recursively including all subtrees
// extraArgs could be "-l" to get the size, which is slower
func (t *Tree) listEntriesRecursive(extraArgs gitcmd.TrustedCmdArgs) (Entries, error) {
func (t *Tree) listEntriesRecursive(ctx context.Context, gitRepo *Repository, extraArgs gitcmd.TrustedCmdArgs) (Entries, error) {
stdout, _, runErr := gitcmd.NewCommand("ls-tree", "-t", "-r").
AddArguments(extraArgs...).
AddDynamicArguments(t.ID.String()).
WithDir(t.repo.Path).
RunStdBytes(t.repo.Ctx)
WithDir(gitRepo.Path).
RunStdBytes(ctx)
if runErr != nil {
return nil, runErr
}
@@ -99,11 +97,11 @@ func (t *Tree) listEntriesRecursive(extraArgs gitcmd.TrustedCmdArgs) (Entries, e
}
// ListEntriesRecursiveFast returns all entries of current tree recursively including all subtrees, no size
func (t *Tree) ListEntriesRecursiveFast() (Entries, error) {
return t.listEntriesRecursive(nil)
func (t *Tree) ListEntriesRecursiveFast(ctx context.Context, gitRepo *Repository) (Entries, error) {
return t.listEntriesRecursive(ctx, gitRepo, nil)
}
// ListEntriesRecursiveWithSize returns all entries of current tree recursively including all subtrees, with size
func (t *Tree) ListEntriesRecursiveWithSize() (Entries, error) {
return t.listEntriesRecursive(gitcmd.TrustedCmdArgs{"--long"})
func (t *Tree) ListEntriesRecursiveWithSize(ctx context.Context, gitRepo *Repository) (Entries, error) {
return t.listEntriesRecursive(ctx, gitRepo, gitcmd.TrustedCmdArgs{"--long"})
}

View File

@@ -20,7 +20,7 @@ func TestSubTree_Issue29101(t *testing.T) {
// old code could produce a different error if called multiple times
for range 10 {
_, err = commit.SubTree("file1.txt")
_, err = commit.SubTree(t.Context(), repo, "file1.txt")
assert.Error(t, err)
assert.True(t, IsErrNotExist(err))
}