aboutsummaryrefslogtreecommitdiffstats
path: root/modules
diff options
context:
space:
mode:
Diffstat (limited to 'modules')
-rw-r--r--modules/context/repo.go4
-rw-r--r--modules/git/batch_reader.go18
-rw-r--r--modules/git/blame.go39
-rw-r--r--modules/git/blob_nogogit.go4
-rw-r--r--modules/git/command.go16
-rw-r--r--modules/git/commit_info_nogogit.go4
-rw-r--r--modules/git/diff.go6
-rw-r--r--modules/git/pipeline/lfs_nogogit.go2
-rw-r--r--modules/git/remote.go11
-rw-r--r--modules/git/repo.go4
-rw-r--r--modules/git/repo_attribute.go2
-rw-r--r--modules/git/repo_base_gogit.go9
-rw-r--r--modules/git/repo_base_nogogit.go20
-rw-r--r--modules/git/repo_blame.go4
-rw-r--r--modules/git/repo_branch.go25
-rw-r--r--modules/git/repo_branch_nogogit.go11
-rw-r--r--modules/git/repo_commit.go42
-rw-r--r--modules/git/repo_commit_gogit.go2
-rw-r--r--modules/git/repo_commit_nogogit.go10
-rw-r--r--modules/git/repo_compare.go20
-rw-r--r--modules/git/repo_gpg.go8
-rw-r--r--modules/git/repo_index.go14
-rw-r--r--modules/git/repo_language_stats_nogogit.go10
-rw-r--r--modules/git/repo_object.go2
-rw-r--r--modules/git/repo_ref_nogogit.go2
-rw-r--r--modules/git/repo_stats.go4
-rw-r--r--modules/git/repo_tag.go19
-rw-r--r--modules/git/repo_tag_nogogit.go2
-rw-r--r--modules/git/repo_tree.go2
-rw-r--r--modules/git/repo_tree_gogit.go2
-rw-r--r--modules/git/repo_tree_nogogit.go2
-rw-r--r--modules/git/tree_nogogit.go2
-rw-r--r--modules/indexer/code/bleve.go2
-rw-r--r--modules/indexer/code/elastic_search.go2
-rw-r--r--modules/indexer/stats/db.go9
-rw-r--r--modules/markup/external/external.go8
-rw-r--r--modules/process/context.go69
-rw-r--r--modules/process/manager.go183
-rw-r--r--modules/process/manager_test.go64
-rw-r--r--modules/process/process.go66
-rw-r--r--modules/templates/helper.go2
41 files changed, 503 insertions, 224 deletions
diff --git a/modules/context/repo.go b/modules/context/repo.go
index 7feaad4ccd..159fd07d9d 100644
--- a/modules/context/repo.go
+++ b/modules/context/repo.go
@@ -534,7 +534,7 @@ func RepoAssignment(ctx *Context) (cancel context.CancelFunc) {
return
}
- gitRepo, err := git.OpenRepository(models.RepoPath(userName, repoName))
+ gitRepo, err := git.OpenRepositoryCtx(ctx, models.RepoPath(userName, repoName))
if err != nil {
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)
@@ -792,7 +792,7 @@ func RepoRefByType(refType RepoRefType, ignoreNotExistErr ...bool) func(*Context
if ctx.Repo.GitRepo == nil {
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 {
ctx.ServerError("RepoRef Invalid repo "+repoPath, err)
return
diff --git a/modules/git/batch_reader.go b/modules/git/batch_reader.go
index 8e3c23251b..71045adbc9 100644
--- a/modules/git/batch_reader.go
+++ b/modules/git/batch_reader.go
@@ -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
-func CatFileBatchCheck(repoPath string) (WriteCloserError, *bufio.Reader, func()) {
+func CatFileBatchCheck(ctx context.Context, repoPath string) (WriteCloserError, *bufio.Reader, func()) {
batchStdinReader, batchStdinWriter := io.Pipe()
batchStdoutReader, batchStdoutWriter := io.Pipe()
- ctx, ctxCancel := context.WithCancel(DefaultContext)
+ ctx, ctxCancel := context.WithCancel(ctx)
closed := make(chan struct{})
cancel := func() {
- _ = batchStdinReader.Close()
- _ = batchStdinWriter.Close()
- _ = batchStdoutReader.Close()
- _ = batchStdoutWriter.Close()
ctxCancel()
+ _ = batchStdoutReader.Close()
+ _ = batchStdinWriter.Close()
<-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
-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.
// so let's create a batch stdin and stdout
batchStdinReader, batchStdinWriter := io.Pipe()
batchStdoutReader, batchStdoutWriter := nio.Pipe(buffer.New(32 * 1024))
- ctx, ctxCancel := context.WithCancel(DefaultContext)
+ ctx, ctxCancel := context.WithCancel(ctx)
closed := make(chan struct{})
cancel := func() {
- _ = batchStdinReader.Close()
+ ctxCancel()
_ = batchStdinWriter.Close()
_ = batchStdoutReader.Close()
- _ = batchStdoutWriter.Close()
- ctxCancel()
<-closed
}
diff --git a/modules/git/blame.go b/modules/git/blame.go
index fcbf183981..b30124594d 100644
--- a/modules/git/blame.go
+++ b/modules/git/blame.go
@@ -24,12 +24,12 @@ type BlamePart struct {
// BlameReader returns part of file blame one by one
type BlameReader struct {
- cmd *exec.Cmd
- pid int64
- output io.ReadCloser
- reader *bufio.Reader
- lastSha *string
- cancel context.CancelFunc
+ cmd *exec.Cmd
+ output io.ReadCloser
+ reader *bufio.Reader
+ lastSha *string
+ 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})")
@@ -100,8 +100,8 @@ func (r *BlameReader) NextPart() (*BlamePart, error) {
// Close BlameReader - don't run NextPart after invoking that
func (r *BlameReader) Close() error {
- defer process.GetManager().Remove(r.pid)
- r.cancel()
+ defer r.finished() // Only remove the process from the process table when the underlying command is closed
+ r.cancel() // However, first cancel our own context early
_ = r.output.Close()
@@ -114,7 +114,7 @@ func (r *BlameReader) Close() error {
// CreateBlameReader creates reader for given repository, commit and file
func CreateBlameReader(ctx context.Context, repoPath, commitID, file string) (*BlameReader, error) {
- gitRepo, err := OpenRepository(repoPath)
+ gitRepo, err := OpenRepositoryCtx(ctx, repoPath)
if err != nil {
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) {
// 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.Dir = dir
cmd.Stderr = os.Stderr
stdout, err := cmd.StdoutPipe()
if err != nil {
- defer cancel()
+ defer finished()
return nil, fmt.Errorf("StdoutPipe: %v", err)
}
if err = cmd.Start(); err != nil {
- defer cancel()
+ defer finished()
+ _ = stdout.Close()
return nil, fmt.Errorf("Start: %v", err)
}
- pid := process.GetManager().Add(fmt.Sprintf("GetBlame [repo_path: %s]", dir), cancel)
-
reader := bufio.NewReader(stdout)
return &BlameReader{
- cmd,
- pid,
- stdout,
- reader,
- nil,
- cancel,
+ cmd: cmd,
+ output: stdout,
+ reader: reader,
+ cancel: cancel,
+ finished: finished,
}, nil
}
diff --git a/modules/git/blob_nogogit.go b/modules/git/blob_nogogit.go
index 65a73ebc52..aabf1b34ad 100644
--- a/modules/git/blob_nogogit.go
+++ b/modules/git/blob_nogogit.go
@@ -29,7 +29,7 @@ type Blob struct {
// DataAsync gets a ReadCloser for the contents of a blob without reading it all.
// Calling the Close function on the result will discard all unread output.
func (b *Blob) DataAsync() (io.ReadCloser, error) {
- wr, rd, cancel := b.repo.CatFileBatch()
+ wr, rd, cancel := b.repo.CatFileBatch(b.repo.Ctx)
_, err := wr.Write([]byte(b.ID.String() + "\n"))
if err != nil {
@@ -67,7 +67,7 @@ func (b *Blob) Size() int64 {
return b.size
}
- wr, rd, cancel := b.repo.CatFileBatchCheck()
+ wr, rd, cancel := b.repo.CatFileBatchCheck(b.repo.Ctx)
defer cancel()
_, err := wr.Write([]byte(b.ID.String() + "\n"))
if err != nil {
diff --git a/modules/git/command.go b/modules/git/command.go
index e7496f072c..3cf85c2d85 100644
--- a/modules/git/command.go
+++ b/modules/git/command.go
@@ -143,8 +143,13 @@ func (c *Command) RunWithContext(rc *RunContext) error {
log.Debug("%s: %v", rc.Dir, c)
}
- ctx, cancel := context.WithTimeout(c.parentContext, rc.Timeout)
- defer cancel()
+ desc := c.desc
+ 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...)
if rc.Env == nil {
@@ -172,13 +177,6 @@ func (c *Command) RunWithContext(rc *RunContext) error {
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 {
err := rc.PipelineFunc(ctx, cancel)
if err != nil {
diff --git a/modules/git/commit_info_nogogit.go b/modules/git/commit_info_nogogit.go
index 261252dab1..b58c1885b6 100644
--- a/modules/git/commit_info_nogogit.go
+++ b/modules/git/commit_info_nogogit.go
@@ -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) {
- wr, rd, cancel := cache.repo.CatFileBatch()
+ wr, rd, cancel := cache.repo.CatFileBatch(ctx)
defer cancel()
var unHitEntryPaths []string
@@ -129,7 +129,7 @@ func GetLastCommitForPaths(ctx context.Context, cache *LastCommitCache, commit *
return nil, err
}
- batchStdinWriter, batchReader, cancel := commit.repo.CatFileBatch()
+ batchStdinWriter, batchReader, cancel := commit.repo.CatFileBatch(ctx)
defer cancel()
commitsMap := map[string]*Commit{}
diff --git a/modules/git/diff.go b/modules/git/diff.go
index b473dc73f6..3a82cda1ce 100644
--- a/modules/git/diff.go
+++ b/modules/git/diff.go
@@ -56,8 +56,8 @@ func GetRepoRawDiffForFile(repo *Repository, startCommit, endCommit string, diff
fileArgs = append(fileArgs, "--", file)
}
// FIXME: graceful: These commands should have a timeout
- ctx, cancel := context.WithCancel(DefaultContext)
- defer cancel()
+ ctx, _, finished := process.GetManager().AddContext(repo.Ctx, fmt.Sprintf("GetRawDiffForFile: [repo_path: %s]", repo.Path))
+ defer finished()
var cmd *exec.Cmd
switch diffType {
@@ -90,8 +90,6 @@ func GetRepoRawDiffForFile(repo *Repository, startCommit, endCommit string, diff
cmd.Dir = repo.Path
cmd.Stdout = writer
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 {
return fmt.Errorf("Run: %v - %s", err, stderr)
diff --git a/modules/git/pipeline/lfs_nogogit.go b/modules/git/pipeline/lfs_nogogit.go
index 4aa21dd05f..1352aa7662 100644
--- a/modules/git/pipeline/lfs_nogogit.go
+++ b/modules/git/pipeline/lfs_nogogit.go
@@ -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.
// so let's create a batch stdin and stdout
- batchStdinWriter, batchReader, cancel := repo.CatFileBatch()
+ batchStdinWriter, batchReader, cancel := repo.CatFileBatch(repo.Ctx)
defer cancel()
// We'll use a scanner for the revList because it's simpler than a bufio.Reader
diff --git a/modules/git/remote.go b/modules/git/remote.go
index 7ba2b35a5e..5ed02300d4 100644
--- a/modules/git/remote.go
+++ b/modules/git/remote.go
@@ -4,19 +4,22 @@
package git
-import "net/url"
+import (
+ "context"
+ "net/url"
+)
// 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()
if err != nil {
return nil, err
}
var cmd *Command
if CheckGitVersionAtLeast("2.7") == nil {
- cmd = NewCommand("remote", "get-url", remoteName)
+ cmd = NewCommandContext(ctx, "remote", "get-url", remoteName)
} else {
- cmd = NewCommand("config", "--get", "remote."+remoteName+".url")
+ cmd = NewCommandContext(ctx, "config", "--get", "remote."+remoteName+".url")
}
result, err := cmd.RunInDir(repoPath)
diff --git a/modules/git/repo.go b/modules/git/repo.go
index 89af7aa9e1..3ff2b6fe2d 100644
--- a/modules/git/repo.go
+++ b/modules/git/repo.go
@@ -211,8 +211,8 @@ type PushOptions struct {
}
// Push pushs local commits to given remote branch.
-func Push(repoPath string, opts PushOptions) error {
- cmd := NewCommand("push")
+func Push(ctx context.Context, repoPath string, opts PushOptions) error {
+ cmd := NewCommandContext(ctx, "push")
if opts.Force {
cmd.AddArguments("-f")
}
diff --git a/modules/git/repo_attribute.go b/modules/git/repo_attribute.go
index 88fb7810a6..0bb550bb4b 100644
--- a/modules/git/repo_attribute.go
+++ b/modules/git/repo_attribute.go
@@ -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 {
return nil, fmt.Errorf("failed to run check-attr: %v\n%s\n%s", err, stdOut.String(), stdErr.String())
diff --git a/modules/git/repo_base_gogit.go b/modules/git/repo_base_gogit.go
index afa5383b11..7299526562 100644
--- a/modules/git/repo_base_gogit.go
+++ b/modules/git/repo_base_gogit.go
@@ -9,6 +9,7 @@
package git
import (
+ "context"
"errors"
"path/filepath"
@@ -30,10 +31,17 @@ type Repository struct {
gogitRepo *gogit.Repository
gogitStorage *filesystem.Storage
gpgSettings *GPGSettings
+
+ Ctx context.Context
}
// OpenRepository opens the repository at the given path.
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)
if err != nil {
return nil, err
@@ -60,6 +68,7 @@ func OpenRepository(repoPath string) (*Repository, error) {
gogitRepo: gogitRepo,
gogitStorage: storage,
tagCache: newObjectCache(),
+ Ctx: ctx,
}, nil
}
diff --git a/modules/git/repo_base_nogogit.go b/modules/git/repo_base_nogogit.go
index 22c4dfdcb3..14a6cacb44 100644
--- a/modules/git/repo_base_nogogit.go
+++ b/modules/git/repo_base_nogogit.go
@@ -32,10 +32,17 @@ type Repository struct {
checkCancel context.CancelFunc
checkReader *bufio.Reader
checkWriter WriteCloserError
+
+ Ctx context.Context
}
// OpenRepository opens the repository at the given path.
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)
if err != nil {
return nil, err
@@ -46,28 +53,29 @@ func OpenRepository(repoPath string) (*Repository, error) {
repo := &Repository{
Path: repoPath,
tagCache: newObjectCache(),
+ Ctx: ctx,
}
- repo.batchWriter, repo.batchReader, repo.batchCancel = CatFileBatch(repoPath)
- repo.checkWriter, repo.checkReader, repo.checkCancel = CatFileBatchCheck(repo.Path)
+ repo.batchWriter, repo.batchReader, repo.batchCancel = CatFileBatch(ctx, repoPath)
+ repo.checkWriter, repo.checkReader, repo.checkCancel = CatFileBatchCheck(ctx, repo.Path)
return repo, nil
}
// 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 {
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() {}
}
// 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 {
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() {}
}
diff --git a/modules/git/repo_blame.go b/modules/git/repo_blame.go
index 5c023554f1..4ca05e3ba4 100644
--- a/modules/git/repo_blame.go
+++ b/modules/git/repo_blame.go
@@ -8,12 +8,12 @@ import "fmt"
// FileBlame return the Blame object of file
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
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 {
return nil, err
}
diff --git a/modules/git/repo_branch.go b/modules/git/repo_branch.go
index 96f692826e..98b1bc8ae7 100644
--- a/modules/git/repo_branch.go
+++ b/modules/git/repo_branch.go
@@ -6,6 +6,7 @@
package git
import (
+ "context"
"fmt"
"strings"
)
@@ -22,14 +23,14 @@ const PullRequestPrefix = "refs/for/"
// TODO: /refs/for-review for suggest change interface
// IsReferenceExist returns true if given reference exists in the repository.
-func IsReferenceExist(repoPath, name string) bool {
- _, err := NewCommand("show-ref", "--verify", "--", name).RunInDir(repoPath)
+func IsReferenceExist(ctx context.Context, repoPath, name string) bool {
+ _, err := NewCommandContext(ctx, "show-ref", "--verify", "--", name).RunInDir(repoPath)
return err == nil
}
// IsBranchExist returns true if given branch exists in the repository.
-func IsBranchExist(repoPath, name string) bool {
- return IsReferenceExist(repoPath, BranchPrefix+name)
+func IsBranchExist(ctx context.Context, repoPath, name string) bool {
+ return IsReferenceExist(ctx, repoPath, BranchPrefix+name)
}
// Branch represents a Git branch.
@@ -45,7 +46,7 @@ func (repo *Repository) GetHEADBranch() (*Branch, error) {
if repo == nil {
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 {
return nil, err
}
@@ -64,13 +65,13 @@ func (repo *Repository) GetHEADBranch() (*Branch, error) {
// SetDefaultBranch sets default branch of repository.
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
}
// GetDefaultBranch gets default branch of repository.
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
@@ -118,7 +119,7 @@ type DeleteBranchOptions struct {
// DeleteBranch delete a branch by name on repository.
func (repo *Repository) DeleteBranch(name string, opts DeleteBranchOptions) error {
- cmd := NewCommand("branch")
+ cmd := NewCommandContext(repo.Ctx, "branch")
if opts.Force {
cmd.AddArguments("-D")
@@ -134,7 +135,7 @@ func (repo *Repository) DeleteBranch(name string, opts DeleteBranchOptions) erro
// CreateBranch create a new branch
func (repo *Repository) CreateBranch(branch, oldbranchOrCommit string) error {
- cmd := NewCommand("branch")
+ cmd := NewCommandContext(repo.Ctx, "branch")
cmd.AddArguments("--", branch, oldbranchOrCommit)
_, err := cmd.RunInDir(repo.Path)
@@ -144,7 +145,7 @@ func (repo *Repository) CreateBranch(branch, oldbranchOrCommit string) error {
// AddRemote adds a new remote to repository.
func (repo *Repository) AddRemote(name, url string, fetch bool) error {
- cmd := NewCommand("remote", "add")
+ cmd := NewCommandContext(repo.Ctx, "remote", "add")
if fetch {
cmd.AddArguments("-f")
}
@@ -156,7 +157,7 @@ func (repo *Repository) AddRemote(name, url string, fetch bool) error {
// RemoveRemote removes a remote from repository.
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
}
@@ -167,6 +168,6 @@ func (branch *Branch) GetCommit() (*Commit, error) {
// RenameBranch rename a branch
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
}
diff --git a/modules/git/repo_branch_nogogit.go b/modules/git/repo_branch_nogogit.go
index 666ca81c1e..1928c7515b 100644
--- a/modules/git/repo_branch_nogogit.go
+++ b/modules/git/repo_branch_nogogit.go
@@ -11,6 +11,7 @@ package git
import (
"bufio"
"bytes"
+ "context"
"io"
"strings"
@@ -23,7 +24,7 @@ func (repo *Repository) IsObjectExist(name string) bool {
return false
}
- wr, rd, cancel := repo.CatFileBatchCheck()
+ wr, rd, cancel := repo.CatFileBatchCheck(repo.Ctx)
defer cancel()
_, err := wr.Write([]byte(name + "\n"))
if err != nil {
@@ -40,7 +41,7 @@ func (repo *Repository) IsReferenceExist(name string) bool {
return false
}
- wr, rd, cancel := repo.CatFileBatchCheck()
+ wr, rd, cancel := repo.CatFileBatchCheck(repo.Ctx)
defer cancel()
_, err := wr.Write([]byte(name + "\n"))
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
// returning at most limit branches, or all branches if limit is 0.
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
-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()
defer func() {
_ = stdoutReader.Close()
@@ -76,7 +77,7 @@ func callShowRef(repoPath, prefix, arg string, skip, limit int) (branchNames []s
go func() {
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 stderrBuilder.Len() == 0 {
_ = stdoutWriter.Close()
diff --git a/modules/git/repo_commit.go b/modules/git/repo_commit.go
index 25060f56da..3afabac27b 100644
--- a/modules/git/repo_commit.go
+++ b/modules/git/repo_commit.go
@@ -58,7 +58,7 @@ func (repo *Repository) getCommitByPathWithID(id SHA1, relpath string) (*Commit,
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 {
return nil, err
}
@@ -73,7 +73,7 @@ func (repo *Repository) getCommitByPathWithID(id SHA1, relpath string) (*Commit,
// GetCommitByPath returns the last commit of relative path.
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 {
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) {
- 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)
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) {
// 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
args := []string{"-i"}
@@ -155,7 +155,7 @@ func (repo *Repository) searchCommits(id SHA1, opts SearchCommitsOptions) ([]*Co
// ignore anything below 4 characters as too unspecific
if len(v) >= 4 {
// 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
hashCmd.AddArguments(args...)
// 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) {
- 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 {
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
// You must ensure that id1 and id2 are valid commit ids.
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 {
return false, err
}
@@ -209,7 +209,7 @@ func (repo *Repository) CommitsByFileAndRange(revision, file string, page int) (
}()
go func() {
stderr := strings.Builder{}
- err := NewCommand("log", revision, "--follow",
+ err := NewCommandContext(repo.Ctx, "log", revision, "--follow",
"--max-count="+strconv.Itoa(setting.Git.CommitsRangeSize*page),
prettyLogFormat, "--", file).
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
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)
if err != nil {
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
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") {
// 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...
- 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 {
return 0, err
@@ -268,13 +268,13 @@ func (repo *Repository) CommitsBetween(last *Commit, before *Commit) ([]*Commit,
var stdout []byte
var err error
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 {
- 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") {
// 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...
- 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 {
@@ -288,13 +288,13 @@ func (repo *Repository) CommitsBetweenLimit(last *Commit, before *Commit, limit,
var stdout []byte
var err error
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 {
- 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") {
// 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...
- 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 {
@@ -333,7 +333,7 @@ func (repo *Repository) CommitsCountBetween(start, end string) (int64, error) {
// commitsBefore the limit is depth, not total number of returned commits.
func (repo *Repository) commitsBefore(id SHA1, limit int) ([]*Commit, error) {
- cmd := NewCommand("log")
+ cmd := NewCommandContext(repo.Ctx, "log")
if limit > 0 {
cmd.AddArguments("-"+strconv.Itoa(limit), prettyLogFormat, id.String())
} 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) {
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 {
return nil, err
}
@@ -386,7 +386,7 @@ func (repo *Repository) getBranches(commit *Commit, limit int) ([]string, error)
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 {
return nil, err
}
@@ -425,7 +425,7 @@ func (repo *Repository) GetCommitsFromIDs(commitIDs []string) []*Commit {
// IsCommitInBranch check if the commit is on the branch
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 {
return false, err
}
diff --git a/modules/git/repo_commit_gogit.go b/modules/git/repo_commit_gogit.go
index 175b6e6446..f00b340d15 100644
--- a/modules/git/repo_commit_gogit.go
+++ b/modules/git/repo_commit_gogit.go
@@ -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 strings.Contains(err.Error(), "unknown revision or path") ||
strings.Contains(err.Error(), "fatal: Needed a single revision") {
diff --git a/modules/git/repo_commit_nogogit.go b/modules/git/repo_commit_nogogit.go
index 8bfc953759..d86e7d3268 100644
--- a/modules/git/repo_commit_nogogit.go
+++ b/modules/git/repo_commit_nogogit.go
@@ -18,7 +18,7 @@ import (
// ResolveReference resolves a name to a reference
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 strings.Contains(err.Error(), "not a valid ref") {
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).
func (repo *Repository) GetRefCommitID(name string) (string, error) {
- wr, rd, cancel := repo.CatFileBatchCheck()
+ wr, rd, cancel := repo.CatFileBatchCheck(repo.Ctx)
defer cancel()
_, _ = wr.Write([]byte(name + "\n"))
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.
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
}
func (repo *Repository) getCommit(id SHA1) (*Commit, error) {
- wr, rd, cancel := repo.CatFileBatch()
+ wr, rd, cancel := repo.CatFileBatch(repo.Ctx)
defer cancel()
_, _ = 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()
_, err := wr.Write([]byte(commitID + "\n"))
if err != nil {
diff --git a/modules/git/repo_compare.go b/modules/git/repo_compare.go
index 019c9bc806..303bb5bc03 100644
--- a/modules/git/repo_compare.go
+++ b/modules/git/repo_compare.go
@@ -35,13 +35,13 @@ func (repo *Repository) GetMergeBase(tmpRemote string, base, head string) (strin
if tmpRemote != "origin" {
tmpBaseName := "refs/remotes/" + tmpRemote + "/tmp_" + base
// 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 {
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
}
@@ -88,7 +88,7 @@ func (repo *Repository) GetCompareInfo(basePath, baseBranch, headBranch string,
// We have a common base - therefore we know that ... should work
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 {
return nil, err
}
@@ -141,14 +141,14 @@ func (repo *Repository) GetDiffNumChangedFiles(base, head string, directComparis
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 {
if strings.Contains(stderr.String(), "no merge base") {
// 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...
w = &lineCountWriter{}
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
}
}
@@ -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
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)
}
// GetDiffBinary generates and returns patch data between given revisions, including binary diffs.
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)
}
// 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 {
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)
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)
}
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
func (repo *Repository) GetDiffFromMergeBase(base, head string, w io.Writer) error {
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)
if err != nil && bytes.Contains(stderr.Bytes(), []byte("no merge base")) {
return repo.GetDiffBinary(base, head, w)
diff --git a/modules/git/repo_gpg.go b/modules/git/repo_gpg.go
index b4c3f3b431..addf6a6b62 100644
--- a/modules/git/repo_gpg.go
+++ b/modules/git/repo_gpg.go
@@ -34,7 +34,7 @@ func (repo *Repository) GetDefaultPublicGPGKey(forceUpdate bool) (*GPGSettings,
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))
if !sign || !valid {
gpgSettings.Sign = false
@@ -42,13 +42,13 @@ func (repo *Repository) GetDefaultPublicGPGKey(forceUpdate bool) (*GPGSettings,
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)
- 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)
- 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)
if err := gpgSettings.LoadPublicKeyContent(); err != nil {
diff --git a/modules/git/repo_index.go b/modules/git/repo_index.go
index 38c01295b6..f5533b25e7 100644
--- a/modules/git/repo_index.go
+++ b/modules/git/repo_index.go
@@ -18,7 +18,7 @@ import (
// ReadTreeToIndex reads a treeish to the index
func (repo *Repository) ReadTreeToIndex(treeish string, indexFilename ...string) error {
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 {
return err
}
@@ -38,7 +38,7 @@ func (repo *Repository) readTreeToIndex(id SHA1, indexFilename ...string) error
if len(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 {
return err
}
@@ -69,13 +69,13 @@ func (repo *Repository) ReadTreeToTemporaryIndex(treeish string) (filename, tmpD
// EmptyIndex empties the index
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
}
// LsFiles checks if the given filenames are in the index
func (repo *Repository) LsFiles(filenames ...string) ([]string, error) {
- cmd := NewCommand("ls-files", "-z", "--")
+ cmd := NewCommandContext(repo.Ctx, "ls-files", "-z", "--")
for _, arg := range filenames {
if 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.
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)
stderr := 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
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)
return err
}
// WriteTree writes the current index as a tree to the object db and returns its hash
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 {
return nil, err
}
diff --git a/modules/git/repo_language_stats_nogogit.go b/modules/git/repo_language_stats_nogogit.go
index 4fda7ab627..0b21bf6344 100644
--- a/modules/git/repo_language_stats_nogogit.go
+++ b/modules/git/repo_language_stats_nogogit.go
@@ -25,7 +25,7 @@ import (
func (repo *Repository) GetLanguageStats(commitID string) (map[string]int64, error) {
// We will feed the commit IDs in order into cat-file --batch, followed by blobs as necessary.
// so let's create a batch stdin and stdout
- batchStdinWriter, batchReader, cancel := repo.CatFileBatch()
+ batchStdinWriter, batchReader, cancel := repo.CatFileBatch(repo.Ctx)
defer cancel()
writeID := func(id string) error {
@@ -76,7 +76,7 @@ func (repo *Repository) GetLanguageStats(commitID string) (map[string]int64, err
IndexFile: indexFilename,
WorkTree: worktree,
}
- ctx, cancel := context.WithCancel(DefaultContext)
+ ctx, cancel := context.WithCancel(repo.Ctx)
if err := checker.Init(ctx); err != nil {
log.Error("Unable to open checker for %s. Error: %v", commitID, err)
} else {
@@ -96,6 +96,12 @@ func (repo *Repository) GetLanguageStats(commitID string) (map[string]int64, err
var content []byte
sizes := make(map[string]int64)
for _, f := range entries {
+ select {
+ case <-repo.Ctx.Done():
+ return sizes, repo.Ctx.Err()
+ default:
+ }
+
contentBuf.Reset()
content = contentBuf.Bytes()
diff --git a/modules/git/repo_object.go b/modules/git/repo_object.go
index f054c34902..3921e6a1d4 100644
--- a/modules/git/repo_object.go
+++ b/modules/git/repo_object.go
@@ -42,7 +42,7 @@ func (repo *Repository) HashObject(reader io.Reader) (SHA1, 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)
stderr := new(bytes.Buffer)
err := cmd.RunInDirFullPipeline(repo.Path, stdout, stderr, reader)
diff --git a/modules/git/repo_ref_nogogit.go b/modules/git/repo_ref_nogogit.go
index ec0c5ec4ca..790b717d38 100644
--- a/modules/git/repo_ref_nogogit.go
+++ b/modules/git/repo_ref_nogogit.go
@@ -23,7 +23,7 @@ func (repo *Repository) GetRefsFiltered(pattern string) ([]*Reference, error) {
go func() {
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 {
_ = stdoutWriter.CloseWithError(ConcatenateError(err, stderrBuilder.String()))
} else {
diff --git a/modules/git/repo_stats.go b/modules/git/repo_stats.go
index aca5ab21cc..caf2caabcc 100644
--- a/modules/git/repo_stats.go
+++ b/modules/git/repo_stats.go
@@ -39,7 +39,7 @@ func (repo *Repository) GetCodeActivityStats(fromTime time.Time, branch string)
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 {
return nil, err
}
@@ -67,7 +67,7 @@ func (repo *Repository) GetCodeActivityStats(fromTime time.Time, branch string)
}
stderr := new(strings.Builder)
- err = NewCommand(args...).RunInDirTimeoutEnvFullPipelineFunc(
+ err = NewCommandContext(repo.Ctx, args...).RunInDirTimeoutEnvFullPipelineFunc(
nil, -1, repo.Path,
stdoutWriter, stderr, nil,
func(ctx context.Context, cancel context.CancelFunc) error {
diff --git a/modules/git/repo_tag.go b/modules/git/repo_tag.go
index 44d7a048bc..4262e0804f 100644
--- a/modules/git/repo_tag.go
+++ b/modules/git/repo_tag.go
@@ -6,6 +6,7 @@
package git
import (
+ "context"
"fmt"
"strings"
@@ -17,19 +18,19 @@ import (
const TagPrefix = "refs/tags/"
// IsTagExist returns true if given tag exists in the repository.
-func IsTagExist(repoPath, name string) bool {
- return IsReferenceExist(repoPath, TagPrefix+name)
+func IsTagExist(ctx context.Context, repoPath, name string) bool {
+ return IsReferenceExist(ctx, repoPath, TagPrefix+name)
}
// CreateTag create one tag in the repository
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
}
// CreateAnnotatedTag create one annotated tag in the repository
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
}
@@ -79,7 +80,7 @@ func (repo *Repository) getTag(tagID SHA1, name string) (*Tag, error) {
}
// 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 {
return nil, err
}
@@ -104,7 +105,7 @@ func (repo *Repository) GetTagNameBySHA(sha string) (string, error) {
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 {
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)
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 {
return "", err
}
@@ -163,7 +164,7 @@ func (repo *Repository) GetTag(name string) (*Tag, error) {
// GetTagInfos returns all tag infos of the repository.
func (repo *Repository) GetTagInfos(page, pageSize int) ([]*Tag, int, error) {
// 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 {
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)
func (repo *Repository) GetTagType(id SHA1) (string, error) {
// 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 {
return "", err
}
diff --git a/modules/git/repo_tag_nogogit.go b/modules/git/repo_tag_nogogit.go
index 172b6fd66c..1a23755aa6 100644
--- a/modules/git/repo_tag_nogogit.go
+++ b/modules/git/repo_tag_nogogit.go
@@ -20,6 +20,6 @@ func (repo *Repository) IsTagExist(name string) bool {
// GetTags returns all tags of the repository.
// returning at most limit tags, or all if limit is 0.
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
}
diff --git a/modules/git/repo_tree.go b/modules/git/repo_tree.go
index 2053b6a1de..f57c26ffee 100644
--- a/modules/git/repo_tree.go
+++ b/modules/git/repo_tree.go
@@ -40,7 +40,7 @@ func (repo *Repository) CommitTree(author *Signature, committer *Signature, tree
"GIT_COMMITTER_EMAIL="+committer.Email,
"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 {
cmd.AddArguments("-p", parent)
diff --git a/modules/git/repo_tree_gogit.go b/modules/git/repo_tree_gogit.go
index 2ddffcf79b..5a90cbe802 100644
--- a/modules/git/repo_tree_gogit.go
+++ b/modules/git/repo_tree_gogit.go
@@ -22,7 +22,7 @@ func (repo *Repository) getTree(id SHA1) (*Tree, error) {
// GetTree find the tree object in the repository.
func (repo *Repository) GetTree(idStr string) (*Tree, error) {
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 {
return nil, err
}
diff --git a/modules/git/repo_tree_nogogit.go b/modules/git/repo_tree_nogogit.go
index b27abb6e02..56a4a732e0 100644
--- a/modules/git/repo_tree_nogogit.go
+++ b/modules/git/repo_tree_nogogit.go
@@ -12,7 +12,7 @@ import (
)
func (repo *Repository) getTree(id SHA1) (*Tree, error) {
- wr, rd, cancel := repo.CatFileBatch()
+ wr, rd, cancel := repo.CatFileBatch(repo.Ctx)
defer cancel()
_, _ = wr.Write([]byte(id.String() + "\n"))
diff --git a/modules/git/tree_nogogit.go b/modules/git/tree_nogogit.go
index 3d3fd26ece..cfa2291e8f 100644
--- a/modules/git/tree_nogogit.go
+++ b/modules/git/tree_nogogit.go
@@ -36,7 +36,7 @@ func (t *Tree) ListEntries() (Entries, error) {
}
if t.repo != nil {
- wr, rd, cancel := t.repo.CatFileBatch()
+ wr, rd, cancel := t.repo.CatFileBatch(t.repo.Ctx)
defer cancel()
_, _ = wr.Write([]byte(t.ID.String() + "\n"))
diff --git a/modules/indexer/code/bleve.go b/modules/indexer/code/bleve.go
index 66b2602d3b..97d5fb082c 100644
--- a/modules/indexer/code/bleve.go
+++ b/modules/indexer/code/bleve.go
@@ -275,7 +275,7 @@ func (b *BleveIndexer) Index(repo *models.Repository, sha string, changes *repoC
batch := gitea_bleve.NewFlushingBatch(b.indexer, maxBatchSize)
if len(changes.Updates) > 0 {
- batchWriter, batchReader, cancel := git.CatFileBatch(repo.RepoPath())
+ batchWriter, batchReader, cancel := git.CatFileBatch(git.DefaultContext, repo.RepoPath())
defer cancel()
for _, update := range changes.Updates {
diff --git a/modules/indexer/code/elastic_search.go b/modules/indexer/code/elastic_search.go
index f76f316f64..6e0813dc15 100644
--- a/modules/indexer/code/elastic_search.go
+++ b/modules/indexer/code/elastic_search.go
@@ -248,7 +248,7 @@ func (b *ElasticSearchIndexer) Index(repo *models.Repository, sha string, change
reqs := make([]elastic.BulkableRequest, 0)
if len(changes.Updates) > 0 {
- batchWriter, batchReader, cancel := git.CatFileBatch(repo.RepoPath())
+ batchWriter, batchReader, cancel := git.CatFileBatch(git.DefaultContext, repo.RepoPath())
defer cancel()
for _, update := range changes.Updates {
diff --git a/modules/indexer/stats/db.go b/modules/indexer/stats/db.go
index 87e8677a28..9e251d0f69 100644
--- a/modules/indexer/stats/db.go
+++ b/modules/indexer/stats/db.go
@@ -5,9 +5,13 @@
package stats
import (
+ "fmt"
+
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/git"
+ "code.gitea.io/gitea/modules/graceful"
"code.gitea.io/gitea/modules/log"
+ "code.gitea.io/gitea/modules/process"
)
// DBIndexer implements Indexer interface to use database's like search
@@ -16,6 +20,9 @@ type DBIndexer struct {
// Index repository status function
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)
if err != nil {
return err
@@ -29,7 +36,7 @@ func (db *DBIndexer) Index(id int64) error {
return err
}
- gitRepo, err := git.OpenRepository(repo.RepoPath())
+ gitRepo, err := git.OpenRepositoryCtx(ctx, repo.RepoPath())
if err != nil {
return err
}
diff --git a/modules/markup/external/external.go b/modules/markup/external/external.go
index 36cbd69f92..3acb601067 100644
--- a/modules/markup/external/external.go
+++ b/modules/markup/external/external.go
@@ -5,7 +5,6 @@
package external
import (
- "context"
"fmt"
"io"
"os"
@@ -107,11 +106,8 @@ func (p *Renderer) Render(ctx *markup.RenderContext, input io.Reader, output io.
ctx.Ctx = graceful.GetManager().ShutdownContext()
}
- processCtx, cancel := context.WithCancel(ctx.Ctx)
- defer cancel()
-
- pid := process.GetManager().Add(fmt.Sprintf("Render [%s] for %s", commands[0], ctx.URLPrefix), cancel)
- defer process.GetManager().Remove(pid)
+ processCtx, _, finished := process.GetManager().AddContext(ctx.Ctx, fmt.Sprintf("Render [%s] for %s", commands[0], ctx.URLPrefix))
+ defer finished()
cmd := exec.CommandContext(processCtx, commands[0], args...)
cmd.Env = append(
diff --git a/modules/process/context.go b/modules/process/context.go
new file mode 100644
index 0000000000..6df5bc1513
--- /dev/null
+++ b/modules/process/context.go
@@ -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
+}
diff --git a/modules/process/manager.go b/modules/process/manager.go
index e42e38a0f0..10a89d04dd 100644
--- a/modules/process/manager.go
+++ b/modules/process/manager.go
@@ -12,6 +12,7 @@ import (
"io"
"os/exec"
"sort"
+ "strconv"
"sync"
"time"
)
@@ -28,57 +29,151 @@ var (
DefaultContext = context.Background()
)
-// Process represents a working process inheriting from Gitea.
-type Process struct {
- PID int64 // Process ID, not system one.
- Description string
- Start time.Time
- Cancel context.CancelFunc
-}
+// IDType is a pid type
+type IDType string
+
+// 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 knows about all processes and counts PIDs.
+// Manager manages all processes and counts PIDs.
type Manager struct {
mutex sync.Mutex
- counter int64
- processes map[int64]*Process
+ next int64
+ lastTime int64
+
+ processes map[IDType]*Process
}
// GetManager returns a Manager and initializes one as singleton if there's none yet
func GetManager() *Manager {
managerInit.Do(func() {
manager = &Manager{
- processes: make(map[int64]*Process),
+ processes: make(map[IDType]*Process),
+ next: 1,
}
})
return manager
}
-// Add a process to the ProcessManager and returns its PID.
-func (pm *Manager) Add(description string, cancel context.CancelFunc) int64 {
+// AddContext creates a new context and adds 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 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()
- pid := pm.counter + 1
- pm.processes[pid] = &Process{
+ start, pid := pm.nextPID()
+
+ parent := pm.processes[parentPID]
+ if parent == nil {
+ parentPID = ""
+ }
+
+ process := &Process{
PID: pid,
+ ParentPID: parentPID,
Description: description,
- Start: time.Now(),
+ Start: start,
Cancel: cancel,
}
- pm.counter = pid
+
+ finished := func() {
+ cancel()
+ pm.remove(process)
+ }
+
+ if parent != nil {
+ parent.AddChild(process)
+ }
+ pm.processes[pid] = process
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.
-func (pm *Manager) Remove(pid int64) {
+func (pm *Manager) Remove(pid IDType) {
pm.mutex.Lock()
delete(pm.processes, pid)
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.
-func (pm *Manager) Cancel(pid int64) {
+func (pm *Manager) Cancel(pid IDType) {
pm.mutex.Lock()
process, ok := pm.processes[pid]
pm.mutex.Unlock()
@@ -88,14 +183,28 @@ func (pm *Manager) Cancel(pid int64) {
}
// Processes gets the processes in a thread safe manner
-func (pm *Manager) Processes() []*Process {
+func (pm *Manager) Processes(onlyRoots bool) []*Process {
pm.mutex.Lock()
processes := make([]*Process, 0, len(pm.processes))
- for _, process := range pm.processes {
- processes = append(processes, process)
+ if onlyRoots {
+ for _, process := range pm.processes {
+ if _, has := pm.processes[process.ParentPID]; !has {
+ processes = append(processes, process)
+ }
+ }
+ } else {
+ for _, process := range pm.processes {
+ processes = append(processes, process)
+ }
}
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
}
@@ -134,8 +243,8 @@ func (pm *Manager) ExecDirEnvStdIn(timeout time.Duration, dir, desc string, env
stdOut := new(bytes.Buffer)
stdErr := new(bytes.Buffer)
- ctx, cancel := context.WithTimeout(DefaultContext, timeout)
- defer cancel()
+ ctx, _, finished := pm.AddContextTimeout(DefaultContext, timeout, desc)
+ defer finished()
cmd := exec.CommandContext(ctx, cmdName, args...)
cmd.Dir = dir
@@ -150,13 +259,11 @@ func (pm *Manager) ExecDirEnvStdIn(timeout time.Duration, dir, desc string, env
return "", "", err
}
- pid := pm.Add(desc, cancel)
err := cmd.Wait()
- pm.Remove(pid)
if err != nil {
err = &Error{
- PID: pid,
+ PID: GetPID(ctx),
Description: desc,
Err: 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
}
-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
type Error struct {
- PID int64
+ PID IDType
Description string
Err error
CtxErr error
@@ -193,7 +286,7 @@ type Error struct {
}
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()
diff --git a/modules/process/manager_test.go b/modules/process/manager_test.go
index a515fc32cd..eb4228e72c 100644
--- a/modules/process/manager_test.go
+++ b/modules/process/manager_test.go
@@ -21,44 +21,72 @@ func TestGetManager(t *testing.T) {
assert.NotNil(t, pm)
}
-func TestManager_Add(t *testing.T) {
- pm := Manager{processes: make(map[int64]*Process)}
+func TestManager_AddContext(t *testing.T) {
+ pm := Manager{processes: make(map[IDType]*Process), next: 1}
- pid := pm.Add("foo", nil)
- assert.Equal(t, int64(1), pid, "expected to get pid 1 got %d", pid)
+ ctx, cancel := context.WithCancel(context.Background())
+ defer cancel()
+
+ p1Ctx, _, finished := pm.AddContext(ctx, "foo")
+ 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")
- pid = pm.Add("bar", nil)
- assert.Equal(t, int64(2), pid, "expected to get pid 2 got %d", 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) {
- pm := Manager{processes: make(map[int64]*Process)}
+ pm := Manager{processes: make(map[IDType]*Process), next: 1}
- ctx, cancel := context.WithCancel(context.Background())
- pid := pm.Add("foo", cancel)
+ ctx, _, finished := pm.AddContext(context.Background(), "foo")
+ defer finished()
+
+ pm.Cancel(GetPID(ctx))
+
+ select {
+ case <-ctx.Done():
+ default:
+ assert.Fail(t, "Cancel should cancel the provided context")
+ }
+ finished()
- pm.Cancel(pid)
+ 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) {
- pm := Manager{processes: make(map[int64]*Process)}
+ pm := Manager{processes: make(map[IDType]*Process), next: 1}
+
+ ctx, cancel := context.WithCancel(context.Background())
+ defer cancel()
+
+ p1Ctx, _, finished := pm.AddContext(ctx, "foo")
+ defer finished()
+ assert.NotEmpty(t, GetContext(p1Ctx).GetPID(), "expected to have non-empty PID")
- pid1 := pm.Add("foo", nil)
- assert.Equal(t, int64(1), pid1, "expected to get pid 1 got %d", pid1)
+ p2Ctx, _, finished := pm.AddContext(p1Ctx, "bar")
+ defer finished()
- pid2 := pm.Add("bar", nil)
- assert.Equal(t, int64(2), pid2, "expected to get pid 2 got %d", pid2)
+ assert.NotEqual(t, GetContext(p1Ctx).GetPID(), GetContext(p2Ctx).GetPID(), "expected to get different pids got %s == %s", GetContext(p2Ctx).GetPID(), GetContext(p1Ctx).GetPID())
- pm.Remove(pid2)
+ pm.Remove(GetPID(p2Ctx))
- _, exists := pm.processes[pid2]
- assert.False(t, exists, "PID %d is in the list but shouldn't", pid2)
+ _, 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) {
diff --git a/modules/process/process.go b/modules/process/process.go
new file mode 100644
index 0000000000..662f878d7f
--- /dev/null
+++ b/modules/process/process.go
@@ -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
+ }
+ }
+}
diff --git a/modules/templates/helper.go b/modules/templates/helper.go
index 4eb8f34707..db9dbb90a5 100644
--- a/modules/templates/helper.go
+++ b/modules/templates/helper.go
@@ -957,7 +957,7 @@ type remoteAddress struct {
func mirrorRemoteAddress(m models.RemoteMirrorer) 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 {
log.Error("GetRemoteAddress %v", err)
return a