forked from gitea/gitea
1
0
Fork 0

Make Requests Processes and create process hierarchy. Associate OpenRepository with context. (#17125)

This PR registers requests with the process manager and manages hierarchy within the processes.

Git repos are then associated with a context, (usually the request's context) - with sub commands using this context as their base context.

Signed-off-by: Andrew Thornton <art27@cantab.net>
This commit is contained in:
zeripath 2021-11-30 20:06:32 +00:00 committed by GitHub
parent d894c90b70
commit 01087e9eef
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
66 changed files with 591 additions and 306 deletions

View File

@ -309,7 +309,7 @@ func runHookPostReceive(c *cli.Context) error {
defer cancel() defer cancel()
// First of all run update-server-info no matter what // First of all run update-server-info no matter what
if _, err := git.NewCommand("update-server-info").SetParentContext(ctx).Run(); err != nil { if _, err := git.NewCommandContext(ctx, "update-server-info").Run(); err != nil {
return fmt.Errorf("Failed to call 'git update-server-info': %v", err) return fmt.Errorf("Failed to call 'git update-server-info': %v", err)
} }

View File

@ -534,7 +534,7 @@ func RepoAssignment(ctx *Context) (cancel context.CancelFunc) {
return return
} }
gitRepo, err := git.OpenRepository(models.RepoPath(userName, repoName)) gitRepo, err := git.OpenRepositoryCtx(ctx, models.RepoPath(userName, repoName))
if err != nil { if err != nil {
if strings.Contains(err.Error(), "repository does not exist") || strings.Contains(err.Error(), "no such file or directory") { if strings.Contains(err.Error(), "repository does not exist") || strings.Contains(err.Error(), "no such file or directory") {
log.Error("Repository %-v has a broken repository on the file system: %s Error: %v", ctx.Repo.Repository, ctx.Repo.Repository.RepoPath(), err) log.Error("Repository %-v has a broken repository on the file system: %s Error: %v", ctx.Repo.Repository, ctx.Repo.Repository.RepoPath(), err)
@ -792,7 +792,7 @@ func RepoRefByType(refType RepoRefType, ignoreNotExistErr ...bool) func(*Context
if ctx.Repo.GitRepo == nil { if ctx.Repo.GitRepo == nil {
repoPath := models.RepoPath(ctx.Repo.Owner.Name, ctx.Repo.Repository.Name) repoPath := models.RepoPath(ctx.Repo.Owner.Name, ctx.Repo.Repository.Name)
ctx.Repo.GitRepo, err = git.OpenRepository(repoPath) ctx.Repo.GitRepo, err = git.OpenRepositoryCtx(ctx, repoPath)
if err != nil { if err != nil {
ctx.ServerError("RepoRef Invalid repo "+repoPath, err) ctx.ServerError("RepoRef Invalid repo "+repoPath, err)
return return

View File

@ -28,17 +28,15 @@ type WriteCloserError interface {
} }
// CatFileBatchCheck opens git cat-file --batch-check in the provided repo and returns a stdin pipe, a stdout reader and cancel function // CatFileBatchCheck opens git cat-file --batch-check in the provided repo and returns a stdin pipe, a stdout reader and cancel function
func CatFileBatchCheck(repoPath string) (WriteCloserError, *bufio.Reader, func()) { func CatFileBatchCheck(ctx context.Context, repoPath string) (WriteCloserError, *bufio.Reader, func()) {
batchStdinReader, batchStdinWriter := io.Pipe() batchStdinReader, batchStdinWriter := io.Pipe()
batchStdoutReader, batchStdoutWriter := io.Pipe() batchStdoutReader, batchStdoutWriter := io.Pipe()
ctx, ctxCancel := context.WithCancel(DefaultContext) ctx, ctxCancel := context.WithCancel(ctx)
closed := make(chan struct{}) closed := make(chan struct{})
cancel := func() { cancel := func() {
_ = batchStdinReader.Close()
_ = batchStdinWriter.Close()
_ = batchStdoutReader.Close()
_ = batchStdoutWriter.Close()
ctxCancel() ctxCancel()
_ = batchStdoutReader.Close()
_ = batchStdinWriter.Close()
<-closed <-closed
} }
@ -67,19 +65,17 @@ func CatFileBatchCheck(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 // CatFileBatch opens git cat-file --batch in the provided repo and returns a stdin pipe, a stdout reader and cancel function
func CatFileBatch(repoPath string) (WriteCloserError, *bufio.Reader, func()) { 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. // 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 // so let's create a batch stdin and stdout
batchStdinReader, batchStdinWriter := io.Pipe() batchStdinReader, batchStdinWriter := io.Pipe()
batchStdoutReader, batchStdoutWriter := nio.Pipe(buffer.New(32 * 1024)) batchStdoutReader, batchStdoutWriter := nio.Pipe(buffer.New(32 * 1024))
ctx, ctxCancel := context.WithCancel(DefaultContext) ctx, ctxCancel := context.WithCancel(ctx)
closed := make(chan struct{}) closed := make(chan struct{})
cancel := func() { cancel := func() {
_ = batchStdinReader.Close() ctxCancel()
_ = batchStdinWriter.Close() _ = batchStdinWriter.Close()
_ = batchStdoutReader.Close() _ = batchStdoutReader.Close()
_ = batchStdoutWriter.Close()
ctxCancel()
<-closed <-closed
} }

View File

@ -25,11 +25,11 @@ type BlamePart struct {
// BlameReader returns part of file blame one by one // BlameReader returns part of file blame one by one
type BlameReader struct { type BlameReader struct {
cmd *exec.Cmd cmd *exec.Cmd
pid int64
output io.ReadCloser output io.ReadCloser
reader *bufio.Reader reader *bufio.Reader
lastSha *string lastSha *string
cancel context.CancelFunc cancel context.CancelFunc // Cancels the context that this reader runs in
finished process.FinishedFunc // Tells the process manager we're finished and it can remove the associated process from the process table
} }
var shaLineRegex = regexp.MustCompile("^([a-z0-9]{40})") var shaLineRegex = regexp.MustCompile("^([a-z0-9]{40})")
@ -100,8 +100,8 @@ func (r *BlameReader) NextPart() (*BlamePart, error) {
// Close BlameReader - don't run NextPart after invoking that // Close BlameReader - don't run NextPart after invoking that
func (r *BlameReader) Close() error { func (r *BlameReader) Close() error {
defer process.GetManager().Remove(r.pid) defer r.finished() // Only remove the process from the process table when the underlying command is closed
r.cancel() r.cancel() // However, first cancel our own context early
_ = r.output.Close() _ = r.output.Close()
@ -114,7 +114,7 @@ func (r *BlameReader) Close() error {
// CreateBlameReader creates reader for given repository, commit and file // CreateBlameReader creates reader for given repository, commit and file
func CreateBlameReader(ctx context.Context, repoPath, commitID, file string) (*BlameReader, error) { func CreateBlameReader(ctx context.Context, repoPath, commitID, file string) (*BlameReader, error) {
gitRepo, err := OpenRepository(repoPath) gitRepo, err := OpenRepositoryCtx(ctx, repoPath)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -125,32 +125,31 @@ func CreateBlameReader(ctx context.Context, repoPath, commitID, file string) (*B
func createBlameReader(ctx context.Context, dir string, command ...string) (*BlameReader, error) { func createBlameReader(ctx context.Context, dir string, command ...string) (*BlameReader, error) {
// Here we use the provided context - this should be tied to the request performing the blame so that it does not hang around. // Here we use the provided context - this should be tied to the request performing the blame so that it does not hang around.
ctx, cancel := context.WithCancel(ctx) ctx, cancel, finished := process.GetManager().AddContext(ctx, fmt.Sprintf("GetBlame [repo_path: %s]", dir))
cmd := exec.CommandContext(ctx, command[0], command[1:]...) cmd := exec.CommandContext(ctx, command[0], command[1:]...)
cmd.Dir = dir cmd.Dir = dir
cmd.Stderr = os.Stderr cmd.Stderr = os.Stderr
stdout, err := cmd.StdoutPipe() stdout, err := cmd.StdoutPipe()
if err != nil { if err != nil {
defer cancel() defer finished()
return nil, fmt.Errorf("StdoutPipe: %v", err) return nil, fmt.Errorf("StdoutPipe: %v", err)
} }
if err = cmd.Start(); err != nil { if err = cmd.Start(); err != nil {
defer cancel() defer finished()
_ = stdout.Close()
return nil, fmt.Errorf("Start: %v", err) return nil, fmt.Errorf("Start: %v", err)
} }
pid := process.GetManager().Add(fmt.Sprintf("GetBlame [repo_path: %s]", dir), cancel)
reader := bufio.NewReader(stdout) reader := bufio.NewReader(stdout)
return &BlameReader{ return &BlameReader{
cmd, cmd: cmd,
pid, output: stdout,
stdout, reader: reader,
reader, cancel: cancel,
nil, finished: finished,
cancel,
}, nil }, nil
} }

View File

@ -29,7 +29,7 @@ type Blob struct {
// DataAsync gets a ReadCloser for the contents of a blob without reading it all. // 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. // Calling the Close function on the result will discard all unread output.
func (b *Blob) DataAsync() (io.ReadCloser, error) { func (b *Blob) DataAsync() (io.ReadCloser, error) {
wr, rd, cancel := b.repo.CatFileBatch() wr, rd, cancel := b.repo.CatFileBatch(b.repo.Ctx)
_, err := wr.Write([]byte(b.ID.String() + "\n")) _, err := wr.Write([]byte(b.ID.String() + "\n"))
if err != nil { if err != nil {
@ -67,7 +67,7 @@ func (b *Blob) Size() int64 {
return b.size return b.size
} }
wr, rd, cancel := b.repo.CatFileBatchCheck() wr, rd, cancel := b.repo.CatFileBatchCheck(b.repo.Ctx)
defer cancel() defer cancel()
_, err := wr.Write([]byte(b.ID.String() + "\n")) _, err := wr.Write([]byte(b.ID.String() + "\n"))
if err != nil { if err != nil {

View File

@ -143,8 +143,13 @@ func (c *Command) RunWithContext(rc *RunContext) error {
log.Debug("%s: %v", rc.Dir, c) log.Debug("%s: %v", rc.Dir, c)
} }
ctx, cancel := context.WithTimeout(c.parentContext, rc.Timeout) desc := c.desc
defer cancel() if desc == "" {
desc = fmt.Sprintf("%s %s [repo_path: %s]", c.name, strings.Join(c.args, " "), rc.Dir)
}
ctx, cancel, finished := process.GetManager().AddContextTimeout(c.parentContext, rc.Timeout, desc)
defer finished()
cmd := exec.CommandContext(ctx, c.name, c.args...) cmd := exec.CommandContext(ctx, c.name, c.args...)
if rc.Env == nil { if rc.Env == nil {
@ -172,13 +177,6 @@ func (c *Command) RunWithContext(rc *RunContext) error {
return err return err
} }
desc := c.desc
if desc == "" {
desc = fmt.Sprintf("%s %s %s [repo_path: %s]", GitExecutable, c.name, strings.Join(c.args, " "), rc.Dir)
}
pid := process.GetManager().Add(desc, cancel)
defer process.GetManager().Remove(pid)
if rc.PipelineFunc != nil { if rc.PipelineFunc != nil {
err := rc.PipelineFunc(ctx, cancel) err := rc.PipelineFunc(ctx, cancel)
if err != nil { if err != nil {

View File

@ -100,7 +100,7 @@ func (tes Entries) GetCommitsInfo(ctx context.Context, commit *Commit, treePath
} }
func getLastCommitForPathsByCache(ctx context.Context, commitID, treePath string, paths []string, cache *LastCommitCache) (map[string]*Commit, []string, error) { func getLastCommitForPathsByCache(ctx context.Context, commitID, treePath string, paths []string, cache *LastCommitCache) (map[string]*Commit, []string, error) {
wr, rd, cancel := cache.repo.CatFileBatch() wr, rd, cancel := cache.repo.CatFileBatch(ctx)
defer cancel() defer cancel()
var unHitEntryPaths []string var unHitEntryPaths []string
@ -129,7 +129,7 @@ func GetLastCommitForPaths(ctx context.Context, cache *LastCommitCache, commit *
return nil, err return nil, err
} }
batchStdinWriter, batchReader, cancel := commit.repo.CatFileBatch() batchStdinWriter, batchReader, cancel := commit.repo.CatFileBatch(ctx)
defer cancel() defer cancel()
commitsMap := map[string]*Commit{} commitsMap := map[string]*Commit{}

View File

@ -56,8 +56,8 @@ func GetRepoRawDiffForFile(repo *Repository, startCommit, endCommit string, diff
fileArgs = append(fileArgs, "--", file) fileArgs = append(fileArgs, "--", file)
} }
// FIXME: graceful: These commands should have a timeout // FIXME: graceful: These commands should have a timeout
ctx, cancel := context.WithCancel(DefaultContext) ctx, _, finished := process.GetManager().AddContext(repo.Ctx, fmt.Sprintf("GetRawDiffForFile: [repo_path: %s]", repo.Path))
defer cancel() defer finished()
var cmd *exec.Cmd var cmd *exec.Cmd
switch diffType { switch diffType {
@ -90,8 +90,6 @@ func GetRepoRawDiffForFile(repo *Repository, startCommit, endCommit string, diff
cmd.Dir = repo.Path cmd.Dir = repo.Path
cmd.Stdout = writer cmd.Stdout = writer
cmd.Stderr = stderr cmd.Stderr = stderr
pid := process.GetManager().Add(fmt.Sprintf("GetRawDiffForFile: [repo_path: %s]", repo.Path), cancel)
defer process.GetManager().Remove(pid)
if err = cmd.Run(); err != nil { if err = cmd.Run(); err != nil {
return fmt.Errorf("Run: %v - %s", err, stderr) return fmt.Errorf("Run: %v - %s", err, stderr)

View File

@ -63,7 +63,7 @@ func FindLFSFile(repo *git.Repository, hash git.SHA1) ([]*LFSResult, error) {
// Next feed the commits in order into cat-file --batch, followed by their trees and sub trees as necessary. // 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 // so let's create a batch stdin and stdout
batchStdinWriter, batchReader, cancel := repo.CatFileBatch() batchStdinWriter, batchReader, cancel := repo.CatFileBatch(repo.Ctx)
defer cancel() defer cancel()
// We'll use a scanner for the revList because it's simpler than a bufio.Reader // We'll use a scanner for the revList because it's simpler than a bufio.Reader

View File

@ -4,19 +4,22 @@
package git package git
import "net/url" import (
"context"
"net/url"
)
// GetRemoteAddress returns the url of a specific remote of the repository. // GetRemoteAddress returns the url of a specific remote of the repository.
func GetRemoteAddress(repoPath, remoteName string) (*url.URL, error) { func GetRemoteAddress(ctx context.Context, repoPath, remoteName string) (*url.URL, error) {
err := LoadGitVersion() err := LoadGitVersion()
if err != nil { if err != nil {
return nil, err return nil, err
} }
var cmd *Command var cmd *Command
if CheckGitVersionAtLeast("2.7") == nil { if CheckGitVersionAtLeast("2.7") == nil {
cmd = NewCommand("remote", "get-url", remoteName) cmd = NewCommandContext(ctx, "remote", "get-url", remoteName)
} else { } else {
cmd = NewCommand("config", "--get", "remote."+remoteName+".url") cmd = NewCommandContext(ctx, "config", "--get", "remote."+remoteName+".url")
} }
result, err := cmd.RunInDir(repoPath) result, err := cmd.RunInDir(repoPath)

View File

@ -211,8 +211,8 @@ type PushOptions struct {
} }
// Push pushs local commits to given remote branch. // Push pushs local commits to given remote branch.
func Push(repoPath string, opts PushOptions) error { func Push(ctx context.Context, repoPath string, opts PushOptions) error {
cmd := NewCommand("push") cmd := NewCommandContext(ctx, "push")
if opts.Force { if opts.Force {
cmd.AddArguments("-f") cmd.AddArguments("-f")
} }

View File

@ -74,7 +74,7 @@ func (repo *Repository) CheckAttribute(opts CheckAttributeOpts) (map[string]map[
} }
} }
cmd := NewCommand(cmdArgs...) cmd := NewCommandContext(repo.Ctx, cmdArgs...)
if err := cmd.RunInDirTimeoutEnvPipeline(env, -1, repo.Path, stdOut, stdErr); err != nil { if err := cmd.RunInDirTimeoutEnvPipeline(env, -1, repo.Path, stdOut, stdErr); err != nil {
return nil, fmt.Errorf("failed to run check-attr: %v\n%s\n%s", err, stdOut.String(), stdErr.String()) return nil, fmt.Errorf("failed to run check-attr: %v\n%s\n%s", err, stdOut.String(), stdErr.String())

View File

@ -9,6 +9,7 @@
package git package git
import ( import (
"context"
"errors" "errors"
"path/filepath" "path/filepath"
@ -30,10 +31,17 @@ type Repository struct {
gogitRepo *gogit.Repository gogitRepo *gogit.Repository
gogitStorage *filesystem.Storage gogitStorage *filesystem.Storage
gpgSettings *GPGSettings gpgSettings *GPGSettings
Ctx context.Context
} }
// OpenRepository opens the repository at the given path. // OpenRepository opens the repository at the given path.
func OpenRepository(repoPath string) (*Repository, error) { func OpenRepository(repoPath string) (*Repository, error) {
return OpenRepositoryCtx(DefaultContext, repoPath)
}
// OpenRepositoryCtx opens the repository at the given path within the context.Context
func OpenRepositoryCtx(ctx context.Context, repoPath string) (*Repository, error) {
repoPath, err := filepath.Abs(repoPath) repoPath, err := filepath.Abs(repoPath)
if err != nil { if err != nil {
return nil, err return nil, err
@ -60,6 +68,7 @@ func OpenRepository(repoPath string) (*Repository, error) {
gogitRepo: gogitRepo, gogitRepo: gogitRepo,
gogitStorage: storage, gogitStorage: storage,
tagCache: newObjectCache(), tagCache: newObjectCache(),
Ctx: ctx,
}, nil }, nil
} }

View File

@ -32,10 +32,17 @@ type Repository struct {
checkCancel context.CancelFunc checkCancel context.CancelFunc
checkReader *bufio.Reader checkReader *bufio.Reader
checkWriter WriteCloserError checkWriter WriteCloserError
Ctx context.Context
} }
// OpenRepository opens the repository at the given path. // OpenRepository opens the repository at the given path.
func OpenRepository(repoPath string) (*Repository, error) { func OpenRepository(repoPath string) (*Repository, error) {
return OpenRepositoryCtx(DefaultContext, repoPath)
}
// OpenRepositoryCtx opens the repository at the given path with the provided context.
func OpenRepositoryCtx(ctx context.Context, repoPath string) (*Repository, error) {
repoPath, err := filepath.Abs(repoPath) repoPath, err := filepath.Abs(repoPath)
if err != nil { if err != nil {
return nil, err return nil, err
@ -46,28 +53,29 @@ func OpenRepository(repoPath string) (*Repository, error) {
repo := &Repository{ repo := &Repository{
Path: repoPath, Path: repoPath,
tagCache: newObjectCache(), tagCache: newObjectCache(),
Ctx: ctx,
} }
repo.batchWriter, repo.batchReader, repo.batchCancel = CatFileBatch(repoPath) repo.batchWriter, repo.batchReader, repo.batchCancel = CatFileBatch(ctx, repoPath)
repo.checkWriter, repo.checkReader, repo.checkCancel = CatFileBatchCheck(repo.Path) repo.checkWriter, repo.checkReader, repo.checkCancel = CatFileBatchCheck(ctx, repo.Path)
return repo, nil return repo, nil
} }
// CatFileBatch obtains a CatFileBatch for this repository // CatFileBatch obtains a CatFileBatch for this repository
func (repo *Repository) CatFileBatch() (WriteCloserError, *bufio.Reader, func()) { func (repo *Repository) CatFileBatch(ctx context.Context) (WriteCloserError, *bufio.Reader, func()) {
if repo.batchCancel == nil || repo.batchReader.Buffered() > 0 { if repo.batchCancel == nil || repo.batchReader.Buffered() > 0 {
log.Debug("Opening temporary cat file batch for: %s", repo.Path) log.Debug("Opening temporary cat file batch for: %s", repo.Path)
return CatFileBatch(repo.Path) return CatFileBatch(ctx, repo.Path)
} }
return repo.batchWriter, repo.batchReader, func() {} return repo.batchWriter, repo.batchReader, func() {}
} }
// CatFileBatchCheck obtains a CatFileBatchCheck for this repository // CatFileBatchCheck obtains a CatFileBatchCheck for this repository
func (repo *Repository) CatFileBatchCheck() (WriteCloserError, *bufio.Reader, func()) { func (repo *Repository) CatFileBatchCheck(ctx context.Context) (WriteCloserError, *bufio.Reader, func()) {
if repo.checkCancel == nil || repo.checkReader.Buffered() > 0 { if repo.checkCancel == nil || repo.checkReader.Buffered() > 0 {
log.Debug("Opening temporary cat file batch-check: %s", repo.Path) log.Debug("Opening temporary cat file batch-check: %s", repo.Path)
return CatFileBatchCheck(repo.Path) return CatFileBatchCheck(ctx, repo.Path)
} }
return repo.checkWriter, repo.checkReader, func() {} return repo.checkWriter, repo.checkReader, func() {}
} }

View File

@ -8,12 +8,12 @@ import "fmt"
// FileBlame return the Blame object of file // FileBlame return the Blame object of file
func (repo *Repository) FileBlame(revision, path, file string) ([]byte, error) { func (repo *Repository) FileBlame(revision, path, file string) ([]byte, error) {
return NewCommand("blame", "--root", "--", file).RunInDirBytes(path) return NewCommandContext(repo.Ctx, "blame", "--root", "--", file).RunInDirBytes(path)
} }
// LineBlame returns the latest commit at the given line // LineBlame returns the latest commit at the given line
func (repo *Repository) LineBlame(revision, path, file string, line uint) (*Commit, error) { func (repo *Repository) LineBlame(revision, path, file string, line uint) (*Commit, error) {
res, err := NewCommand("blame", fmt.Sprintf("-L %d,%d", line, line), "-p", revision, "--", file).RunInDir(path) res, err := NewCommandContext(repo.Ctx, "blame", fmt.Sprintf("-L %d,%d", line, line), "-p", revision, "--", file).RunInDir(path)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -6,6 +6,7 @@
package git package git
import ( import (
"context"
"fmt" "fmt"
"strings" "strings"
) )
@ -22,14 +23,14 @@ const PullRequestPrefix = "refs/for/"
// TODO: /refs/for-review for suggest change interface // TODO: /refs/for-review for suggest change interface
// IsReferenceExist returns true if given reference exists in the repository. // IsReferenceExist returns true if given reference exists in the repository.
func IsReferenceExist(repoPath, name string) bool { func IsReferenceExist(ctx context.Context, repoPath, name string) bool {
_, err := NewCommand("show-ref", "--verify", "--", name).RunInDir(repoPath) _, err := NewCommandContext(ctx, "show-ref", "--verify", "--", name).RunInDir(repoPath)
return err == nil return err == nil
} }
// IsBranchExist returns true if given branch exists in the repository. // IsBranchExist returns true if given branch exists in the repository.
func IsBranchExist(repoPath, name string) bool { func IsBranchExist(ctx context.Context, repoPath, name string) bool {
return IsReferenceExist(repoPath, BranchPrefix+name) return IsReferenceExist(ctx, repoPath, BranchPrefix+name)
} }
// Branch represents a Git branch. // Branch represents a Git branch.
@ -45,7 +46,7 @@ func (repo *Repository) GetHEADBranch() (*Branch, error) {
if repo == nil { if repo == nil {
return nil, fmt.Errorf("nil repo") return nil, fmt.Errorf("nil repo")
} }
stdout, err := NewCommand("symbolic-ref", "HEAD").RunInDir(repo.Path) stdout, err := NewCommandContext(repo.Ctx, "symbolic-ref", "HEAD").RunInDir(repo.Path)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -64,13 +65,13 @@ func (repo *Repository) GetHEADBranch() (*Branch, error) {
// SetDefaultBranch sets default branch of repository. // SetDefaultBranch sets default branch of repository.
func (repo *Repository) SetDefaultBranch(name string) error { func (repo *Repository) SetDefaultBranch(name string) error {
_, err := NewCommand("symbolic-ref", "HEAD", BranchPrefix+name).RunInDir(repo.Path) _, err := NewCommandContext(repo.Ctx, "symbolic-ref", "HEAD", BranchPrefix+name).RunInDir(repo.Path)
return err return err
} }
// GetDefaultBranch gets default branch of repository. // GetDefaultBranch gets default branch of repository.
func (repo *Repository) GetDefaultBranch() (string, error) { func (repo *Repository) GetDefaultBranch() (string, error) {
return NewCommand("symbolic-ref", "HEAD").RunInDir(repo.Path) return NewCommandContext(repo.Ctx, "symbolic-ref", "HEAD").RunInDir(repo.Path)
} }
// GetBranch returns a branch by it's name // GetBranch returns a branch by it's name
@ -118,7 +119,7 @@ type DeleteBranchOptions struct {
// DeleteBranch delete a branch by name on repository. // DeleteBranch delete a branch by name on repository.
func (repo *Repository) DeleteBranch(name string, opts DeleteBranchOptions) error { func (repo *Repository) DeleteBranch(name string, opts DeleteBranchOptions) error {
cmd := NewCommand("branch") cmd := NewCommandContext(repo.Ctx, "branch")
if opts.Force { if opts.Force {
cmd.AddArguments("-D") cmd.AddArguments("-D")
@ -134,7 +135,7 @@ func (repo *Repository) DeleteBranch(name string, opts DeleteBranchOptions) erro
// CreateBranch create a new branch // CreateBranch create a new branch
func (repo *Repository) CreateBranch(branch, oldbranchOrCommit string) error { func (repo *Repository) CreateBranch(branch, oldbranchOrCommit string) error {
cmd := NewCommand("branch") cmd := NewCommandContext(repo.Ctx, "branch")
cmd.AddArguments("--", branch, oldbranchOrCommit) cmd.AddArguments("--", branch, oldbranchOrCommit)
_, err := cmd.RunInDir(repo.Path) _, err := cmd.RunInDir(repo.Path)
@ -144,7 +145,7 @@ func (repo *Repository) CreateBranch(branch, oldbranchOrCommit string) error {
// AddRemote adds a new remote to repository. // AddRemote adds a new remote to repository.
func (repo *Repository) AddRemote(name, url string, fetch bool) error { func (repo *Repository) AddRemote(name, url string, fetch bool) error {
cmd := NewCommand("remote", "add") cmd := NewCommandContext(repo.Ctx, "remote", "add")
if fetch { if fetch {
cmd.AddArguments("-f") cmd.AddArguments("-f")
} }
@ -156,7 +157,7 @@ func (repo *Repository) AddRemote(name, url string, fetch bool) error {
// RemoveRemote removes a remote from repository. // RemoveRemote removes a remote from repository.
func (repo *Repository) RemoveRemote(name string) error { func (repo *Repository) RemoveRemote(name string) error {
_, err := NewCommand("remote", "rm", name).RunInDir(repo.Path) _, err := NewCommandContext(repo.Ctx, "remote", "rm", name).RunInDir(repo.Path)
return err return err
} }
@ -167,6 +168,6 @@ func (branch *Branch) GetCommit() (*Commit, error) {
// RenameBranch rename a branch // RenameBranch rename a branch
func (repo *Repository) RenameBranch(from, to string) error { func (repo *Repository) RenameBranch(from, to string) error {
_, err := NewCommand("branch", "-m", from, to).RunInDir(repo.Path) _, err := NewCommandContext(repo.Ctx, "branch", "-m", from, to).RunInDir(repo.Path)
return err return err
} }

View File

@ -11,6 +11,7 @@ package git
import ( import (
"bufio" "bufio"
"bytes" "bytes"
"context"
"io" "io"
"strings" "strings"
@ -23,7 +24,7 @@ func (repo *Repository) IsObjectExist(name string) bool {
return false return false
} }
wr, rd, cancel := repo.CatFileBatchCheck() wr, rd, cancel := repo.CatFileBatchCheck(repo.Ctx)
defer cancel() defer cancel()
_, err := wr.Write([]byte(name + "\n")) _, err := wr.Write([]byte(name + "\n"))
if err != nil { if err != nil {
@ -40,7 +41,7 @@ func (repo *Repository) IsReferenceExist(name string) bool {
return false return false
} }
wr, rd, cancel := repo.CatFileBatchCheck() wr, rd, cancel := repo.CatFileBatchCheck(repo.Ctx)
defer cancel() defer cancel()
_, err := wr.Write([]byte(name + "\n")) _, err := wr.Write([]byte(name + "\n"))
if err != nil { if err != nil {
@ -63,11 +64,11 @@ func (repo *Repository) IsBranchExist(name string) bool {
// GetBranches returns branches from the repository, skipping skip initial branches and // GetBranches returns branches from the repository, skipping skip initial branches and
// returning at most limit branches, or all branches if limit is 0. // returning at most limit branches, or all branches if limit is 0.
func (repo *Repository) GetBranches(skip, limit int) ([]string, int, error) { func (repo *Repository) GetBranches(skip, limit int) ([]string, int, error) {
return callShowRef(repo.Path, BranchPrefix, "--heads", skip, limit) return callShowRef(repo.Ctx, repo.Path, BranchPrefix, "--heads", skip, limit)
} }
// callShowRef return refs, if limit = 0 it will not limit // callShowRef return refs, if limit = 0 it will not limit
func callShowRef(repoPath, prefix, arg string, skip, limit int) (branchNames []string, countAll int, err error) { func callShowRef(ctx context.Context, repoPath, prefix, arg string, skip, limit int) (branchNames []string, countAll int, err error) {
stdoutReader, stdoutWriter := io.Pipe() stdoutReader, stdoutWriter := io.Pipe()
defer func() { defer func() {
_ = stdoutReader.Close() _ = stdoutReader.Close()
@ -76,7 +77,7 @@ func callShowRef(repoPath, prefix, arg string, skip, limit int) (branchNames []s
go func() { go func() {
stderrBuilder := &strings.Builder{} stderrBuilder := &strings.Builder{}
err := NewCommand("show-ref", arg).RunInDirPipeline(repoPath, stdoutWriter, stderrBuilder) err := NewCommandContext(ctx, "show-ref", arg).RunInDirPipeline(repoPath, stdoutWriter, stderrBuilder)
if err != nil { if err != nil {
if stderrBuilder.Len() == 0 { if stderrBuilder.Len() == 0 {
_ = stdoutWriter.Close() _ = stdoutWriter.Close()

View File

@ -58,7 +58,7 @@ func (repo *Repository) getCommitByPathWithID(id SHA1, relpath string) (*Commit,
relpath = `\` + relpath relpath = `\` + relpath
} }
stdout, err := NewCommand("log", "-1", prettyLogFormat, id.String(), "--", relpath).RunInDir(repo.Path) stdout, err := NewCommandContext(repo.Ctx, "log", "-1", prettyLogFormat, id.String(), "--", relpath).RunInDir(repo.Path)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -73,7 +73,7 @@ func (repo *Repository) getCommitByPathWithID(id SHA1, relpath string) (*Commit,
// GetCommitByPath returns the last commit of relative path. // GetCommitByPath returns the last commit of relative path.
func (repo *Repository) GetCommitByPath(relpath string) (*Commit, error) { func (repo *Repository) GetCommitByPath(relpath string) (*Commit, error) {
stdout, err := NewCommand("log", "-1", prettyLogFormat, "--", relpath).RunInDirBytes(repo.Path) stdout, err := NewCommandContext(repo.Ctx, "log", "-1", prettyLogFormat, "--", relpath).RunInDirBytes(repo.Path)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -86,7 +86,7 @@ func (repo *Repository) GetCommitByPath(relpath string) (*Commit, error) {
} }
func (repo *Repository) commitsByRange(id SHA1, page, pageSize int) ([]*Commit, error) { func (repo *Repository) commitsByRange(id SHA1, page, pageSize int) ([]*Commit, error) {
stdout, err := NewCommand("log", id.String(), "--skip="+strconv.Itoa((page-1)*pageSize), stdout, err := NewCommandContext(repo.Ctx, "log", id.String(), "--skip="+strconv.Itoa((page-1)*pageSize),
"--max-count="+strconv.Itoa(pageSize), prettyLogFormat).RunInDirBytes(repo.Path) "--max-count="+strconv.Itoa(pageSize), prettyLogFormat).RunInDirBytes(repo.Path)
if err != nil { if err != nil {
@ -97,7 +97,7 @@ func (repo *Repository) commitsByRange(id SHA1, page, pageSize int) ([]*Commit,
func (repo *Repository) searchCommits(id SHA1, opts SearchCommitsOptions) ([]*Commit, error) { func (repo *Repository) searchCommits(id SHA1, opts SearchCommitsOptions) ([]*Commit, error) {
// create new git log command with limit of 100 commis // create new git log command with limit of 100 commis
cmd := NewCommand("log", id.String(), "-100", prettyLogFormat) cmd := NewCommandContext(repo.Ctx, "log", id.String(), "-100", prettyLogFormat)
// ignore case // ignore case
args := []string{"-i"} args := []string{"-i"}
@ -155,7 +155,7 @@ func (repo *Repository) searchCommits(id SHA1, opts SearchCommitsOptions) ([]*Co
// ignore anything below 4 characters as too unspecific // ignore anything below 4 characters as too unspecific
if len(v) >= 4 { if len(v) >= 4 {
// create new git log command with 1 commit limit // create new git log command with 1 commit limit
hashCmd := NewCommand("log", "-1", prettyLogFormat) hashCmd := NewCommandContext(repo.Ctx, "log", "-1", prettyLogFormat)
// add previous arguments except for --grep and --all // add previous arguments except for --grep and --all
hashCmd.AddArguments(args...) hashCmd.AddArguments(args...)
// add keyword as <commit> // add keyword as <commit>
@ -176,7 +176,7 @@ func (repo *Repository) searchCommits(id SHA1, opts SearchCommitsOptions) ([]*Co
} }
func (repo *Repository) getFilesChanged(id1, id2 string) ([]string, error) { func (repo *Repository) getFilesChanged(id1, id2 string) ([]string, error) {
stdout, err := NewCommand("diff", "--name-only", id1, id2).RunInDirBytes(repo.Path) stdout, err := NewCommandContext(repo.Ctx, "diff", "--name-only", id1, id2).RunInDirBytes(repo.Path)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -186,7 +186,7 @@ func (repo *Repository) getFilesChanged(id1, id2 string) ([]string, error) {
// FileChangedBetweenCommits Returns true if the file changed between commit IDs id1 and id2 // FileChangedBetweenCommits Returns true if the file changed between commit IDs id1 and id2
// You must ensure that id1 and id2 are valid commit ids. // You must ensure that id1 and id2 are valid commit ids.
func (repo *Repository) FileChangedBetweenCommits(filename, id1, id2 string) (bool, error) { func (repo *Repository) FileChangedBetweenCommits(filename, id1, id2 string) (bool, error) {
stdout, err := NewCommand("diff", "--name-only", "-z", id1, id2, "--", filename).RunInDirBytes(repo.Path) stdout, err := NewCommandContext(repo.Ctx, "diff", "--name-only", "-z", id1, id2, "--", filename).RunInDirBytes(repo.Path)
if err != nil { if err != nil {
return false, err return false, err
} }
@ -209,7 +209,7 @@ func (repo *Repository) CommitsByFileAndRange(revision, file string, page int) (
}() }()
go func() { go func() {
stderr := strings.Builder{} stderr := strings.Builder{}
err := NewCommand("log", revision, "--follow", err := NewCommandContext(repo.Ctx, "log", revision, "--follow",
"--max-count="+strconv.Itoa(setting.Git.CommitsRangeSize*page), "--max-count="+strconv.Itoa(setting.Git.CommitsRangeSize*page),
prettyLogFormat, "--", file). prettyLogFormat, "--", file).
RunInDirPipeline(repo.Path, stdoutWriter, &stderr) RunInDirPipeline(repo.Path, stdoutWriter, &stderr)
@ -240,7 +240,7 @@ func (repo *Repository) CommitsByFileAndRange(revision, file string, page int) (
// CommitsByFileAndRangeNoFollow return the commits according revision file and the page // CommitsByFileAndRangeNoFollow return the commits according revision file and the page
func (repo *Repository) CommitsByFileAndRangeNoFollow(revision, file string, page int) ([]*Commit, error) { func (repo *Repository) CommitsByFileAndRangeNoFollow(revision, file string, page int) ([]*Commit, error) {
stdout, err := NewCommand("log", revision, "--skip="+strconv.Itoa((page-1)*50), stdout, err := NewCommandContext(repo.Ctx, "log", revision, "--skip="+strconv.Itoa((page-1)*50),
"--max-count="+strconv.Itoa(setting.Git.CommitsRangeSize), prettyLogFormat, "--", file).RunInDirBytes(repo.Path) "--max-count="+strconv.Itoa(setting.Git.CommitsRangeSize), prettyLogFormat, "--", file).RunInDirBytes(repo.Path)
if err != nil { if err != nil {
return nil, err return nil, err
@ -250,11 +250,11 @@ func (repo *Repository) CommitsByFileAndRangeNoFollow(revision, file string, pag
// FilesCountBetween return the number of files changed between two commits // FilesCountBetween return the number of files changed between two commits
func (repo *Repository) FilesCountBetween(startCommitID, endCommitID string) (int, error) { func (repo *Repository) FilesCountBetween(startCommitID, endCommitID string) (int, error) {
stdout, err := NewCommand("diff", "--name-only", startCommitID+"..."+endCommitID).RunInDir(repo.Path) stdout, err := NewCommandContext(repo.Ctx, "diff", "--name-only", startCommitID+"..."+endCommitID).RunInDir(repo.Path)
if err != nil && strings.Contains(err.Error(), "no merge base") { if err != nil && strings.Contains(err.Error(), "no merge base") {
// git >= 2.28 now returns an error if startCommitID and endCommitID have become unrelated. // git >= 2.28 now returns an error if startCommitID and endCommitID have become unrelated.
// previously it would return the results of git diff --name-only startCommitID endCommitID so let's try that... // previously it would return the results of git diff --name-only startCommitID endCommitID so let's try that...
stdout, err = NewCommand("diff", "--name-only", startCommitID, endCommitID).RunInDir(repo.Path) stdout, err = NewCommandContext(repo.Ctx, "diff", "--name-only", startCommitID, endCommitID).RunInDir(repo.Path)
} }
if err != nil { if err != nil {
return 0, err return 0, err
@ -268,13 +268,13 @@ func (repo *Repository) CommitsBetween(last *Commit, before *Commit) ([]*Commit,
var stdout []byte var stdout []byte
var err error var err error
if before == nil { if before == nil {
stdout, err = NewCommand("rev-list", last.ID.String()).RunInDirBytes(repo.Path) stdout, err = NewCommandContext(repo.Ctx, "rev-list", last.ID.String()).RunInDirBytes(repo.Path)
} else { } else {
stdout, err = NewCommand("rev-list", before.ID.String()+".."+last.ID.String()).RunInDirBytes(repo.Path) stdout, err = NewCommandContext(repo.Ctx, "rev-list", before.ID.String()+".."+last.ID.String()).RunInDirBytes(repo.Path)
if err != nil && strings.Contains(err.Error(), "no merge base") { if err != nil && strings.Contains(err.Error(), "no merge base") {
// future versions of git >= 2.28 are likely to return an error if before and last have become unrelated. // future versions of git >= 2.28 are likely to return an error if before and last have become unrelated.
// previously it would return the results of git rev-list before last so let's try that... // previously it would return the results of git rev-list before last so let's try that...
stdout, err = NewCommand("rev-list", before.ID.String(), last.ID.String()).RunInDirBytes(repo.Path) stdout, err = NewCommandContext(repo.Ctx, "rev-list", before.ID.String(), last.ID.String()).RunInDirBytes(repo.Path)
} }
} }
if err != nil { if err != nil {
@ -288,13 +288,13 @@ func (repo *Repository) CommitsBetweenLimit(last *Commit, before *Commit, limit,
var stdout []byte var stdout []byte
var err error var err error
if before == nil { if before == nil {
stdout, err = NewCommand("rev-list", "--max-count", strconv.Itoa(limit), "--skip", strconv.Itoa(skip), last.ID.String()).RunInDirBytes(repo.Path) stdout, err = NewCommandContext(repo.Ctx, "rev-list", "--max-count", strconv.Itoa(limit), "--skip", strconv.Itoa(skip), last.ID.String()).RunInDirBytes(repo.Path)
} else { } else {
stdout, err = NewCommand("rev-list", "--max-count", strconv.Itoa(limit), "--skip", strconv.Itoa(skip), before.ID.String()+".."+last.ID.String()).RunInDirBytes(repo.Path) stdout, err = NewCommandContext(repo.Ctx, "rev-list", "--max-count", strconv.Itoa(limit), "--skip", strconv.Itoa(skip), before.ID.String()+".."+last.ID.String()).RunInDirBytes(repo.Path)
if err != nil && strings.Contains(err.Error(), "no merge base") { if err != nil && strings.Contains(err.Error(), "no merge base") {
// future versions of git >= 2.28 are likely to return an error if before and last have become unrelated. // future versions of git >= 2.28 are likely to return an error if before and last have become unrelated.
// previously it would return the results of git rev-list --max-count n before last so let's try that... // previously it would return the results of git rev-list --max-count n before last so let's try that...
stdout, err = NewCommand("rev-list", "--max-count", strconv.Itoa(limit), "--skip", strconv.Itoa(skip), before.ID.String(), last.ID.String()).RunInDirBytes(repo.Path) stdout, err = NewCommandContext(repo.Ctx, "rev-list", "--max-count", strconv.Itoa(limit), "--skip", strconv.Itoa(skip), before.ID.String(), last.ID.String()).RunInDirBytes(repo.Path)
} }
} }
if err != nil { if err != nil {
@ -333,7 +333,7 @@ func (repo *Repository) CommitsCountBetween(start, end string) (int64, error) {
// commitsBefore the limit is depth, not total number of returned commits. // commitsBefore the limit is depth, not total number of returned commits.
func (repo *Repository) commitsBefore(id SHA1, limit int) ([]*Commit, error) { func (repo *Repository) commitsBefore(id SHA1, limit int) ([]*Commit, error) {
cmd := NewCommand("log") cmd := NewCommandContext(repo.Ctx, "log")
if limit > 0 { if limit > 0 {
cmd.AddArguments("-"+strconv.Itoa(limit), prettyLogFormat, id.String()) cmd.AddArguments("-"+strconv.Itoa(limit), prettyLogFormat, id.String())
} else { } else {
@ -377,7 +377,7 @@ func (repo *Repository) getCommitsBeforeLimit(id SHA1, num int) ([]*Commit, erro
func (repo *Repository) getBranches(commit *Commit, limit int) ([]string, error) { func (repo *Repository) getBranches(commit *Commit, limit int) ([]string, error) {
if CheckGitVersionAtLeast("2.7.0") == nil { if CheckGitVersionAtLeast("2.7.0") == nil {
stdout, err := NewCommand("for-each-ref", "--count="+strconv.Itoa(limit), "--format=%(refname:strip=2)", "--contains", commit.ID.String(), BranchPrefix).RunInDir(repo.Path) stdout, err := NewCommandContext(repo.Ctx, "for-each-ref", "--count="+strconv.Itoa(limit), "--format=%(refname:strip=2)", "--contains", commit.ID.String(), BranchPrefix).RunInDir(repo.Path)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -386,7 +386,7 @@ func (repo *Repository) getBranches(commit *Commit, limit int) ([]string, error)
return branches, nil return branches, nil
} }
stdout, err := NewCommand("branch", "--contains", commit.ID.String()).RunInDir(repo.Path) stdout, err := NewCommandContext(repo.Ctx, "branch", "--contains", commit.ID.String()).RunInDir(repo.Path)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -425,7 +425,7 @@ func (repo *Repository) GetCommitsFromIDs(commitIDs []string) []*Commit {
// IsCommitInBranch check if the commit is on the branch // IsCommitInBranch check if the commit is on the branch
func (repo *Repository) IsCommitInBranch(commitID, branch string) (r bool, err error) { func (repo *Repository) IsCommitInBranch(commitID, branch string) (r bool, err error) {
stdout, err := NewCommand("branch", "--contains", commitID, branch).RunInDir(repo.Path) stdout, err := NewCommandContext(repo.Ctx, "branch", "--contains", commitID, branch).RunInDir(repo.Path)
if err != nil { if err != nil {
return false, err return false, err
} }

View File

@ -40,7 +40,7 @@ func (repo *Repository) ConvertToSHA1(commitID string) (SHA1, error) {
} }
} }
actualCommitID, err := NewCommand("rev-parse", "--verify", commitID).RunInDir(repo.Path) actualCommitID, err := NewCommandContext(repo.Ctx, "rev-parse", "--verify", commitID).RunInDir(repo.Path)
if err != nil { if err != nil {
if strings.Contains(err.Error(), "unknown revision or path") || if strings.Contains(err.Error(), "unknown revision or path") ||
strings.Contains(err.Error(), "fatal: Needed a single revision") { strings.Contains(err.Error(), "fatal: Needed a single revision") {

View File

@ -18,7 +18,7 @@ import (
// ResolveReference resolves a name to a reference // ResolveReference resolves a name to a reference
func (repo *Repository) ResolveReference(name string) (string, error) { func (repo *Repository) ResolveReference(name string) (string, error) {
stdout, err := NewCommand("show-ref", "--hash", name).RunInDir(repo.Path) stdout, err := NewCommandContext(repo.Ctx, "show-ref", "--hash", name).RunInDir(repo.Path)
if err != nil { if err != nil {
if strings.Contains(err.Error(), "not a valid ref") { if strings.Contains(err.Error(), "not a valid ref") {
return "", ErrNotExist{name, ""} return "", ErrNotExist{name, ""}
@ -35,7 +35,7 @@ func (repo *Repository) ResolveReference(name string) (string, error) {
// GetRefCommitID returns the last commit ID string of given reference (branch or tag). // GetRefCommitID returns the last commit ID string of given reference (branch or tag).
func (repo *Repository) GetRefCommitID(name string) (string, error) { func (repo *Repository) GetRefCommitID(name string) (string, error) {
wr, rd, cancel := repo.CatFileBatchCheck() wr, rd, cancel := repo.CatFileBatchCheck(repo.Ctx)
defer cancel() defer cancel()
_, _ = wr.Write([]byte(name + "\n")) _, _ = wr.Write([]byte(name + "\n"))
shaBs, _, _, err := ReadBatchLine(rd) shaBs, _, _, err := ReadBatchLine(rd)
@ -48,12 +48,12 @@ func (repo *Repository) GetRefCommitID(name string) (string, error) {
// IsCommitExist returns true if given commit exists in current repository. // IsCommitExist returns true if given commit exists in current repository.
func (repo *Repository) IsCommitExist(name string) bool { func (repo *Repository) IsCommitExist(name string) bool {
_, err := NewCommand("cat-file", "-e", name).RunInDir(repo.Path) _, err := NewCommandContext(repo.Ctx, "cat-file", "-e", name).RunInDir(repo.Path)
return err == nil return err == nil
} }
func (repo *Repository) getCommit(id SHA1) (*Commit, error) { func (repo *Repository) getCommit(id SHA1) (*Commit, error) {
wr, rd, cancel := repo.CatFileBatch() wr, rd, cancel := repo.CatFileBatch(repo.Ctx)
defer cancel() defer cancel()
_, _ = wr.Write([]byte(id.String() + "\n")) _, _ = wr.Write([]byte(id.String() + "\n"))
@ -132,7 +132,7 @@ func (repo *Repository) ConvertToSHA1(commitID string) (SHA1, error) {
} }
} }
wr, rd, cancel := repo.CatFileBatchCheck() wr, rd, cancel := repo.CatFileBatchCheck(repo.Ctx)
defer cancel() defer cancel()
_, err := wr.Write([]byte(commitID + "\n")) _, err := wr.Write([]byte(commitID + "\n"))
if err != nil { if err != nil {

View File

@ -35,13 +35,13 @@ func (repo *Repository) GetMergeBase(tmpRemote string, base, head string) (strin
if tmpRemote != "origin" { if tmpRemote != "origin" {
tmpBaseName := "refs/remotes/" + tmpRemote + "/tmp_" + base tmpBaseName := "refs/remotes/" + tmpRemote + "/tmp_" + base
// Fetch commit into a temporary branch in order to be able to handle commits and tags // Fetch commit into a temporary branch in order to be able to handle commits and tags
_, err := NewCommand("fetch", tmpRemote, base+":"+tmpBaseName).RunInDir(repo.Path) _, err := NewCommandContext(repo.Ctx, "fetch", tmpRemote, base+":"+tmpBaseName).RunInDir(repo.Path)
if err == nil { if err == nil {
base = tmpBaseName base = tmpBaseName
} }
} }
stdout, err := NewCommand("merge-base", "--", base, head).RunInDir(repo.Path) stdout, err := NewCommandContext(repo.Ctx, "merge-base", "--", base, head).RunInDir(repo.Path)
return strings.TrimSpace(stdout), base, err return strings.TrimSpace(stdout), base, err
} }
@ -88,7 +88,7 @@ func (repo *Repository) GetCompareInfo(basePath, baseBranch, headBranch string,
// We have a common base - therefore we know that ... should work // We have a common base - therefore we know that ... should work
if !fileOnly { if !fileOnly {
logs, err := NewCommand("log", baseCommitID+separator+headBranch, prettyLogFormat).RunInDirBytes(repo.Path) logs, err := NewCommandContext(repo.Ctx, "log", baseCommitID+separator+headBranch, prettyLogFormat).RunInDirBytes(repo.Path)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -141,14 +141,14 @@ func (repo *Repository) GetDiffNumChangedFiles(base, head string, directComparis
separator = ".." separator = ".."
} }
if err := NewCommand("diff", "-z", "--name-only", base+separator+head). if err := NewCommandContext(repo.Ctx, "diff", "-z", "--name-only", base+separator+head).
RunInDirPipeline(repo.Path, w, stderr); err != nil { RunInDirPipeline(repo.Path, w, stderr); err != nil {
if strings.Contains(stderr.String(), "no merge base") { if strings.Contains(stderr.String(), "no merge base") {
// git >= 2.28 now returns an error if base and head have become unrelated. // git >= 2.28 now returns an error if base and head have become unrelated.
// previously it would return the results of git diff -z --name-only base head so let's try that... // previously it would return the results of git diff -z --name-only base head so let's try that...
w = &lineCountWriter{} w = &lineCountWriter{}
stderr.Reset() stderr.Reset()
if err = NewCommand("diff", "-z", "--name-only", base, head).RunInDirPipeline(repo.Path, w, stderr); err == nil { if err = NewCommandContext(repo.Ctx, "diff", "-z", "--name-only", base, head).RunInDirPipeline(repo.Path, w, stderr); err == nil {
return w.numLines, nil return w.numLines, nil
} }
} }
@ -231,23 +231,23 @@ func (repo *Repository) GetDiffOrPatch(base, head string, w io.Writer, patch, bi
// GetDiff generates and returns patch data between given revisions, optimized for human readability // GetDiff generates and returns patch data between given revisions, optimized for human readability
func (repo *Repository) GetDiff(base, head string, w io.Writer) error { func (repo *Repository) GetDiff(base, head string, w io.Writer) error {
return NewCommand("diff", "-p", base, head). return NewCommandContext(repo.Ctx, "diff", "-p", base, head).
RunInDirPipeline(repo.Path, w, nil) RunInDirPipeline(repo.Path, w, nil)
} }
// GetDiffBinary generates and returns patch data between given revisions, including binary diffs. // GetDiffBinary generates and returns patch data between given revisions, including binary diffs.
func (repo *Repository) GetDiffBinary(base, head string, w io.Writer) error { func (repo *Repository) GetDiffBinary(base, head string, w io.Writer) error {
return NewCommand("diff", "-p", "--binary", base, head). return NewCommandContext(repo.Ctx, "diff", "-p", "--binary", base, head).
RunInDirPipeline(repo.Path, w, nil) RunInDirPipeline(repo.Path, w, nil)
} }
// GetPatch generates and returns format-patch data between given revisions, able to be used with `git apply` // GetPatch generates and returns format-patch data between given revisions, able to be used with `git apply`
func (repo *Repository) GetPatch(base, head string, w io.Writer) error { func (repo *Repository) GetPatch(base, head string, w io.Writer) error {
stderr := new(bytes.Buffer) stderr := new(bytes.Buffer)
err := NewCommand("format-patch", "--binary", "--stdout", base+"..."+head). err := NewCommandContext(repo.Ctx, "format-patch", "--binary", "--stdout", base+"..."+head).
RunInDirPipeline(repo.Path, w, stderr) RunInDirPipeline(repo.Path, w, stderr)
if err != nil && bytes.Contains(stderr.Bytes(), []byte("no merge base")) { if err != nil && bytes.Contains(stderr.Bytes(), []byte("no merge base")) {
return NewCommand("format-patch", "--binary", "--stdout", base, head). return NewCommandContext(repo.Ctx, "format-patch", "--binary", "--stdout", base, head).
RunInDirPipeline(repo.Path, w, nil) RunInDirPipeline(repo.Path, w, nil)
} }
return err return err
@ -256,7 +256,7 @@ func (repo *Repository) GetPatch(base, head string, w io.Writer) error {
// GetDiffFromMergeBase generates and return patch data from merge base to head // GetDiffFromMergeBase generates and return patch data from merge base to head
func (repo *Repository) GetDiffFromMergeBase(base, head string, w io.Writer) error { func (repo *Repository) GetDiffFromMergeBase(base, head string, w io.Writer) error {
stderr := new(bytes.Buffer) stderr := new(bytes.Buffer)
err := NewCommand("diff", "-p", "--binary", base+"..."+head). err := NewCommandContext(repo.Ctx, "diff", "-p", "--binary", base+"..."+head).
RunInDirPipeline(repo.Path, w, stderr) RunInDirPipeline(repo.Path, w, stderr)
if err != nil && bytes.Contains(stderr.Bytes(), []byte("no merge base")) { if err != nil && bytes.Contains(stderr.Bytes(), []byte("no merge base")) {
return repo.GetDiffBinary(base, head, w) return repo.GetDiffBinary(base, head, w)

View File

@ -34,7 +34,7 @@ func (repo *Repository) GetDefaultPublicGPGKey(forceUpdate bool) (*GPGSettings,
Sign: true, Sign: true,
} }
value, _ := NewCommand("config", "--get", "commit.gpgsign").RunInDir(repo.Path) value, _ := NewCommandContext(repo.Ctx, "config", "--get", "commit.gpgsign").RunInDir(repo.Path)
sign, valid := ParseBool(strings.TrimSpace(value)) sign, valid := ParseBool(strings.TrimSpace(value))
if !sign || !valid { if !sign || !valid {
gpgSettings.Sign = false gpgSettings.Sign = false
@ -42,13 +42,13 @@ func (repo *Repository) GetDefaultPublicGPGKey(forceUpdate bool) (*GPGSettings,
return gpgSettings, nil return gpgSettings, nil
} }
signingKey, _ := NewCommand("config", "--get", "user.signingkey").RunInDir(repo.Path) signingKey, _ := NewCommandContext(repo.Ctx, "config", "--get", "user.signingkey").RunInDir(repo.Path)
gpgSettings.KeyID = strings.TrimSpace(signingKey) gpgSettings.KeyID = strings.TrimSpace(signingKey)
defaultEmail, _ := NewCommand("config", "--get", "user.email").RunInDir(repo.Path) defaultEmail, _ := NewCommandContext(repo.Ctx, "config", "--get", "user.email").RunInDir(repo.Path)
gpgSettings.Email = strings.TrimSpace(defaultEmail) gpgSettings.Email = strings.TrimSpace(defaultEmail)
defaultName, _ := NewCommand("config", "--get", "user.name").RunInDir(repo.Path) defaultName, _ := NewCommandContext(repo.Ctx, "config", "--get", "user.name").RunInDir(repo.Path)
gpgSettings.Name = strings.TrimSpace(defaultName) gpgSettings.Name = strings.TrimSpace(defaultName)
if err := gpgSettings.LoadPublicKeyContent(); err != nil { if err := gpgSettings.LoadPublicKeyContent(); err != nil {

View File

@ -18,7 +18,7 @@ import (
// ReadTreeToIndex reads a treeish to the index // ReadTreeToIndex reads a treeish to the index
func (repo *Repository) ReadTreeToIndex(treeish string, indexFilename ...string) error { func (repo *Repository) ReadTreeToIndex(treeish string, indexFilename ...string) error {
if len(treeish) != 40 { if len(treeish) != 40 {
res, err := NewCommand("rev-parse", "--verify", treeish).RunInDir(repo.Path) res, err := NewCommandContext(repo.Ctx, "rev-parse", "--verify", treeish).RunInDir(repo.Path)
if err != nil { if err != nil {
return err return err
} }
@ -38,7 +38,7 @@ func (repo *Repository) readTreeToIndex(id SHA1, indexFilename ...string) error
if len(indexFilename) > 0 { if len(indexFilename) > 0 {
env = append(os.Environ(), "GIT_INDEX_FILE="+indexFilename[0]) env = append(os.Environ(), "GIT_INDEX_FILE="+indexFilename[0])
} }
_, err := NewCommand("read-tree", id.String()).RunInDirWithEnv(repo.Path, env) _, err := NewCommandContext(repo.Ctx, "read-tree", id.String()).RunInDirWithEnv(repo.Path, env)
if err != nil { if err != nil {
return err return err
} }
@ -69,13 +69,13 @@ func (repo *Repository) ReadTreeToTemporaryIndex(treeish string) (filename, tmpD
// EmptyIndex empties the index // EmptyIndex empties the index
func (repo *Repository) EmptyIndex() error { func (repo *Repository) EmptyIndex() error {
_, err := NewCommand("read-tree", "--empty").RunInDir(repo.Path) _, err := NewCommandContext(repo.Ctx, "read-tree", "--empty").RunInDir(repo.Path)
return err return err
} }
// LsFiles checks if the given filenames are in the index // LsFiles checks if the given filenames are in the index
func (repo *Repository) LsFiles(filenames ...string) ([]string, error) { func (repo *Repository) LsFiles(filenames ...string) ([]string, error) {
cmd := NewCommand("ls-files", "-z", "--") cmd := NewCommandContext(repo.Ctx, "ls-files", "-z", "--")
for _, arg := range filenames { for _, arg := range filenames {
if arg != "" { if arg != "" {
cmd.AddArguments(arg) cmd.AddArguments(arg)
@ -95,7 +95,7 @@ func (repo *Repository) LsFiles(filenames ...string) ([]string, error) {
// RemoveFilesFromIndex removes given filenames from the index - it does not check whether they are present. // RemoveFilesFromIndex removes given filenames from the index - it does not check whether they are present.
func (repo *Repository) RemoveFilesFromIndex(filenames ...string) error { func (repo *Repository) RemoveFilesFromIndex(filenames ...string) error {
cmd := NewCommand("update-index", "--remove", "-z", "--index-info") cmd := NewCommandContext(repo.Ctx, "update-index", "--remove", "-z", "--index-info")
stdout := new(bytes.Buffer) stdout := new(bytes.Buffer)
stderr := new(bytes.Buffer) stderr := new(bytes.Buffer)
buffer := new(bytes.Buffer) buffer := new(bytes.Buffer)
@ -111,14 +111,14 @@ func (repo *Repository) RemoveFilesFromIndex(filenames ...string) error {
// AddObjectToIndex adds the provided object hash to the index at the provided filename // AddObjectToIndex adds the provided object hash to the index at the provided filename
func (repo *Repository) AddObjectToIndex(mode string, object SHA1, filename string) error { func (repo *Repository) AddObjectToIndex(mode string, object SHA1, filename string) error {
cmd := NewCommand("update-index", "--add", "--replace", "--cacheinfo", mode, object.String(), filename) cmd := NewCommandContext(repo.Ctx, "update-index", "--add", "--replace", "--cacheinfo", mode, object.String(), filename)
_, err := cmd.RunInDir(repo.Path) _, err := cmd.RunInDir(repo.Path)
return err return err
} }
// WriteTree writes the current index as a tree to the object db and returns its hash // WriteTree writes the current index as a tree to the object db and returns its hash
func (repo *Repository) WriteTree() (*Tree, error) { func (repo *Repository) WriteTree() (*Tree, error) {
res, err := NewCommand("write-tree").RunInDir(repo.Path) res, err := NewCommandContext(repo.Ctx, "write-tree").RunInDir(repo.Path)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -25,7 +25,7 @@ import (
func (repo *Repository) GetLanguageStats(commitID string) (map[string]int64, error) { 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. // 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 // so let's create a batch stdin and stdout
batchStdinWriter, batchReader, cancel := repo.CatFileBatch() batchStdinWriter, batchReader, cancel := repo.CatFileBatch(repo.Ctx)
defer cancel() defer cancel()
writeID := func(id string) error { writeID := func(id string) error {
@ -76,7 +76,7 @@ func (repo *Repository) GetLanguageStats(commitID string) (map[string]int64, err
IndexFile: indexFilename, IndexFile: indexFilename,
WorkTree: worktree, WorkTree: worktree,
} }
ctx, cancel := context.WithCancel(DefaultContext) ctx, cancel := context.WithCancel(repo.Ctx)
if err := checker.Init(ctx); err != nil { if err := checker.Init(ctx); err != nil {
log.Error("Unable to open checker for %s. Error: %v", commitID, err) log.Error("Unable to open checker for %s. Error: %v", commitID, err)
} else { } else {
@ -96,6 +96,12 @@ func (repo *Repository) GetLanguageStats(commitID string) (map[string]int64, err
var content []byte var content []byte
sizes := make(map[string]int64) sizes := make(map[string]int64)
for _, f := range entries { for _, f := range entries {
select {
case <-repo.Ctx.Done():
return sizes, repo.Ctx.Err()
default:
}
contentBuf.Reset() contentBuf.Reset()
content = contentBuf.Bytes() content = contentBuf.Bytes()

View File

@ -42,7 +42,7 @@ func (repo *Repository) HashObject(reader io.Reader) (SHA1, error) {
} }
func (repo *Repository) hashObject(reader io.Reader) (string, error) { func (repo *Repository) hashObject(reader io.Reader) (string, error) {
cmd := NewCommand("hash-object", "-w", "--stdin") cmd := NewCommandContext(repo.Ctx, "hash-object", "-w", "--stdin")
stdout := new(bytes.Buffer) stdout := new(bytes.Buffer)
stderr := new(bytes.Buffer) stderr := new(bytes.Buffer)
err := cmd.RunInDirFullPipeline(repo.Path, stdout, stderr, reader) err := cmd.RunInDirFullPipeline(repo.Path, stdout, stderr, reader)

View File

@ -23,7 +23,7 @@ func (repo *Repository) GetRefsFiltered(pattern string) ([]*Reference, error) {
go func() { go func() {
stderrBuilder := &strings.Builder{} stderrBuilder := &strings.Builder{}
err := NewCommand("for-each-ref").RunInDirPipeline(repo.Path, stdoutWriter, stderrBuilder) err := NewCommandContext(repo.Ctx, "for-each-ref").RunInDirPipeline(repo.Path, stdoutWriter, stderrBuilder)
if err != nil { if err != nil {
_ = stdoutWriter.CloseWithError(ConcatenateError(err, stderrBuilder.String())) _ = stdoutWriter.CloseWithError(ConcatenateError(err, stderrBuilder.String()))
} else { } else {

View File

@ -39,7 +39,7 @@ func (repo *Repository) GetCodeActivityStats(fromTime time.Time, branch string)
since := fromTime.Format(time.RFC3339) since := fromTime.Format(time.RFC3339)
stdout, err := NewCommand("rev-list", "--count", "--no-merges", "--branches=*", "--date=iso", fmt.Sprintf("--since='%s'", since)).RunInDirBytes(repo.Path) stdout, err := NewCommandContext(repo.Ctx, "rev-list", "--count", "--no-merges", "--branches=*", "--date=iso", fmt.Sprintf("--since='%s'", since)).RunInDirBytes(repo.Path)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -67,7 +67,7 @@ func (repo *Repository) GetCodeActivityStats(fromTime time.Time, branch string)
} }
stderr := new(strings.Builder) stderr := new(strings.Builder)
err = NewCommand(args...).RunInDirTimeoutEnvFullPipelineFunc( err = NewCommandContext(repo.Ctx, args...).RunInDirTimeoutEnvFullPipelineFunc(
nil, -1, repo.Path, nil, -1, repo.Path,
stdoutWriter, stderr, nil, stdoutWriter, stderr, nil,
func(ctx context.Context, cancel context.CancelFunc) error { func(ctx context.Context, cancel context.CancelFunc) error {

View File

@ -6,6 +6,7 @@
package git package git
import ( import (
"context"
"fmt" "fmt"
"strings" "strings"
@ -17,19 +18,19 @@ import (
const TagPrefix = "refs/tags/" const TagPrefix = "refs/tags/"
// IsTagExist returns true if given tag exists in the repository. // IsTagExist returns true if given tag exists in the repository.
func IsTagExist(repoPath, name string) bool { func IsTagExist(ctx context.Context, repoPath, name string) bool {
return IsReferenceExist(repoPath, TagPrefix+name) return IsReferenceExist(ctx, repoPath, TagPrefix+name)
} }
// CreateTag create one tag in the repository // CreateTag create one tag in the repository
func (repo *Repository) CreateTag(name, revision string) error { func (repo *Repository) CreateTag(name, revision string) error {
_, err := NewCommand("tag", "--", name, revision).RunInDir(repo.Path) _, err := NewCommandContext(repo.Ctx, "tag", "--", name, revision).RunInDir(repo.Path)
return err return err
} }
// CreateAnnotatedTag create one annotated tag in the repository // CreateAnnotatedTag create one annotated tag in the repository
func (repo *Repository) CreateAnnotatedTag(name, message, revision string) error { func (repo *Repository) CreateAnnotatedTag(name, message, revision string) error {
_, err := NewCommand("tag", "-a", "-m", message, "--", name, revision).RunInDir(repo.Path) _, err := NewCommandContext(repo.Ctx, "tag", "-a", "-m", message, "--", name, revision).RunInDir(repo.Path)
return err return err
} }
@ -79,7 +80,7 @@ func (repo *Repository) getTag(tagID SHA1, name string) (*Tag, error) {
} }
// The tag is an annotated tag with a message. // The tag is an annotated tag with a message.
data, err := NewCommand("cat-file", "-p", tagID.String()).RunInDirBytes(repo.Path) data, err := NewCommandContext(repo.Ctx, "cat-file", "-p", tagID.String()).RunInDirBytes(repo.Path)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -104,7 +105,7 @@ func (repo *Repository) GetTagNameBySHA(sha string) (string, error) {
return "", fmt.Errorf("SHA is too short: %s", sha) return "", fmt.Errorf("SHA is too short: %s", sha)
} }
stdout, err := NewCommand("show-ref", "--tags", "-d").RunInDir(repo.Path) stdout, err := NewCommandContext(repo.Ctx, "show-ref", "--tags", "-d").RunInDir(repo.Path)
if err != nil { if err != nil {
return "", err return "", err
} }
@ -127,7 +128,7 @@ func (repo *Repository) GetTagNameBySHA(sha string) (string, error) {
// GetTagID returns the object ID for a tag (annotated tags have both an object SHA AND a commit SHA) // GetTagID returns the object ID for a tag (annotated tags have both an object SHA AND a commit SHA)
func (repo *Repository) GetTagID(name string) (string, error) { func (repo *Repository) GetTagID(name string) (string, error) {
stdout, err := NewCommand("show-ref", "--tags", "--", name).RunInDir(repo.Path) stdout, err := NewCommandContext(repo.Ctx, "show-ref", "--tags", "--", name).RunInDir(repo.Path)
if err != nil { if err != nil {
return "", err return "", err
} }
@ -163,7 +164,7 @@ func (repo *Repository) GetTag(name string) (*Tag, error) {
// GetTagInfos returns all tag infos of the repository. // GetTagInfos returns all tag infos of the repository.
func (repo *Repository) GetTagInfos(page, pageSize int) ([]*Tag, int, error) { func (repo *Repository) GetTagInfos(page, pageSize int) ([]*Tag, int, error) {
// TODO this a slow implementation, makes one git command per tag // TODO this a slow implementation, makes one git command per tag
stdout, err := NewCommand("tag").RunInDir(repo.Path) stdout, err := NewCommandContext(repo.Ctx, "tag").RunInDir(repo.Path)
if err != nil { if err != nil {
return nil, 0, err return nil, 0, err
} }
@ -196,7 +197,7 @@ func (repo *Repository) GetTagInfos(page, pageSize int) ([]*Tag, int, error) {
// GetTagType gets the type of the tag, either commit (simple) or tag (annotated) // GetTagType gets the type of the tag, either commit (simple) or tag (annotated)
func (repo *Repository) GetTagType(id SHA1) (string, error) { func (repo *Repository) GetTagType(id SHA1) (string, error) {
// Get tag type // Get tag type
stdout, err := NewCommand("cat-file", "-t", id.String()).RunInDir(repo.Path) stdout, err := NewCommandContext(repo.Ctx, "cat-file", "-t", id.String()).RunInDir(repo.Path)
if err != nil { if err != nil {
return "", err return "", err
} }

View File

@ -20,6 +20,6 @@ func (repo *Repository) IsTagExist(name string) bool {
// GetTags returns all tags of the repository. // GetTags returns all tags of the repository.
// returning at most limit tags, or all if limit is 0. // returning at most limit tags, or all if limit is 0.
func (repo *Repository) GetTags(skip, limit int) (tags []string, err error) { func (repo *Repository) GetTags(skip, limit int) (tags []string, err error) {
tags, _, err = callShowRef(repo.Path, TagPrefix, "--tags", skip, limit) tags, _, err = callShowRef(repo.Ctx, repo.Path, TagPrefix, "--tags", skip, limit)
return return
} }

View File

@ -40,7 +40,7 @@ func (repo *Repository) CommitTree(author *Signature, committer *Signature, tree
"GIT_COMMITTER_EMAIL="+committer.Email, "GIT_COMMITTER_EMAIL="+committer.Email,
"GIT_COMMITTER_DATE="+commitTimeStr, "GIT_COMMITTER_DATE="+commitTimeStr,
) )
cmd := NewCommand("commit-tree", tree.ID.String()) cmd := NewCommandContext(repo.Ctx, "commit-tree", tree.ID.String())
for _, parent := range opts.Parents { for _, parent := range opts.Parents {
cmd.AddArguments("-p", parent) cmd.AddArguments("-p", parent)

View File

@ -22,7 +22,7 @@ func (repo *Repository) getTree(id SHA1) (*Tree, error) {
// GetTree find the tree object in the repository. // GetTree find the tree object in the repository.
func (repo *Repository) GetTree(idStr string) (*Tree, error) { func (repo *Repository) GetTree(idStr string) (*Tree, error) {
if len(idStr) != 40 { if len(idStr) != 40 {
res, err := NewCommand("rev-parse", "--verify", idStr).RunInDir(repo.Path) res, err := NewCommandContext(repo.Ctx, "rev-parse", "--verify", idStr).RunInDir(repo.Path)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -12,7 +12,7 @@ import (
) )
func (repo *Repository) getTree(id SHA1) (*Tree, error) { func (repo *Repository) getTree(id SHA1) (*Tree, error) {
wr, rd, cancel := repo.CatFileBatch() wr, rd, cancel := repo.CatFileBatch(repo.Ctx)
defer cancel() defer cancel()
_, _ = wr.Write([]byte(id.String() + "\n")) _, _ = wr.Write([]byte(id.String() + "\n"))

View File

@ -36,7 +36,7 @@ func (t *Tree) ListEntries() (Entries, error) {
} }
if t.repo != nil { if t.repo != nil {
wr, rd, cancel := t.repo.CatFileBatch() wr, rd, cancel := t.repo.CatFileBatch(t.repo.Ctx)
defer cancel() defer cancel()
_, _ = wr.Write([]byte(t.ID.String() + "\n")) _, _ = wr.Write([]byte(t.ID.String() + "\n"))

View File

@ -275,7 +275,7 @@ func (b *BleveIndexer) Index(repo *models.Repository, sha string, changes *repoC
batch := gitea_bleve.NewFlushingBatch(b.indexer, maxBatchSize) batch := gitea_bleve.NewFlushingBatch(b.indexer, maxBatchSize)
if len(changes.Updates) > 0 { if len(changes.Updates) > 0 {
batchWriter, batchReader, cancel := git.CatFileBatch(repo.RepoPath()) batchWriter, batchReader, cancel := git.CatFileBatch(git.DefaultContext, repo.RepoPath())
defer cancel() defer cancel()
for _, update := range changes.Updates { for _, update := range changes.Updates {

View File

@ -248,7 +248,7 @@ func (b *ElasticSearchIndexer) Index(repo *models.Repository, sha string, change
reqs := make([]elastic.BulkableRequest, 0) reqs := make([]elastic.BulkableRequest, 0)
if len(changes.Updates) > 0 { if len(changes.Updates) > 0 {
batchWriter, batchReader, cancel := git.CatFileBatch(repo.RepoPath()) batchWriter, batchReader, cancel := git.CatFileBatch(git.DefaultContext, repo.RepoPath())
defer cancel() defer cancel()
for _, update := range changes.Updates { for _, update := range changes.Updates {

View File

@ -5,9 +5,13 @@
package stats package stats
import ( import (
"fmt"
"code.gitea.io/gitea/models" "code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/graceful"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/process"
) )
// DBIndexer implements Indexer interface to use database's like search // DBIndexer implements Indexer interface to use database's like search
@ -16,6 +20,9 @@ type DBIndexer struct {
// Index repository status function // Index repository status function
func (db *DBIndexer) Index(id int64) error { func (db *DBIndexer) Index(id int64) error {
ctx, _, finished := process.GetManager().AddContext(graceful.GetManager().ShutdownContext(), fmt.Sprintf("Stats.DB Index Repo[%d]", id))
defer finished()
repo, err := models.GetRepositoryByID(id) repo, err := models.GetRepositoryByID(id)
if err != nil { if err != nil {
return err return err
@ -29,7 +36,7 @@ func (db *DBIndexer) Index(id int64) error {
return err return err
} }
gitRepo, err := git.OpenRepository(repo.RepoPath()) gitRepo, err := git.OpenRepositoryCtx(ctx, repo.RepoPath())
if err != nil { if err != nil {
return err return err
} }

View File

@ -5,7 +5,6 @@
package external package external
import ( import (
"context"
"fmt" "fmt"
"io" "io"
"os" "os"
@ -107,11 +106,8 @@ func (p *Renderer) Render(ctx *markup.RenderContext, input io.Reader, output io.
ctx.Ctx = graceful.GetManager().ShutdownContext() ctx.Ctx = graceful.GetManager().ShutdownContext()
} }
processCtx, cancel := context.WithCancel(ctx.Ctx) processCtx, _, finished := process.GetManager().AddContext(ctx.Ctx, fmt.Sprintf("Render [%s] for %s", commands[0], ctx.URLPrefix))
defer cancel() defer finished()
pid := process.GetManager().Add(fmt.Sprintf("Render [%s] for %s", commands[0], ctx.URLPrefix), cancel)
defer process.GetManager().Remove(pid)
cmd := exec.CommandContext(processCtx, commands[0], args...) cmd := exec.CommandContext(processCtx, commands[0], args...)
cmd.Env = append( cmd.Env = append(

View File

@ -0,0 +1,69 @@
// Copyright 2021 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package process
import (
"context"
)
// Context is a wrapper around context.Context and contains the current pid for this context
type Context struct {
context.Context
pid IDType
}
// GetPID returns the PID for this context
func (c *Context) GetPID() IDType {
return c.pid
}
// GetParent returns the parent process context (if any)
func (c *Context) GetParent() *Context {
return GetContext(c.Context)
}
// Value is part of the interface for context.Context. We mostly defer to the internal context - but we return this in response to the ProcessContextKey
func (c *Context) Value(key interface{}) interface{} {
if key == ProcessContextKey {
return c
}
return c.Context.Value(key)
}
// ProcessContextKey is the key under which process contexts are stored
var ProcessContextKey interface{} = "process-context"
// GetContext will return a process context if one exists
func GetContext(ctx context.Context) *Context {
if pCtx, ok := ctx.(*Context); ok {
return pCtx
}
pCtxInterface := ctx.Value(ProcessContextKey)
if pCtxInterface == nil {
return nil
}
if pCtx, ok := pCtxInterface.(*Context); ok {
return pCtx
}
return nil
}
// GetPID returns the PID for this context
func GetPID(ctx context.Context) IDType {
pCtx := GetContext(ctx)
if pCtx == nil {
return ""
}
return pCtx.GetPID()
}
// GetParentPID returns the ParentPID for this context
func GetParentPID(ctx context.Context) IDType {
var parentPID IDType
if parentProcess := GetContext(ctx); parentProcess != nil {
parentPID = parentProcess.GetPID()
}
return parentPID
}

View File

@ -12,6 +12,7 @@ import (
"io" "io"
"os/exec" "os/exec"
"sort" "sort"
"strconv"
"sync" "sync"
"time" "time"
) )
@ -28,57 +29,151 @@ var (
DefaultContext = context.Background() DefaultContext = context.Background()
) )
// Process represents a working process inheriting from Gitea. // IDType is a pid type
type Process struct { type IDType string
PID int64 // Process ID, not system one.
Description string
Start time.Time
Cancel context.CancelFunc
}
// Manager knows about all processes and counts PIDs. // FinishedFunc is a function that marks that the process is finished and can be removed from the process table
// - it is simply an alias for context.CancelFunc and is only for documentary purposes
type FinishedFunc = context.CancelFunc
// Manager manages all processes and counts PIDs.
type Manager struct { type Manager struct {
mutex sync.Mutex mutex sync.Mutex
counter int64 next int64
processes map[int64]*Process lastTime int64
processes map[IDType]*Process
} }
// GetManager returns a Manager and initializes one as singleton if there's none yet // GetManager returns a Manager and initializes one as singleton if there's none yet
func GetManager() *Manager { func GetManager() *Manager {
managerInit.Do(func() { managerInit.Do(func() {
manager = &Manager{ manager = &Manager{
processes: make(map[int64]*Process), processes: make(map[IDType]*Process),
next: 1,
} }
}) })
return manager return manager
} }
// Add a process to the ProcessManager and returns its PID. // AddContext creates a new context and adds it as a process. Once the process is finished, finished must be called
func (pm *Manager) Add(description string, cancel context.CancelFunc) int64 { // to remove the process from the process table. It should not be called until the process is finished but must always be called.
//
// cancel should be used to cancel the returned context, however it will not remove the process from the process table.
// finished will cancel the returned context and remove it from the process table.
//
// Most processes will not need to use the cancel function but there will be cases whereby you want to cancel the process but not immediately remove it from the
// process table.
func (pm *Manager) AddContext(parent context.Context, description string) (ctx context.Context, cancel context.CancelFunc, finished FinishedFunc) {
parentPID := GetParentPID(parent)
ctx, cancel = context.WithCancel(parent)
pid, finished := pm.Add(parentPID, description, cancel)
return &Context{
Context: ctx,
pid: pid,
}, cancel, finished
}
// AddContextTimeout creates a new context and add it as a process. Once the process is finished, finished must be called
// to remove the process from the process table. It should not be called until the process is finsihed but must always be called.
//
// cancel should be used to cancel the returned context, however it will not remove the process from the process table.
// finished will cancel the returned context and remove it from the process table.
//
// Most processes will not need to use the cancel function but there will be cases whereby you want to cancel the process but not immediately remove it from the
// process table.
func (pm *Manager) AddContextTimeout(parent context.Context, timeout time.Duration, description string) (ctx context.Context, cancel context.CancelFunc, finshed FinishedFunc) {
parentPID := GetParentPID(parent)
ctx, cancel = context.WithTimeout(parent, timeout)
pid, finshed := pm.Add(parentPID, description, cancel)
return &Context{
Context: ctx,
pid: pid,
}, cancel, finshed
}
// Add create a new process
func (pm *Manager) Add(parentPID IDType, description string, cancel context.CancelFunc) (IDType, FinishedFunc) {
pm.mutex.Lock() pm.mutex.Lock()
pid := pm.counter + 1 start, pid := pm.nextPID()
pm.processes[pid] = &Process{
parent := pm.processes[parentPID]
if parent == nil {
parentPID = ""
}
process := &Process{
PID: pid, PID: pid,
ParentPID: parentPID,
Description: description, Description: description,
Start: time.Now(), Start: start,
Cancel: cancel, Cancel: cancel,
} }
pm.counter = pid
finished := func() {
cancel()
pm.remove(process)
}
if parent != nil {
parent.AddChild(process)
}
pm.processes[pid] = process
pm.mutex.Unlock() pm.mutex.Unlock()
return pid return pid, finished
}
// nextPID will return the next available PID. pm.mutex should already be locked.
func (pm *Manager) nextPID() (start time.Time, pid IDType) {
start = time.Now()
startUnix := start.Unix()
if pm.lastTime == startUnix {
pm.next++
} else {
pm.next = 1
}
pm.lastTime = startUnix
pid = IDType(strconv.FormatInt(start.Unix(), 16))
if pm.next == 1 {
return
}
pid = IDType(string(pid) + "-" + strconv.FormatInt(pm.next, 10))
return
} }
// Remove a process from the ProcessManager. // Remove a process from the ProcessManager.
func (pm *Manager) Remove(pid int64) { func (pm *Manager) Remove(pid IDType) {
pm.mutex.Lock() pm.mutex.Lock()
delete(pm.processes, pid) delete(pm.processes, pid)
pm.mutex.Unlock() pm.mutex.Unlock()
} }
func (pm *Manager) remove(process *Process) {
pm.mutex.Lock()
if p := pm.processes[process.PID]; p == process {
delete(pm.processes, process.PID)
}
parent := pm.processes[process.ParentPID]
pm.mutex.Unlock()
if parent == nil {
return
}
parent.RemoveChild(process)
}
// Cancel a process in the ProcessManager. // Cancel a process in the ProcessManager.
func (pm *Manager) Cancel(pid int64) { func (pm *Manager) Cancel(pid IDType) {
pm.mutex.Lock() pm.mutex.Lock()
process, ok := pm.processes[pid] process, ok := pm.processes[pid]
pm.mutex.Unlock() pm.mutex.Unlock()
@ -88,14 +183,28 @@ func (pm *Manager) Cancel(pid int64) {
} }
// Processes gets the processes in a thread safe manner // Processes gets the processes in a thread safe manner
func (pm *Manager) Processes() []*Process { func (pm *Manager) Processes(onlyRoots bool) []*Process {
pm.mutex.Lock() pm.mutex.Lock()
processes := make([]*Process, 0, len(pm.processes)) processes := make([]*Process, 0, len(pm.processes))
if onlyRoots {
for _, process := range pm.processes {
if _, has := pm.processes[process.ParentPID]; !has {
processes = append(processes, process)
}
}
} else {
for _, process := range pm.processes { for _, process := range pm.processes {
processes = append(processes, process) processes = append(processes, process)
} }
}
pm.mutex.Unlock() pm.mutex.Unlock()
sort.Sort(processList(processes))
sort.Slice(processes, func(i, j int) bool {
left, right := processes[i], processes[j]
return left.Start.Before(right.Start)
})
return processes return processes
} }
@ -134,8 +243,8 @@ func (pm *Manager) ExecDirEnvStdIn(timeout time.Duration, dir, desc string, env
stdOut := new(bytes.Buffer) stdOut := new(bytes.Buffer)
stdErr := new(bytes.Buffer) stdErr := new(bytes.Buffer)
ctx, cancel := context.WithTimeout(DefaultContext, timeout) ctx, _, finished := pm.AddContextTimeout(DefaultContext, timeout, desc)
defer cancel() defer finished()
cmd := exec.CommandContext(ctx, cmdName, args...) cmd := exec.CommandContext(ctx, cmdName, args...)
cmd.Dir = dir cmd.Dir = dir
@ -150,13 +259,11 @@ func (pm *Manager) ExecDirEnvStdIn(timeout time.Duration, dir, desc string, env
return "", "", err return "", "", err
} }
pid := pm.Add(desc, cancel)
err := cmd.Wait() err := cmd.Wait()
pm.Remove(pid)
if err != nil { if err != nil {
err = &Error{ err = &Error{
PID: pid, PID: GetPID(ctx),
Description: desc, Description: desc,
Err: err, Err: err,
CtxErr: ctx.Err(), CtxErr: ctx.Err(),
@ -168,23 +275,9 @@ func (pm *Manager) ExecDirEnvStdIn(timeout time.Duration, dir, desc string, env
return stdOut.String(), stdErr.String(), err return stdOut.String(), stdErr.String(), err
} }
type processList []*Process
func (l processList) Len() int {
return len(l)
}
func (l processList) Less(i, j int) bool {
return l[i].PID < l[j].PID
}
func (l processList) Swap(i, j int) {
l[i], l[j] = l[j], l[i]
}
// Error is a wrapped error describing the error results of Process Execution // Error is a wrapped error describing the error results of Process Execution
type Error struct { type Error struct {
PID int64 PID IDType
Description string Description string
Err error Err error
CtxErr error CtxErr error
@ -193,7 +286,7 @@ type Error struct {
} }
func (err *Error) Error() string { func (err *Error) Error() string {
return fmt.Sprintf("exec(%d:%s) failed: %v(%v) stdout: %s stderr: %s", err.PID, err.Description, err.Err, err.CtxErr, err.Stdout, err.Stderr) return fmt.Sprintf("exec(%s:%s) failed: %v(%v) stdout: %s stderr: %s", err.PID, err.Description, err.Err, err.CtxErr, err.Stdout, err.Stderr)
} }
// Unwrap implements the unwrappable implicit interface for go1.13 Unwrap() // Unwrap implements the unwrappable implicit interface for go1.13 Unwrap()

View File

@ -21,44 +21,72 @@ func TestGetManager(t *testing.T) {
assert.NotNil(t, pm) assert.NotNil(t, pm)
} }
func TestManager_Add(t *testing.T) { func TestManager_AddContext(t *testing.T) {
pm := Manager{processes: make(map[int64]*Process)} pm := Manager{processes: make(map[IDType]*Process), next: 1}
pid := pm.Add("foo", nil) ctx, cancel := context.WithCancel(context.Background())
assert.Equal(t, int64(1), pid, "expected to get pid 1 got %d", pid) defer cancel()
pid = pm.Add("bar", nil) p1Ctx, _, finished := pm.AddContext(ctx, "foo")
assert.Equal(t, int64(2), pid, "expected to get pid 2 got %d", pid) defer finished()
assert.NotEmpty(t, GetContext(p1Ctx).GetPID(), "expected to get non-empty pid")
p2Ctx, _, finished := pm.AddContext(p1Ctx, "bar")
defer finished()
assert.NotEmpty(t, GetContext(p2Ctx).GetPID(), "expected to get non-empty pid")
assert.NotEqual(t, GetContext(p1Ctx).GetPID(), GetContext(p2Ctx).GetPID(), "expected to get different pids %s == %s", GetContext(p2Ctx).GetPID(), GetContext(p1Ctx).GetPID())
assert.Equal(t, GetContext(p1Ctx).GetPID(), GetContext(p2Ctx).GetParent().GetPID(), "expected to get pid %s got %s", GetContext(p1Ctx).GetPID(), GetContext(p2Ctx).GetParent().GetPID())
} }
func TestManager_Cancel(t *testing.T) { func TestManager_Cancel(t *testing.T) {
pm := Manager{processes: make(map[int64]*Process)} pm := Manager{processes: make(map[IDType]*Process), next: 1}
ctx, cancel := context.WithCancel(context.Background()) ctx, _, finished := pm.AddContext(context.Background(), "foo")
pid := pm.Add("foo", cancel) defer finished()
pm.Cancel(pid) pm.Cancel(GetPID(ctx))
select { select {
case <-ctx.Done(): case <-ctx.Done():
default: default:
assert.Fail(t, "Cancel should cancel the provided context") assert.Fail(t, "Cancel should cancel the provided context")
} }
finished()
ctx, cancel, finished := pm.AddContext(context.Background(), "foo")
defer finished()
cancel()
select {
case <-ctx.Done():
default:
assert.Fail(t, "Cancel should cancel the provided context")
}
finished()
} }
func TestManager_Remove(t *testing.T) { func TestManager_Remove(t *testing.T) {
pm := Manager{processes: make(map[int64]*Process)} pm := Manager{processes: make(map[IDType]*Process), next: 1}
pid1 := pm.Add("foo", nil) ctx, cancel := context.WithCancel(context.Background())
assert.Equal(t, int64(1), pid1, "expected to get pid 1 got %d", pid1) defer cancel()
pid2 := pm.Add("bar", nil) p1Ctx, _, finished := pm.AddContext(ctx, "foo")
assert.Equal(t, int64(2), pid2, "expected to get pid 2 got %d", pid2) defer finished()
assert.NotEmpty(t, GetContext(p1Ctx).GetPID(), "expected to have non-empty PID")
pm.Remove(pid2) p2Ctx, _, finished := pm.AddContext(p1Ctx, "bar")
defer finished()
_, exists := pm.processes[pid2] assert.NotEqual(t, GetContext(p1Ctx).GetPID(), GetContext(p2Ctx).GetPID(), "expected to get different pids got %s == %s", GetContext(p2Ctx).GetPID(), GetContext(p1Ctx).GetPID())
assert.False(t, exists, "PID %d is in the list but shouldn't", pid2)
pm.Remove(GetPID(p2Ctx))
_, exists := pm.processes[GetPID(p2Ctx)]
assert.False(t, exists, "PID %d is in the list but shouldn't", GetPID(p2Ctx))
} }
func TestExecTimeoutNever(t *testing.T) { func TestExecTimeoutNever(t *testing.T) {

View File

@ -0,0 +1,66 @@
// Copyright 2021 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package process
import (
"context"
"sync"
"time"
)
// Process represents a working process inheriting from Gitea.
type Process struct {
PID IDType // Process ID, not system one.
ParentPID IDType
Description string
Start time.Time
Cancel context.CancelFunc
lock sync.Mutex
children []*Process
}
// Children gets the children of the process
// Note: this function will behave nicely even if p is nil
func (p *Process) Children() (children []*Process) {
if p == nil {
return
}
p.lock.Lock()
defer p.lock.Unlock()
children = make([]*Process, len(p.children))
copy(children, p.children)
return children
}
// AddChild adds a child process
// Note: this function will behave nicely even if p is nil
func (p *Process) AddChild(child *Process) {
if p == nil {
return
}
p.lock.Lock()
defer p.lock.Unlock()
p.children = append(p.children, child)
}
// RemoveChild removes a child process
// Note: this function will behave nicely even if p is nil
func (p *Process) RemoveChild(process *Process) {
if p == nil {
return
}
p.lock.Lock()
defer p.lock.Unlock()
for i, child := range p.children {
if child == process {
p.children = append(p.children[:i], p.children[i+1:]...)
return
}
}
}

View File

@ -957,7 +957,7 @@ type remoteAddress struct {
func mirrorRemoteAddress(m models.RemoteMirrorer) remoteAddress { func mirrorRemoteAddress(m models.RemoteMirrorer) remoteAddress {
a := remoteAddress{} a := remoteAddress{}
u, err := git.GetRemoteAddress(m.GetRepository().RepoPath(), m.GetRemoteName()) u, err := git.GetRemoteAddress(git.DefaultContext, m.GetRepository().RepoPath(), m.GetRemoteName())
if err != nil { if err != nil {
log.Error("GetRemoteAddress %v", err) log.Error("GetRemoteAddress %v", err)
return a return a

View File

@ -2696,6 +2696,7 @@ monitor.execute_time = Execution Time
monitor.process.cancel = Cancel process monitor.process.cancel = Cancel process
monitor.process.cancel_desc = Cancelling a process may cause data loss monitor.process.cancel_desc = Cancelling a process may cause data loss
monitor.process.cancel_notices = Cancel: <strong>%s</strong>? monitor.process.cancel_notices = Cancel: <strong>%s</strong>?
monitor.process.children = Children
monitor.queues = Queues monitor.queues = Queues
monitor.queue = Queue: %s monitor.queue = Queue: %s
monitor.queue.name = Name monitor.queue.name = Name

View File

@ -405,7 +405,7 @@ func CreateBranchProtection(ctx *context.APIContext) {
repo := ctx.Repo.Repository repo := ctx.Repo.Repository
// Currently protection must match an actual branch // Currently protection must match an actual branch
if !git.IsBranchExist(ctx.Repo.Repository.RepoPath(), form.BranchName) { if !git.IsBranchExist(ctx.Req.Context(), ctx.Repo.Repository.RepoPath(), form.BranchName) {
ctx.NotFound() ctx.NotFound()
return return
} }

View File

@ -11,6 +11,7 @@ import (
"code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/process"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"github.com/chi-middleware/proxy" "github.com/chi-middleware/proxy"
@ -22,7 +23,9 @@ func Middlewares() []func(http.Handler) http.Handler {
var handlers = []func(http.Handler) http.Handler{ var handlers = []func(http.Handler) http.Handler{
func(next http.Handler) http.Handler { func(next http.Handler) http.Handler {
return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) { return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
next.ServeHTTP(context.NewResponse(resp), req) ctx, _, finished := process.GetManager().AddContext(req.Context(), fmt.Sprintf("%s: %s", req.Method, req.RequestURI))
defer finished()
next.ServeHTTP(context.NewResponse(resp), req.WithContext(ctx))
}) })
}, },
} }

View File

@ -326,7 +326,7 @@ func Monitor(ctx *context.Context) {
ctx.Data["Title"] = ctx.Tr("admin.monitor") ctx.Data["Title"] = ctx.Tr("admin.monitor")
ctx.Data["PageIsAdmin"] = true ctx.Data["PageIsAdmin"] = true
ctx.Data["PageIsAdminMonitor"] = true ctx.Data["PageIsAdminMonitor"] = true
ctx.Data["Processes"] = process.GetManager().Processes() ctx.Data["Processes"] = process.GetManager().Processes(true)
ctx.Data["Entries"] = cron.ListTasks() ctx.Data["Entries"] = cron.ListTasks()
ctx.Data["Queues"] = queue.GetManager().ManagedQueues() ctx.Data["Queues"] = queue.GetManager().ManagedQueues()
ctx.HTML(http.StatusOK, tplMonitor) ctx.HTML(http.StatusOK, tplMonitor)
@ -334,8 +334,8 @@ func Monitor(ctx *context.Context) {
// MonitorCancel cancels a process // MonitorCancel cancels a process
func MonitorCancel(ctx *context.Context) { func MonitorCancel(ctx *context.Context) {
pid := ctx.ParamsInt64("pid") pid := ctx.Params("pid")
process.GetManager().Cancel(pid) process.GetManager().Cancel(process.IDType(pid))
ctx.JSON(http.StatusOK, map[string]interface{}{ ctx.JSON(http.StatusOK, map[string]interface{}{
"redirect": setting.AppSubURL + "/admin/monitor", "redirect": setting.AppSubURL + "/admin/monitor",
}) })

View File

@ -124,7 +124,7 @@ func RestoreBranchPost(ctx *context.Context) {
return return
} }
if err := git.Push(ctx.Repo.Repository.RepoPath(), git.PushOptions{ if err := git.Push(ctx, ctx.Repo.Repository.RepoPath(), git.PushOptions{
Remote: ctx.Repo.Repository.RepoPath(), Remote: ctx.Repo.Repository.RepoPath(),
Branch: fmt.Sprintf("%s:%s%s", deletedBranch.Commit, git.BranchPrefix, deletedBranch.Name), Branch: fmt.Sprintf("%s:%s%s", deletedBranch.Commit, git.BranchPrefix, deletedBranch.Name),
Env: models.PushingEnvironment(ctx.User, ctx.Repo.Repository), Env: models.PushingEnvironment(ctx.User, ctx.Repo.Repository),

View File

@ -8,7 +8,6 @@ package repo
import ( import (
"bytes" "bytes"
"compress/gzip" "compress/gzip"
gocontext "context"
"fmt" "fmt"
"net/http" "net/http"
"os" "os"
@ -485,8 +484,10 @@ func serviceRPC(h serviceHandler, service string) {
h.environ = append(h.environ, "GIT_PROTOCOL="+protocol) h.environ = append(h.environ, "GIT_PROTOCOL="+protocol)
} }
ctx, cancel := gocontext.WithCancel(git.DefaultContext) // ctx, cancel := gocontext.WithCancel(git.DefaultContext)
defer cancel() ctx, _, finished := process.GetManager().AddContext(h.r.Context(), fmt.Sprintf("%s %s %s [repo_path: %s]", git.GitExecutable, service, "--stateless-rpc", h.dir))
defer finished()
var stderr bytes.Buffer var stderr bytes.Buffer
cmd := exec.CommandContext(ctx, git.GitExecutable, service, "--stateless-rpc", h.dir) cmd := exec.CommandContext(ctx, git.GitExecutable, service, "--stateless-rpc", h.dir)
cmd.Dir = h.dir cmd.Dir = h.dir
@ -495,9 +496,6 @@ func serviceRPC(h serviceHandler, service string) {
cmd.Stdin = reqBody cmd.Stdin = reqBody
cmd.Stderr = &stderr cmd.Stderr = &stderr
pid := process.GetManager().Add(fmt.Sprintf("%s %s %s [repo_path: %s]", git.GitExecutable, service, "--stateless-rpc", h.dir), cancel)
defer process.GetManager().Remove(pid)
if err := cmd.Run(); err != nil { if err := cmd.Run(); err != nil {
log.Error("Fail to serve RPC(%s) in %s: %v - %s", service, h.dir, err, stderr.String()) log.Error("Fail to serve RPC(%s) in %s: %v - %s", service, h.dir, err, stderr.String())
return return

View File

@ -1594,7 +1594,7 @@ func ViewIssue(ctx *context.Context) {
} }
ctx.Data["IsPullBranchDeletable"] = canDelete && ctx.Data["IsPullBranchDeletable"] = canDelete &&
pull.HeadRepo != nil && pull.HeadRepo != nil &&
git.IsBranchExist(pull.HeadRepo.RepoPath(), pull.HeadBranch) && git.IsBranchExist(ctx, pull.HeadRepo.RepoPath(), pull.HeadBranch) &&
(!pull.HasMerged || ctx.Data["HeadBranchCommitID"] == ctx.Data["PullHeadCommitID"]) (!pull.HasMerged || ctx.Data["HeadBranchCommitID"] == ctx.Data["PullHeadCommitID"])
stillCanManualMerge := func() bool { stillCanManualMerge := func() bool {

View File

@ -436,7 +436,7 @@ func PrepareViewPullInfo(ctx *context.Context, issue *models.Issue) *git.Compare
if pull.Flow == models.PullRequestFlowGithub { if pull.Flow == models.PullRequestFlowGithub {
headBranchExist = headGitRepo.IsBranchExist(pull.HeadBranch) headBranchExist = headGitRepo.IsBranchExist(pull.HeadBranch)
} else { } else {
headBranchExist = git.IsReferenceExist(baseGitRepo.Path, pull.GetGitRefName()) headBranchExist = git.IsReferenceExist(ctx, baseGitRepo.Path, pull.GetGitRefName())
} }
if headBranchExist { if headBranchExist {

View File

@ -178,7 +178,7 @@ func SettingsPost(ctx *context.Context) {
} }
} }
u, _ := git.GetRemoteAddress(ctx.Repo.Repository.RepoPath(), ctx.Repo.Mirror.GetRemoteName()) u, _ := git.GetRemoteAddress(ctx, ctx.Repo.Repository.RepoPath(), ctx.Repo.Mirror.GetRemoteName())
if u.User != nil && form.MirrorPassword == "" && form.MirrorUsername == u.User.Username() { if u.User != nil && form.MirrorPassword == "" && form.MirrorUsername == u.User.Username() {
form.MirrorPassword, _ = u.User.Password() form.MirrorPassword, _ = u.User.Password()
} }

View File

@ -82,11 +82,10 @@ func (t *Task) RunWithUser(doer *user_model.User, config Config) {
} }
}() }()
graceful.GetManager().RunWithShutdownContext(func(baseCtx context.Context) { graceful.GetManager().RunWithShutdownContext(func(baseCtx context.Context) {
ctx, cancel := context.WithCancel(baseCtx)
defer cancel()
pm := process.GetManager() pm := process.GetManager()
pid := pm.Add(config.FormatMessage(t.Name, "process", doer), cancel) ctx, _, finished := pm.AddContext(baseCtx, config.FormatMessage(t.Name, "process", doer))
defer pm.Remove(pid) defer finished()
if err := t.fun(ctx, doer, config); err != nil { if err := t.fun(ctx, doer, config); err != nil {
if db.IsErrCancelled(err) { if db.IsErrCancelled(err) {
message := err.(db.ErrCancelled).Message message := err.(db.ErrCancelled).Message

View File

@ -1303,8 +1303,9 @@ func GetDiff(gitRepo *git.Repository, opts *DiffOptions, files ...string) (*Diff
return nil, err return nil, err
} }
ctx, cancel := context.WithTimeout(git.DefaultContext, time.Duration(setting.Git.Timeout.Default)*time.Second) timeout := time.Duration(setting.Git.Timeout.Default) * time.Second
defer cancel() ctx, _, finished := process.GetManager().AddContextTimeout(gitRepo.Ctx, timeout, fmt.Sprintf("GetDiffRange [repo_path: %s]", repoPath))
defer finished()
argsLength := 6 argsLength := 6
if len(opts.WhitespaceBehavior) > 0 { if len(opts.WhitespaceBehavior) > 0 {
@ -1369,9 +1370,6 @@ func GetDiff(gitRepo *git.Repository, opts *DiffOptions, files ...string) (*Diff
return nil, fmt.Errorf("error during Start: %w", err) return nil, fmt.Errorf("error during Start: %w", err)
} }
pid := process.GetManager().Add(fmt.Sprintf("GetDiffRange [repo_path: %s]", repoPath), cancel)
defer process.GetManager().Remove(pid)
diff, err := ParsePatch(opts.MaxLines, opts.MaxLineCharacters, opts.MaxFiles, stdout, parsePatchSkipToFile) diff, err := ParsePatch(opts.MaxLines, opts.MaxLineCharacters, opts.MaxFiles, stdout, parsePatchSkipToFile)
if err != nil { if err != nil {
return nil, fmt.Errorf("unable to ParsePatch: %w", err) return nil, fmt.Errorf("unable to ParsePatch: %w", err)

View File

@ -7,7 +7,6 @@ package mailer
import ( import (
"bytes" "bytes"
"context"
"crypto/tls" "crypto/tls"
"fmt" "fmt"
"io" "io"
@ -258,11 +257,10 @@ func (s *sendmailSender) Send(from string, to []string, msg io.WriterTo) error {
args = append(args, to...) args = append(args, to...)
log.Trace("Sending with: %s %v", setting.MailService.SendmailPath, args) log.Trace("Sending with: %s %v", setting.MailService.SendmailPath, args)
pm := process.GetManager()
desc := fmt.Sprintf("SendMail: %s %v", setting.MailService.SendmailPath, args) desc := fmt.Sprintf("SendMail: %s %v", setting.MailService.SendmailPath, args)
ctx, cancel := context.WithTimeout(graceful.GetManager().HammerContext(), setting.MailService.SendmailTimeout) ctx, _, finished := process.GetManager().AddContextTimeout(graceful.GetManager().HammerContext(), setting.MailService.SendmailTimeout, desc)
defer cancel() defer finished()
cmd := exec.CommandContext(ctx, setting.MailService.SendmailPath, args...) cmd := exec.CommandContext(ctx, setting.MailService.SendmailPath, args...)
pipe, err := cmd.StdinPipe() pipe, err := cmd.StdinPipe()
@ -272,18 +270,17 @@ func (s *sendmailSender) Send(from string, to []string, msg io.WriterTo) error {
} }
if err = cmd.Start(); err != nil { if err = cmd.Start(); err != nil {
_ = pipe.Close()
return err return err
} }
pid := pm.Add(desc, cancel)
_, err = msg.WriteTo(pipe) _, err = msg.WriteTo(pipe)
// we MUST close the pipe or sendmail will hang waiting for more of the message // we MUST close the pipe or sendmail will hang waiting for more of the message
// Also we should wait on our sendmail command even if something fails // Also we should wait on our sendmail command even if something fails
closeError = pipe.Close() closeError = pipe.Close()
waitError = cmd.Wait() waitError = cmd.Wait()
pm.Remove(pid)
if err != nil { if err != nil {
return err return err
} else if closeError != nil { } else if closeError != nil {

View File

@ -18,6 +18,7 @@ import (
"code.gitea.io/gitea/modules/lfs" "code.gitea.io/gitea/modules/lfs"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/notification" "code.gitea.io/gitea/modules/notification"
"code.gitea.io/gitea/modules/process"
repo_module "code.gitea.io/gitea/modules/repository" repo_module "code.gitea.io/gitea/modules/repository"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/timeutil" "code.gitea.io/gitea/modules/timeutil"
@ -192,7 +193,7 @@ func runSync(ctx context.Context, m *models.Mirror) ([]*mirrorSyncResult, bool)
} }
gitArgs = append(gitArgs, m.GetRemoteName()) gitArgs = append(gitArgs, m.GetRemoteName())
remoteAddr, remoteErr := git.GetRemoteAddress(repoPath, m.GetRemoteName()) remoteAddr, remoteErr := git.GetRemoteAddress(ctx, repoPath, m.GetRemoteName())
if remoteErr != nil { if remoteErr != nil {
log.Error("GetRemoteAddress Error %v", remoteErr) log.Error("GetRemoteAddress Error %v", remoteErr)
} }
@ -287,7 +288,7 @@ func runSync(ctx context.Context, m *models.Mirror) ([]*mirrorSyncResult, bool)
// sanitize the output, since it may contain the remote address, which may // sanitize the output, since it may contain the remote address, which may
// contain a password // contain a password
remoteAddr, remoteErr := git.GetRemoteAddress(wikiPath, m.GetRemoteName()) remoteAddr, remoteErr := git.GetRemoteAddress(ctx, wikiPath, m.GetRemoteName())
if remoteErr != nil { if remoteErr != nil {
log.Error("GetRemoteAddress Error %v", remoteErr) log.Error("GetRemoteAddress Error %v", remoteErr)
} }
@ -367,6 +368,9 @@ func SyncPullMirror(ctx context.Context, repoID int64) bool {
return false return false
} }
ctx, _, finished := process.GetManager().AddContext(ctx, fmt.Sprintf("Syncing Mirror %s/%s", m.Repo.OwnerName, m.Repo.Name))
defer finished()
log.Trace("SyncMirrors [repo: %-v]: Running Sync", m.Repo) log.Trace("SyncMirrors [repo: %-v]: Running Sync", m.Repo)
results, ok := runSync(ctx, m) results, ok := runSync(ctx, m)
if !ok { if !ok {
@ -385,7 +389,7 @@ func SyncPullMirror(ctx context.Context, repoID int64) bool {
log.Trace("SyncMirrors [repo: %-v]: no branches updated", m.Repo) log.Trace("SyncMirrors [repo: %-v]: no branches updated", m.Repo)
} else { } else {
log.Trace("SyncMirrors [repo: %-v]: %d branches updated", m.Repo, len(results)) log.Trace("SyncMirrors [repo: %-v]: %d branches updated", m.Repo, len(results))
gitRepo, err = git.OpenRepository(m.Repo.RepoPath()) gitRepo, err = git.OpenRepositoryCtx(ctx, m.Repo.RepoPath())
if err != nil { if err != nil {
log.Error("OpenRepository [%d]: %v", m.RepoID, err) log.Error("OpenRepository [%d]: %v", m.RepoID, err)
return false return false

View File

@ -7,6 +7,7 @@ package mirror
import ( import (
"context" "context"
"errors" "errors"
"fmt"
"io" "io"
"regexp" "regexp"
"time" "time"
@ -15,6 +16,7 @@ import (
"code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/lfs" "code.gitea.io/gitea/modules/lfs"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/process"
"code.gitea.io/gitea/modules/repository" "code.gitea.io/gitea/modules/repository"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/timeutil" "code.gitea.io/gitea/modules/timeutil"
@ -92,6 +94,9 @@ func SyncPushMirror(ctx context.Context, mirrorID int64) bool {
m.LastError = "" m.LastError = ""
ctx, _, finished := process.GetManager().AddContext(ctx, fmt.Sprintf("Syncing PushMirror %s/%s to %s", m.Repo.OwnerName, m.Repo.Name, m.RemoteName))
defer finished()
log.Trace("SyncPushMirror [mirror: %d][repo: %-v]: Running Sync", m.ID, m.Repo) log.Trace("SyncPushMirror [mirror: %d][repo: %-v]: Running Sync", m.ID, m.Repo)
err = runPushSync(ctx, m) err = runPushSync(ctx, m)
if err != nil { if err != nil {
@ -116,7 +121,7 @@ func runPushSync(ctx context.Context, m *models.PushMirror) error {
timeout := time.Duration(setting.Git.Timeout.Mirror) * time.Second timeout := time.Duration(setting.Git.Timeout.Mirror) * time.Second
performPush := func(path string) error { performPush := func(path string) error {
remoteAddr, err := git.GetRemoteAddress(path, m.RemoteName) remoteAddr, err := git.GetRemoteAddress(ctx, path, m.RemoteName)
if err != nil { if err != nil {
log.Error("GetRemoteAddress(%s) Error %v", path, err) log.Error("GetRemoteAddress(%s) Error %v", path, err)
return errors.New("Unexpected error") return errors.New("Unexpected error")
@ -125,7 +130,7 @@ func runPushSync(ctx context.Context, m *models.PushMirror) error {
if setting.LFS.StartServer { if setting.LFS.StartServer {
log.Trace("SyncMirrors [repo: %-v]: syncing LFS objects...", m.Repo) log.Trace("SyncMirrors [repo: %-v]: syncing LFS objects...", m.Repo)
gitRepo, err := git.OpenRepository(path) gitRepo, err := git.OpenRepositoryCtx(ctx, path)
if err != nil { if err != nil {
log.Error("OpenRepository: %v", err) log.Error("OpenRepository: %v", err)
return errors.New("Unexpected error") return errors.New("Unexpected error")
@ -141,7 +146,7 @@ func runPushSync(ctx context.Context, m *models.PushMirror) error {
log.Trace("Pushing %s mirror[%d] remote %s", path, m.ID, m.RemoteName) log.Trace("Pushing %s mirror[%d] remote %s", path, m.ID, m.RemoteName)
if err := git.Push(path, git.PushOptions{ if err := git.Push(ctx, path, git.PushOptions{
Remote: m.RemoteName, Remote: m.RemoteName,
Force: true, Force: true,
Mirror: true, Mirror: true,
@ -162,7 +167,7 @@ func runPushSync(ctx context.Context, m *models.PushMirror) error {
if m.Repo.HasWiki() { if m.Repo.HasWiki() {
wikiPath := m.Repo.WikiPath() wikiPath := m.Repo.WikiPath()
_, err := git.GetRemoteAddress(wikiPath, m.RemoteName) _, err := git.GetRemoteAddress(ctx, wikiPath, m.RemoteName)
if err == nil { if err == nil {
err := performPush(wikiPath) err := performPush(wikiPath)
if err != nil { if err != nil {

View File

@ -112,7 +112,7 @@ func GetPullRequestCommitStatusState(pr *models.PullRequest) (structs.CommitStat
if pr.Flow == models.PullRequestFlowGithub && !headGitRepo.IsBranchExist(pr.HeadBranch) { if pr.Flow == models.PullRequestFlowGithub && !headGitRepo.IsBranchExist(pr.HeadBranch) {
return "", errors.New("Head branch does not exist, can not merge") return "", errors.New("Head branch does not exist, can not merge")
} }
if pr.Flow == models.PullRequestFlowAGit && !git.IsReferenceExist(headGitRepo.Path, pr.GetGitRefName()) { if pr.Flow == models.PullRequestFlowAGit && !git.IsReferenceExist(headGitRepo.Ctx, headGitRepo.Path, pr.GetGitRefName()) {
return "", errors.New("Head branch does not exist, can not merge") return "", errors.New("Head branch does not exist, can not merge")
} }

View File

@ -313,7 +313,7 @@ func AddTestPullRequestTask(doer *user_model.User, repoID int64, branch string,
for _, pr := range prs { for _, pr := range prs {
divergence, err := GetDiverging(pr) divergence, err := GetDiverging(pr)
if err != nil { if err != nil {
if models.IsErrBranchDoesNotExist(err) && !git.IsBranchExist(pr.HeadRepo.RepoPath(), pr.HeadBranch) { if models.IsErrBranchDoesNotExist(err) && !git.IsBranchExist(ctx, pr.HeadRepo.RepoPath(), pr.HeadBranch) {
log.Warn("Cannot test PR %s/%d: head_branch %s no longer exists", pr.BaseRepo.Name, pr.IssueID, pr.HeadBranch) log.Warn("Cannot test PR %s/%d: head_branch %s no longer exists", pr.BaseRepo.Name, pr.IssueID, pr.HeadBranch)
} else { } else {
log.Error("GetDiverging: %v", err) log.Error("GetDiverging: %v", err)
@ -431,7 +431,7 @@ func pushToBaseRepoHelper(pr *models.PullRequest, prefixHeadBranch string) (err
gitRefName := pr.GetGitRefName() gitRefName := pr.GetGitRefName()
if err := git.Push(headRepoPath, git.PushOptions{ if err := git.Push(git.DefaultContext, headRepoPath, git.PushOptions{
Remote: baseRepoPath, Remote: baseRepoPath,
Branch: prefixHeadBranch + pr.HeadBranch + ":" + gitRefName, Branch: prefixHeadBranch + pr.HeadBranch + ":" + gitRefName,
Force: true, Force: true,

View File

@ -152,7 +152,7 @@ func createTemporaryRepo(pr *models.PullRequest) (string, error) {
if err := models.RemoveTemporaryPath(tmpBasePath); err != nil { if err := models.RemoveTemporaryPath(tmpBasePath); err != nil {
log.Error("CreateTempRepo: RemoveTemporaryPath: %s", err) log.Error("CreateTempRepo: RemoveTemporaryPath: %s", err)
} }
if !git.IsBranchExist(pr.HeadRepo.RepoPath(), pr.HeadBranch) { if !git.IsBranchExist(git.DefaultContext, pr.HeadRepo.RepoPath(), pr.HeadBranch) {
return "", models.ErrBranchDoesNotExist{ return "", models.ErrBranchDoesNotExist{
BranchName: pr.HeadBranch, BranchName: pr.HeadBranch,
} }

View File

@ -24,13 +24,13 @@ func CreateNewBranch(doer *user_model.User, repo *models.Repository, oldBranchNa
return err return err
} }
if !git.IsBranchExist(repo.RepoPath(), oldBranchName) { if !git.IsBranchExist(git.DefaultContext, repo.RepoPath(), oldBranchName) {
return models.ErrBranchDoesNotExist{ return models.ErrBranchDoesNotExist{
BranchName: oldBranchName, BranchName: oldBranchName,
} }
} }
if err := git.Push(repo.RepoPath(), git.PushOptions{ if err := git.Push(git.DefaultContext, repo.RepoPath(), git.PushOptions{
Remote: repo.RepoPath(), Remote: repo.RepoPath(),
Branch: fmt.Sprintf("%s:%s%s", oldBranchName, git.BranchPrefix, branchName), Branch: fmt.Sprintf("%s:%s%s", oldBranchName, git.BranchPrefix, branchName),
Env: models.PushingEnvironment(doer, repo), Env: models.PushingEnvironment(doer, repo),
@ -106,7 +106,7 @@ func CreateNewBranchFromCommit(doer *user_model.User, repo *models.Repository, c
return err return err
} }
if err := git.Push(repo.RepoPath(), git.PushOptions{ if err := git.Push(git.DefaultContext, repo.RepoPath(), git.PushOptions{
Remote: repo.RepoPath(), Remote: repo.RepoPath(),
Branch: fmt.Sprintf("%s:%s%s", commit, git.BranchPrefix, branchName), Branch: fmt.Sprintf("%s:%s%s", commit, git.BranchPrefix, branchName),
Env: models.PushingEnvironment(doer, repo), Env: models.PushingEnvironment(doer, repo),

View File

@ -264,7 +264,7 @@ func (t *TemporaryUploadRepository) CommitTreeWithDate(author, committer *user_m
func (t *TemporaryUploadRepository) Push(doer *user_model.User, commitHash string, branch string) error { func (t *TemporaryUploadRepository) Push(doer *user_model.User, commitHash string, branch string) error {
// Because calls hooks we need to pass in the environment // Because calls hooks we need to pass in the environment
env := models.PushingEnvironment(doer, t.repo) env := models.PushingEnvironment(doer, t.repo)
if err := git.Push(t.basePath, git.PushOptions{ if err := git.Push(t.gitRepo.Ctx, t.basePath, git.PushOptions{
Remote: t.repo.RepoPath(), Remote: t.repo.RepoPath(),
Branch: strings.TrimSpace(commitHash) + ":refs/heads/" + strings.TrimSpace(branch), Branch: strings.TrimSpace(commitHash) + ":refs/heads/" + strings.TrimSpace(branch),
Env: env, Env: env,

View File

@ -5,7 +5,6 @@
package task package task
import ( import (
"context"
"errors" "errors"
"fmt" "fmt"
"strings" "strings"
@ -99,11 +98,9 @@ func runMigrateTask(t *models.Task) (err error) {
opts.MigrateToRepoID = t.RepoID opts.MigrateToRepoID = t.RepoID
ctx, cancel := context.WithCancel(graceful.GetManager().ShutdownContext())
defer cancel()
pm := process.GetManager() pm := process.GetManager()
pid := pm.Add(fmt.Sprintf("MigrateTask: %s/%s", t.Owner.Name, opts.RepoName), cancel) ctx, _, finished := pm.AddContext(graceful.GetManager().ShutdownContext(), fmt.Sprintf("MigrateTask: %s/%s", t.Owner.Name, opts.RepoName))
defer pm.Remove(pid) defer finished()
t.StartTime = timeutil.TimeStampNow() t.StartTime = timeutil.TimeStampNow()
t.Status = structs.TaskStatusRunning t.Status = structs.TaskStatusRunning

View File

@ -128,7 +128,7 @@ func updateWikiPage(doer *user_model.User, repo *models.Repository, oldWikiName,
return fmt.Errorf("InitWiki: %v", err) return fmt.Errorf("InitWiki: %v", err)
} }
hasMasterBranch := git.IsBranchExist(repo.WikiPath(), "master") hasMasterBranch := git.IsBranchExist(git.DefaultContext, repo.WikiPath(), "master")
basePath, err := models.CreateTemporaryPath("update-wiki") basePath, err := models.CreateTemporaryPath("update-wiki")
if err != nil { if err != nil {
@ -243,7 +243,7 @@ func updateWikiPage(doer *user_model.User, repo *models.Repository, oldWikiName,
return err return err
} }
if err := git.Push(basePath, git.PushOptions{ if err := git.Push(gitRepo.Ctx, basePath, git.PushOptions{
Remote: "origin", Remote: "origin",
Branch: fmt.Sprintf("%s:%s%s", commitHash.String(), git.BranchPrefix, "master"), Branch: fmt.Sprintf("%s:%s%s", commitHash.String(), git.BranchPrefix, "master"),
Env: models.FullPushingEnvironment( Env: models.FullPushingEnvironment(
@ -357,7 +357,7 @@ func DeleteWikiPage(doer *user_model.User, repo *models.Repository, wikiName str
return err return err
} }
if err := git.Push(basePath, git.PushOptions{ if err := git.Push(gitRepo.Ctx, basePath, git.PushOptions{
Remote: "origin", Remote: "origin",
Branch: fmt.Sprintf("%s:%s%s", commitHash.String(), git.BranchPrefix, "master"), Branch: fmt.Sprintf("%s:%s%s", commitHash.String(), git.BranchPrefix, "master"),
Env: models.PushingEnvironment(doer, repo), Env: models.PushingEnvironment(doer, repo),

View File

@ -65,33 +65,7 @@
</table> </table>
</div> </div>
<h4 class="ui top attached header"> {{template "admin/process" .}}
{{.i18n.Tr "admin.monitor.process"}}
</h4>
<div class="ui attached table segment">
<table class="ui very basic striped table">
<thead>
<tr>
<th>Pid</th>
<th>{{.i18n.Tr "admin.monitor.desc"}}</th>
<th>{{.i18n.Tr "admin.monitor.start"}}</th>
<th>{{.i18n.Tr "admin.monitor.execute_time"}}</th>
<th></th>
</tr>
</thead>
<tbody>
{{range .Processes}}
<tr>
<td>{{.PID}}</td>
<td>{{.Description}}</td>
<td>{{DateFmtLong .Start}}</td>
<td>{{TimeSince .Start $.Lang}}</td>
<td><a class="delete-button" href="" data-url="{{$.Link}}/cancel/{{.PID}}" data-id="{{.PID}}" data-name="{{.Description}}">{{svg "octicon-trash" 16 "text-red"}}</a></td>
</tr>
{{end}}
</tbody>
</table>
</div>
</div> </div>
</div> </div>
<div class="ui small basic delete modal"> <div class="ui small basic delete modal">

View File

@ -0,0 +1,20 @@
<div class="item">
<div class="df ac">
<div class="content f1">
<div class="header">{{.Process.Description}}</div>
<div class="description"><span title="{{DateFmtLong .Process.Start}}">{{TimeSince .Process.Start .root.Lang}}</span></div>
</div>
<div>
<a class="delete-button icon" href="" data-url="{{.root.Link}}/cancel/{{.Process.PID}}" data-id="{{.Process.PID}}" data-name="{{.Process.Description}}">{{svg "octicon-trash" 16 "text-red"}}</a>
</div>
</div>
{{$children := .Process.Children}}
{{if $children}}
<div class="divided list">
{{range $children}}
{{template "admin/process-row" dict "Process" . "root" $.root}}
{{end}}
</div>
{{end}}
</div>

View File

@ -0,0 +1,10 @@
<h4 class="ui top attached header">
{{.i18n.Tr "admin.monitor.process"}}
</h4>
<div class="ui attached segment">
<div class="ui relaxed divided list">
{{range .Processes}}
{{template "admin/process-row" dict "Process" . "root" $}}
{{end}}
</div>
</div>