mirror of
https://github.com/go-gitea/gitea.git
synced 2026-01-22 23:42:44 +01:00
Most potential deadlock problems should have been fixed, and new code is unlikely to cause new problems with the new design. Also raise the minimum Git version required to 2.6.0 (released in 2015)
210 lines
4.7 KiB
Go
210 lines
4.7 KiB
Go
// Copyright 2019 The Gitea Authors. All rights reserved.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package attribute
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"fmt"
|
|
"io"
|
|
"path/filepath"
|
|
"time"
|
|
|
|
"code.gitea.io/gitea/modules/git"
|
|
"code.gitea.io/gitea/modules/git/gitcmd"
|
|
"code.gitea.io/gitea/modules/log"
|
|
)
|
|
|
|
// BatchChecker provides a reader for check-attribute content that can be long running
|
|
type BatchChecker struct {
|
|
attributesNum int
|
|
repo *git.Repository
|
|
stdinWriter io.WriteCloser
|
|
stdOut *nulSeparatedAttributeWriter
|
|
ctx context.Context
|
|
cancel context.CancelFunc
|
|
cmd *gitcmd.Command
|
|
}
|
|
|
|
// NewBatchChecker creates a check attribute reader for the current repository and provided commit ID
|
|
// If treeish is empty, then it will use current working directory, otherwise it will use the provided treeish on the bare repo
|
|
func NewBatchChecker(repo *git.Repository, treeish string, attributes []string) (checker *BatchChecker, returnedErr error) {
|
|
ctx, cancel := context.WithCancel(repo.Ctx)
|
|
defer func() {
|
|
if returnedErr != nil {
|
|
cancel()
|
|
}
|
|
}()
|
|
|
|
cmd, envs, cleanup, err := checkAttrCommand(repo, treeish, nil, attributes)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer func() {
|
|
if returnedErr != nil {
|
|
cleanup()
|
|
}
|
|
}()
|
|
|
|
cmd.AddArguments("--stdin")
|
|
|
|
checker = &BatchChecker{
|
|
attributesNum: len(attributes),
|
|
repo: repo,
|
|
ctx: ctx,
|
|
cmd: cmd,
|
|
cancel: func() {
|
|
cancel()
|
|
cleanup()
|
|
},
|
|
}
|
|
|
|
stdinWriter, stdinWriterClose := cmd.MakeStdinPipe()
|
|
checker.stdinWriter = stdinWriter
|
|
|
|
lw := new(nulSeparatedAttributeWriter)
|
|
lw.attributes = make(chan attributeTriple, len(attributes))
|
|
lw.closed = make(chan struct{})
|
|
checker.stdOut = lw
|
|
|
|
cmd.WithEnv(envs).
|
|
WithDir(repo.Path).
|
|
WithStdoutCopy(lw)
|
|
|
|
go func() {
|
|
defer stdinWriterClose()
|
|
defer checker.cancel()
|
|
defer lw.Close()
|
|
|
|
err := cmd.RunWithStderr(ctx)
|
|
if err != nil && !gitcmd.IsErrorCanceledOrKilled(err) {
|
|
log.Error("Attribute checker for commit %s exits with error: %v", treeish, err)
|
|
}
|
|
}()
|
|
|
|
return checker, nil
|
|
}
|
|
|
|
// CheckPath check attr for given path
|
|
func (c *BatchChecker) CheckPath(path string) (rs *Attributes, err error) {
|
|
defer func() {
|
|
if err != nil && err != c.ctx.Err() {
|
|
log.Error("Unexpected error when checking path %s in %s, error: %v", path, filepath.Base(c.repo.Path), err)
|
|
}
|
|
}()
|
|
|
|
select {
|
|
case <-c.ctx.Done():
|
|
return nil, c.ctx.Err()
|
|
default:
|
|
}
|
|
|
|
if _, err = c.stdinWriter.Write([]byte(path + "\x00")); err != nil {
|
|
defer c.Close()
|
|
return nil, err
|
|
}
|
|
|
|
reportTimeout := func() error {
|
|
stdOutClosed := false
|
|
select {
|
|
case <-c.stdOut.closed:
|
|
stdOutClosed = true
|
|
default:
|
|
}
|
|
debugMsg := fmt.Sprintf("check path %q in repo %q", path, filepath.Base(c.repo.Path))
|
|
debugMsg += fmt.Sprintf(", stdOut: tmp=%q, pos=%d, closed=%v", string(c.stdOut.tmp), c.stdOut.pos, stdOutClosed)
|
|
if c.cmd != nil {
|
|
debugMsg += fmt.Sprintf(", process state: %q", c.cmd.ProcessState())
|
|
}
|
|
_ = c.Close()
|
|
return fmt.Errorf("CheckPath timeout: %s", debugMsg)
|
|
}
|
|
|
|
rs = NewAttributes()
|
|
for i := 0; i < c.attributesNum; i++ {
|
|
select {
|
|
case <-time.After(5 * time.Second):
|
|
// there is no "hang" problem now. This code is just used to catch other potential problems.
|
|
return nil, reportTimeout()
|
|
case attr, ok := <-c.stdOut.ReadAttribute():
|
|
if !ok {
|
|
return nil, c.ctx.Err()
|
|
}
|
|
rs.m[attr.Attribute] = Attribute(attr.Value)
|
|
case <-c.ctx.Done():
|
|
return nil, c.ctx.Err()
|
|
}
|
|
}
|
|
return rs, nil
|
|
}
|
|
|
|
func (c *BatchChecker) Close() error {
|
|
c.cancel()
|
|
err := c.stdinWriter.Close()
|
|
return err
|
|
}
|
|
|
|
type attributeTriple struct {
|
|
Filename string
|
|
Attribute string
|
|
Value string
|
|
}
|
|
|
|
type nulSeparatedAttributeWriter struct {
|
|
tmp []byte
|
|
attributes chan attributeTriple
|
|
closed chan struct{}
|
|
working attributeTriple
|
|
pos int
|
|
}
|
|
|
|
func (wr *nulSeparatedAttributeWriter) Write(p []byte) (n int, err error) {
|
|
l, read := len(p), 0
|
|
|
|
nulIdx := bytes.IndexByte(p, '\x00')
|
|
for nulIdx >= 0 {
|
|
wr.tmp = append(wr.tmp, p[:nulIdx]...)
|
|
switch wr.pos {
|
|
case 0:
|
|
wr.working = attributeTriple{
|
|
Filename: string(wr.tmp),
|
|
}
|
|
case 1:
|
|
wr.working.Attribute = string(wr.tmp)
|
|
case 2:
|
|
wr.working.Value = string(wr.tmp)
|
|
}
|
|
wr.tmp = wr.tmp[:0]
|
|
wr.pos++
|
|
if wr.pos > 2 {
|
|
wr.attributes <- wr.working
|
|
wr.pos = 0
|
|
}
|
|
read += nulIdx + 1
|
|
if l > read {
|
|
p = p[nulIdx+1:]
|
|
nulIdx = bytes.IndexByte(p, '\x00')
|
|
} else {
|
|
return l, nil
|
|
}
|
|
}
|
|
wr.tmp = append(wr.tmp, p...)
|
|
return l, nil
|
|
}
|
|
|
|
func (wr *nulSeparatedAttributeWriter) ReadAttribute() <-chan attributeTriple {
|
|
return wr.attributes
|
|
}
|
|
|
|
func (wr *nulSeparatedAttributeWriter) Close() error {
|
|
select {
|
|
case <-wr.closed:
|
|
return nil
|
|
default:
|
|
}
|
|
close(wr.attributes)
|
|
close(wr.closed)
|
|
return nil
|
|
}
|