--- /dev/null
+// Copyright 2024 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package git
+
+import (
+ "bufio"
+ "context"
+)
+
+type Batch struct {
+ cancel context.CancelFunc
+ Reader *bufio.Reader
+ Writer WriteCloserError
+}
+
+func (repo *Repository) NewBatch(ctx context.Context) (*Batch, error) {
+ // Now because of some insanity with git cat-file not immediately failing if not run in a valid git directory we need to run git rev-parse first!
+ if err := ensureValidGitRepository(ctx, repo.Path); err != nil {
+ return nil, err
+ }
+
+ var batch Batch
+ batch.Writer, batch.Reader, batch.cancel = catFileBatch(ctx, repo.Path)
+ return &batch, nil
+}
+
+func (repo *Repository) NewBatchCheck(ctx context.Context) (*Batch, error) {
+ // Now because of some insanity with git cat-file not immediately failing if not run in a valid git directory we need to run git rev-parse first!
+ if err := ensureValidGitRepository(ctx, repo.Path); err != nil {
+ return nil, err
+ }
+
+ var check Batch
+ check.Writer, check.Reader, check.cancel = catFileBatchCheck(ctx, repo.Path)
+ return &check, nil
+}
+
+func (b *Batch) Close() {
+ if b.cancel != nil {
+ b.cancel()
+ b.Reader = nil
+ b.Writer = nil
+ b.cancel = nil
+ }
+}
CloseWithError(err error) error
}
-// EnsureValidGitRepository runs git rev-parse in the repository path - thus ensuring that the repository is a valid repository.
+// ensureValidGitRepository runs git rev-parse in the repository path - thus ensuring that the repository is a valid repository.
// Run before opening git cat-file.
// This is needed otherwise the git cat-file will hang for invalid repositories.
-func EnsureValidGitRepository(ctx context.Context, repoPath string) error {
+func ensureValidGitRepository(ctx context.Context, repoPath string) error {
stderr := strings.Builder{}
err := NewCommand(ctx, "rev-parse").
SetDescription(fmt.Sprintf("%s rev-parse [repo_path: %s]", GitExecutable, repoPath)).
return nil
}
-// CatFileBatchCheck opens git cat-file --batch-check in the provided repo and returns a stdin pipe, a stdout reader and cancel function
-func CatFileBatchCheck(ctx context.Context, repoPath string) (WriteCloserError, *bufio.Reader, func()) {
+// catFileBatchCheck opens git cat-file --batch-check in the provided repo and returns a stdin pipe, a stdout reader and cancel function
+func catFileBatchCheck(ctx context.Context, repoPath string) (WriteCloserError, *bufio.Reader, func()) {
batchStdinReader, batchStdinWriter := io.Pipe()
batchStdoutReader, batchStdoutWriter := io.Pipe()
ctx, ctxCancel := context.WithCancel(ctx)
return batchStdinWriter, batchReader, cancel
}
-// CatFileBatch opens git cat-file --batch in the provided repo and returns a stdin pipe, a stdout reader and cancel function
-func CatFileBatch(ctx context.Context, repoPath string) (WriteCloserError, *bufio.Reader, func()) {
+// catFileBatch opens git cat-file --batch in the provided repo and returns a stdin pipe, a stdout reader and cancel function
+func catFileBatch(ctx context.Context, repoPath string) (WriteCloserError, *bufio.Reader, func()) {
// We often want to 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
batchStdinReader, batchStdinWriter := io.Pipe()
// DataAsync gets a ReadCloser for the contents of a blob without reading it all.
// Calling the Close function on the result will discard all unread output.
func (b *Blob) DataAsync() (io.ReadCloser, error) {
- wr, rd, cancel := b.repo.CatFileBatch(b.repo.Ctx)
+ wr, rd, cancel, err := b.repo.CatFileBatch(b.repo.Ctx)
+ if err != nil {
+ return nil, err
+ }
- _, err := wr.Write([]byte(b.ID.String() + "\n"))
+ _, err = wr.Write([]byte(b.ID.String() + "\n"))
if err != nil {
cancel()
return nil, err
return b.size
}
- wr, rd, cancel := b.repo.CatFileBatchCheck(b.repo.Ctx)
+ wr, rd, cancel, err := b.repo.CatFileBatchCheck(b.repo.Ctx)
+ if err != nil {
+ log.Debug("error whilst reading size for %s in %s. Error: %v", b.ID.String(), b.repo.Path, err)
+ return 0
+ }
defer cancel()
- _, err := wr.Write([]byte(b.ID.String() + "\n"))
+ _, err = wr.Write([]byte(b.ID.String() + "\n"))
if err != nil {
log.Debug("error whilst reading size for %s in %s. Error: %v", b.ID.String(), b.repo.Path, err)
return 0
return nil, err
}
- batchStdinWriter, batchReader, cancel := commit.repo.CatFileBatch(ctx)
+ batchStdinWriter, batchReader, cancel, err := commit.repo.CatFileBatch(ctx)
+ if err != nil {
+ return nil, err
+ }
defer cancel()
commitsMap := map[string]*Commit{}
// 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
- batchStdinWriter, batchReader, cancel := repo.CatFileBatch(repo.Ctx)
+ batchStdinWriter, batchReader, 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
gpgSettings *GPGSettings
- batchInUse bool
- batchCancel context.CancelFunc
- batchReader *bufio.Reader
- batchWriter WriteCloserError
+ batchInUse bool
+ batch *Batch
- checkInUse bool
- checkCancel context.CancelFunc
- checkReader *bufio.Reader
- checkWriter WriteCloserError
+ checkInUse bool
+ check *Batch
Ctx context.Context
LastCommitCache *LastCommitCache
return nil, util.NewNotExistErrorf("no such file or directory")
}
- // Now because of some insanity with git cat-file not immediately failing if not run in a valid git directory we need to run git rev-parse first!
- if err := EnsureValidGitRepository(ctx, repoPath); err != nil {
- return nil, err
- }
-
- repo := &Repository{
+ return &Repository{
Path: repoPath,
tagCache: newObjectCache(),
Ctx: ctx,
- }
-
- repo.batchWriter, repo.batchReader, repo.batchCancel = CatFileBatch(ctx, repoPath)
- repo.checkWriter, repo.checkReader, repo.checkCancel = CatFileBatchCheck(ctx, repoPath)
-
- return repo, nil
+ }, nil
}
// CatFileBatch obtains a CatFileBatch for this repository
-func (repo *Repository) CatFileBatch(ctx context.Context) (WriteCloserError, *bufio.Reader, func()) {
- if repo.batchCancel == nil || repo.batchInUse {
- log.Debug("Opening temporary cat file batch for: %s", repo.Path)
- return CatFileBatch(ctx, repo.Path)
+func (repo *Repository) CatFileBatch(ctx context.Context) (WriteCloserError, *bufio.Reader, func(), error) {
+ if repo.batch == nil {
+ var err error
+ repo.batch, err = repo.NewBatch(ctx)
+ if err != nil {
+ return nil, nil, nil, err
+ }
}
- repo.batchInUse = true
- return repo.batchWriter, repo.batchReader, func() {
- repo.batchInUse = false
+
+ if !repo.batchInUse {
+ repo.batchInUse = true
+ return repo.batch.Writer, repo.batch.Reader, func() {
+ repo.batchInUse = false
+ }, nil
}
+
+ log.Debug("Opening temporary cat file batch for: %s", repo.Path)
+ tempBatch, err := repo.NewBatch(ctx)
+ if err != nil {
+ return nil, nil, nil, err
+ }
+ return tempBatch.Writer, tempBatch.Reader, tempBatch.Close, nil
}
// CatFileBatchCheck obtains a CatFileBatchCheck for this repository
-func (repo *Repository) CatFileBatchCheck(ctx context.Context) (WriteCloserError, *bufio.Reader, func()) {
- if repo.checkCancel == nil || repo.checkInUse {
- log.Debug("Opening temporary cat file batch-check for: %s", repo.Path)
- return CatFileBatchCheck(ctx, repo.Path)
+func (repo *Repository) CatFileBatchCheck(ctx context.Context) (WriteCloserError, *bufio.Reader, func(), error) {
+ if repo.check == nil {
+ var err error
+ repo.check, err = repo.NewBatchCheck(ctx)
+ if err != nil {
+ return nil, nil, nil, err
+ }
}
- repo.checkInUse = true
- return repo.checkWriter, repo.checkReader, func() {
- repo.checkInUse = false
+
+ if !repo.checkInUse {
+ repo.checkInUse = true
+ return repo.check.Writer, repo.check.Reader, func() {
+ repo.checkInUse = false
+ }, nil
+ }
+
+ log.Debug("Opening temporary cat file batch-check for: %s", repo.Path)
+ tempBatchCheck, err := repo.NewBatchCheck(ctx)
+ if err != nil {
+ return nil, nil, nil, err
}
+ return tempBatchCheck.Writer, tempBatchCheck.Reader, tempBatchCheck.Close, nil
}
func (repo *Repository) Close() error {
if repo == nil {
return nil
}
- if repo.batchCancel != nil {
- repo.batchCancel()
- repo.batchReader = nil
- repo.batchWriter = nil
- repo.batchCancel = nil
+ if repo.batch != nil {
+ repo.batch.Close()
+ repo.batch = nil
repo.batchInUse = false
}
- if repo.checkCancel != nil {
- repo.checkCancel()
- repo.checkCancel = nil
- repo.checkReader = nil
- repo.checkWriter = nil
+ if repo.check != nil {
+ repo.check.Close()
+ repo.check = nil
repo.checkInUse = false
}
repo.LastCommitCache = nil
return false
}
- wr, rd, cancel := repo.CatFileBatchCheck(repo.Ctx)
+ wr, rd, cancel, err := repo.CatFileBatchCheck(repo.Ctx)
+ if err != nil {
+ log.Debug("Error writing to CatFileBatchCheck %v", err)
+ return false
+ }
defer cancel()
- _, err := wr.Write([]byte(name + "\n"))
+ _, err = wr.Write([]byte(name + "\n"))
if err != nil {
log.Debug("Error writing to CatFileBatchCheck %v", err)
return false
return false
}
- wr, rd, cancel := repo.CatFileBatchCheck(repo.Ctx)
+ wr, rd, cancel, err := repo.CatFileBatchCheck(repo.Ctx)
+ if err != nil {
+ log.Debug("Error writing to CatFileBatchCheck %v", err)
+ return false
+ }
defer cancel()
- _, err := wr.Write([]byte(name + "\n"))
+ _, err = wr.Write([]byte(name + "\n"))
if err != nil {
log.Debug("Error writing to CatFileBatchCheck %v", err)
return false
// GetRefCommitID returns the last commit ID string of given reference (branch or tag).
func (repo *Repository) GetRefCommitID(name string) (string, error) {
- wr, rd, cancel := repo.CatFileBatchCheck(repo.Ctx)
+ wr, rd, cancel, err := repo.CatFileBatchCheck(repo.Ctx)
+ if err != nil {
+ return "", err
+ }
defer cancel()
- _, err := wr.Write([]byte(name + "\n"))
+ _, err = wr.Write([]byte(name + "\n"))
if err != nil {
return "", err
}
// IsCommitExist returns true if given commit exists in current repository.
func (repo *Repository) IsCommitExist(name string) bool {
+ if err := ensureValidGitRepository(repo.Ctx, repo.Path); err != nil {
+ log.Error("IsCommitExist: %v", err)
+ return false
+ }
_, _, err := NewCommand(repo.Ctx, "cat-file", "-e").AddDynamicArguments(name).RunStdString(&RunOpts{Dir: repo.Path})
return err == nil
}
func (repo *Repository) getCommit(id ObjectID) (*Commit, error) {
- wr, rd, cancel := repo.CatFileBatch(repo.Ctx)
+ wr, rd, cancel, err := repo.CatFileBatch(repo.Ctx)
+ if err != nil {
+ return nil, err
+ }
defer cancel()
_, _ = wr.Write([]byte(id.String() + "\n"))
}
}
- wr, rd, cancel := repo.CatFileBatchCheck(repo.Ctx)
+ wr, rd, cancel, err := repo.CatFileBatchCheck(repo.Ctx)
+ if err != nil {
+ return nil, err
+ }
defer cancel()
_, err = wr.Write([]byte(commitID + "\n"))
if err != nil {
func (repo *Repository) GetLanguageStats(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
- batchStdinWriter, batchReader, cancel := repo.CatFileBatch(repo.Ctx)
+ batchStdinWriter, batchReader, cancel, err := repo.CatFileBatch(repo.Ctx)
+ if err != nil {
+ return nil, err
+ }
defer cancel()
writeID := func(id string) error {
// GetTagType gets the type of the tag, either commit (simple) or tag (annotated)
func (repo *Repository) GetTagType(id ObjectID) (string, error) {
- wr, rd, cancel := repo.CatFileBatchCheck(repo.Ctx)
+ wr, rd, cancel, err := repo.CatFileBatchCheck(repo.Ctx)
+ if err != nil {
+ return "", err
+ }
defer cancel()
- _, err := wr.Write([]byte(id.String() + "\n"))
+ _, err = wr.Write([]byte(id.String() + "\n"))
if err != nil {
return "", err
}
}
// The tag is an annotated tag with a message.
- wr, rd, cancel := repo.CatFileBatch(repo.Ctx)
+ wr, rd, cancel, err := repo.CatFileBatch(repo.Ctx)
+ if err != nil {
+ return nil, err
+ }
defer cancel()
if _, err := wr.Write([]byte(tagID.String() + "\n")); err != nil {
)
func (repo *Repository) getTree(id ObjectID) (*Tree, error) {
- wr, rd, cancel := repo.CatFileBatch(repo.Ctx)
+ wr, rd, cancel, err := repo.CatFileBatch(repo.Ctx)
+ if err != nil {
+ return nil, err
+ }
defer cancel()
_, _ = wr.Write([]byte(id.String() + "\n"))
return te.size
}
- wr, rd, cancel := te.ptree.repo.CatFileBatchCheck(te.ptree.repo.Ctx)
+ wr, rd, cancel, err := te.ptree.repo.CatFileBatchCheck(te.ptree.repo.Ctx)
+ if err != nil {
+ log.Debug("error whilst reading size for %s in %s. Error: %v", te.ID.String(), te.ptree.repo.Path, err)
+ return 0
+ }
defer cancel()
- _, err := wr.Write([]byte(te.ID.String() + "\n"))
+ _, err = wr.Write([]byte(te.ID.String() + "\n"))
if err != nil {
log.Debug("error whilst reading size for %s in %s. Error: %v", te.ID.String(), te.ptree.repo.Path, err)
return 0
}
if t.repo != nil {
- wr, rd, cancel := t.repo.CatFileBatch(t.repo.Ctx)
+ wr, rd, cancel, err := t.repo.CatFileBatch(t.repo.Ctx)
+ if err != nil {
+ return nil, err
+ }
defer cancel()
_, _ = wr.Write([]byte(t.ID.String() + "\n"))
"code.gitea.io/gitea/modules/analyze"
"code.gitea.io/gitea/modules/charset"
"code.gitea.io/gitea/modules/git"
+ "code.gitea.io/gitea/modules/gitrepo"
"code.gitea.io/gitea/modules/indexer/code/internal"
indexer_internal "code.gitea.io/gitea/modules/indexer/internal"
inner_bleve "code.gitea.io/gitea/modules/indexer/internal/bleve"
- "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/timeutil"
"code.gitea.io/gitea/modules/typesniffer"
func (b *Indexer) Index(ctx context.Context, repo *repo_model.Repository, sha string, changes *internal.RepoChanges) error {
batch := inner_bleve.NewFlushingBatch(b.inner.Indexer, maxBatchSize)
if len(changes.Updates) > 0 {
- // Now because of some insanity with git cat-file not immediately failing if not run in a valid git directory we need to run git rev-parse first!
- if err := git.EnsureValidGitRepository(ctx, repo.RepoPath()); err != nil {
- log.Error("Unable to open git repo: %s for %-v: %v", repo.RepoPath(), repo, err)
+ r, err := gitrepo.OpenRepository(ctx, repo)
+ if err != nil {
return err
}
-
- batchWriter, batchReader, cancel := git.CatFileBatch(ctx, repo.RepoPath())
- defer cancel()
+ defer r.Close()
+ gitBatch, err := r.NewBatch(ctx)
+ if err != nil {
+ return err
+ }
+ defer gitBatch.Close()
for _, update := range changes.Updates {
- if err := b.addUpdate(ctx, batchWriter, batchReader, sha, update, repo, batch); err != nil {
+ if err := b.addUpdate(ctx, gitBatch.Writer, gitBatch.Reader, sha, update, repo, batch); err != nil {
return err
}
}
- cancel()
+ gitBatch.Close()
}
for _, filename := range changes.RemovedFilenames {
if err := b.addDelete(filename, repo, batch); err != nil {
"code.gitea.io/gitea/modules/analyze"
"code.gitea.io/gitea/modules/charset"
"code.gitea.io/gitea/modules/git"
+ "code.gitea.io/gitea/modules/gitrepo"
"code.gitea.io/gitea/modules/indexer/code/internal"
indexer_internal "code.gitea.io/gitea/modules/indexer/internal"
inner_elasticsearch "code.gitea.io/gitea/modules/indexer/internal/elasticsearch"
"code.gitea.io/gitea/modules/json"
- "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/timeutil"
"code.gitea.io/gitea/modules/typesniffer"
func (b *Indexer) Index(ctx context.Context, repo *repo_model.Repository, sha string, changes *internal.RepoChanges) error {
reqs := make([]elastic.BulkableRequest, 0)
if len(changes.Updates) > 0 {
- // Now because of some insanity with git cat-file not immediately failing if not run in a valid git directory we need to run git rev-parse first!
- if err := git.EnsureValidGitRepository(ctx, repo.RepoPath()); err != nil {
- log.Error("Unable to open git repo: %s for %-v: %v", repo.RepoPath(), repo, err)
+ r, err := gitrepo.OpenRepository(ctx, repo)
+ if err != nil {
return err
}
-
- batchWriter, batchReader, cancel := git.CatFileBatch(ctx, repo.RepoPath())
- defer cancel()
+ defer r.Close()
+ batch, err := r.NewBatch(ctx)
+ if err != nil {
+ return err
+ }
+ defer batch.Close()
for _, update := range changes.Updates {
- updateReqs, err := b.addUpdate(ctx, batchWriter, batchReader, sha, update, repo)
+ updateReqs, err := b.addUpdate(ctx, batch.Writer, batch.Reader, sha, update, repo)
if err != nil {
return err
}
reqs = append(reqs, updateReqs...)
}
}
- cancel()
+ batch.Close()
}
for _, filename := range changes.RemovedFilenames {