]> source.dussan.org Git - gitea.git/commitdiff
Make Requests Processes and create process hierarchy. Associate OpenRepository with...
authorzeripath <art27@cantab.net>
Tue, 30 Nov 2021 20:06:32 +0000 (20:06 +0000)
committerGitHub <noreply@github.com>
Tue, 30 Nov 2021 20:06:32 +0000 (20:06 +0000)
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>
66 files changed:
cmd/hook.go
modules/context/repo.go
modules/git/batch_reader.go
modules/git/blame.go
modules/git/blob_nogogit.go
modules/git/command.go
modules/git/commit_info_nogogit.go
modules/git/diff.go
modules/git/pipeline/lfs_nogogit.go
modules/git/remote.go
modules/git/repo.go
modules/git/repo_attribute.go
modules/git/repo_base_gogit.go
modules/git/repo_base_nogogit.go
modules/git/repo_blame.go
modules/git/repo_branch.go
modules/git/repo_branch_nogogit.go
modules/git/repo_commit.go
modules/git/repo_commit_gogit.go
modules/git/repo_commit_nogogit.go
modules/git/repo_compare.go
modules/git/repo_gpg.go
modules/git/repo_index.go
modules/git/repo_language_stats_nogogit.go
modules/git/repo_object.go
modules/git/repo_ref_nogogit.go
modules/git/repo_stats.go
modules/git/repo_tag.go
modules/git/repo_tag_nogogit.go
modules/git/repo_tree.go
modules/git/repo_tree_gogit.go
modules/git/repo_tree_nogogit.go
modules/git/tree_nogogit.go
modules/indexer/code/bleve.go
modules/indexer/code/elastic_search.go
modules/indexer/stats/db.go
modules/markup/external/external.go
modules/process/context.go [new file with mode: 0644]
modules/process/manager.go
modules/process/manager_test.go
modules/process/process.go [new file with mode: 0644]
modules/templates/helper.go
options/locale/locale_en-US.ini
routers/api/v1/repo/branch.go
routers/common/middleware.go
routers/web/admin/admin.go
routers/web/repo/branch.go
routers/web/repo/http.go
routers/web/repo/issue.go
routers/web/repo/pull.go
routers/web/repo/setting.go
services/cron/tasks.go
services/gitdiff/gitdiff.go
services/mailer/mailer.go
services/mirror/mirror_pull.go
services/mirror/mirror_push.go
services/pull/commit_status.go
services/pull/pull.go
services/pull/temp_repo.go
services/repository/branch.go
services/repository/files/temp_repo.go
services/task/migrate.go
services/wiki/wiki.go
templates/admin/monitor.tmpl
templates/admin/process-row.tmpl [new file with mode: 0644]
templates/admin/process.tmpl [new file with mode: 0644]

index 6b8d89500ad6923ec7c875cb529a03d8d2e0a991..9bbe4f33abf9cb5941070e7d764143c677e609fe 100644 (file)
@@ -309,7 +309,7 @@ func runHookPostReceive(c *cli.Context) error {
        defer cancel()
 
        // 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)
        }
 
index 7feaad4ccd52010421f23c031a44a4e76df76e0c..159fd07d9d18b940b19bbbddc415d6b3b3a4a971 100644 (file)
@@ -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
index 8e3c23251b6353a582755bac5cfa87248e6d3930..71045adbc9bf43eed419dd5ed7febabb2d985e33 100644 (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
-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
        }
 
index fcbf183981c3792df0ce3fff1bfe6985b02eee1f..b30124594dc25b561006e5c5335d43cab79730ac 100644 (file)
@@ -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
 }
index 65a73ebc522fafc3979fb747724efd64ed61d0e2..aabf1b34aded838b69e8ac51f51dd45d38f98ab5 100644 (file)
@@ -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 {
index e7496f072cb1871f931e4da598d9dde9ba6fea32..3cf85c2d85209c251163d8fc1ea2d87f2efc5930 100644 (file)
@@ -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 {
index 261252dab1b51421fb17d9d1357d625bd3758923..b58c1885b69266780a62247ca21b60c4616fb1c2 100644 (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) {
-       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{}
index b473dc73f60e887b4a148b5d5d107d84feb40b23..3a82cda1ce037fb30208ccd9745997c7c08d7f6e 100644 (file)
@@ -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)
index 4aa21dd05f88838bdfcf4d0088838c7b276f8a30..1352aa76627cc4a4b755fa26bf10c8f6cc03024f 100644 (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.
        // 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
index 7ba2b35a5ed32ab1420be7d1510e9663fcc6e98a..5ed02300d42016f3964804eb76681c99399f9c80 100644 (file)
@@ -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)
index 89af7aa9e1dad36952f79bc6ad81fb2b20710c76..3ff2b6fe2d06870d5fc068cb3ed7962134dece03 100644 (file)
@@ -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")
        }
index 88fb7810a663742503539833051e8ff3c265bc65..0bb550bb4b89ab26a620bdb9935b405f618805be 100644 (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 {
                return nil, fmt.Errorf("failed to run check-attr: %v\n%s\n%s", err, stdOut.String(), stdErr.String())
index afa5383b1164b1d4fa7b57366fc95c31b93c1d5c..72995265622e51714a98fdcfe3cfa9c09592d9ee 100644 (file)
@@ -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
 }
 
index 22c4dfdcb3b8dc18d418f890088b75253bc1d565..14a6cacb44702b715f4b6fdeb96ce3ae54f4dd51 100644 (file)
@@ -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() {}
 }
index 5c023554f17bfe53fd9403e0178667fa7bcc82d2..4ca05e3ba42115dca961ffdb63219e414b808b58 100644 (file)
@@ -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
        }
index 96f692826ec935eb912ed2bef8fb69dc51bf369f..98b1bc8ae7c784ce21577b44d3525d7d53506758 100644 (file)
@@ -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
 }
index 666ca81c1e8f83bc538cfcca9ed681da26baff0d..1928c7515bcab7616ce5451471c0488eaaa2727e 100644 (file)
@@ -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()
index 25060f56da62740c234bdbd597e94ba384541bea..3afabac27b51a2e87ede1ee4e4a99078653fba0e 100644 (file)
@@ -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
        }
index 175b6e644660c2570a887a0b2ccfe1146bdcc98f..f00b340d154e39da66b2edfd0cf1d35cbe069dad 100644 (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 strings.Contains(err.Error(), "unknown revision or path") ||
                        strings.Contains(err.Error(), "fatal: Needed a single revision") {
index 8bfc953759d537b266d7a473241ef1f47e84e0ef..d86e7d32680bd8461c5f0dcabeee82470395fcae 100644 (file)
@@ -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 {
index 019c9bc806ed3034ed82a3437a2a6a0ca2e756f5..303bb5bc0392b158c6f43594d5a57d505d012787 100644 (file)
@@ -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)
index b4c3f3b4314aa3de6e07305d1436e78b2cc87d66..addf6a6b629640f34acc715d68932efcbe91aa57 100644 (file)
@@ -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 {
index 38c01295b67cca0349acc314dbf9e07cb77844e4..f5533b25e700b353001536cff5ef1631be8c8eb2 100644 (file)
@@ -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
        }
index 4fda7ab6275e99f14026b8e793a0137899f72afa..0b21bf6344c1ca67adc9c5fc8beec73dff11087a 100644 (file)
@@ -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()
 
index f054c349029e5c927b911ab009a0f489872d107f..3921e6a1d41ce97334ec612175241c8ad2609713 100644 (file)
@@ -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)
index ec0c5ec4cad301a314f3eea1141a45a0a017a248..790b717d384f9999d7482cf27b0a4704bbcf1368 100644 (file)
@@ -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 {
index aca5ab21ccb2e06b952a13347fa4ecd8edec3310..caf2caabcc3bd26394dc3dcf975c911530c4aaf3 100644 (file)
@@ -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 {
index 44d7a048bc7673d285cbe9e749e7e77aadc7ea2e..4262e0804f14a91207595e1ecb0d55601f5cc937 100644 (file)
@@ -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
        }
index 172b6fd66cbb096d730e818bcba46d5ec3566943..1a23755aa666b284a0af4c7f9cdbcf57ca79039a 100644 (file)
@@ -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
 }
index 2053b6a1de17d710ae56df16a7c6c59a4628e531..f57c26ffee95410a8ff9cc1f83a8a7229c84d67a 100644 (file)
@@ -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)
index 2ddffcf79b679625e69f866858fb5ec67c33090d..5a90cbe802ef4dc25ab90ae8eb584577e61b423e 100644 (file)
@@ -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
                }
index b27abb6e02a6f9ce0048f6e013daa0d3325a2dd0..56a4a732e034c84d931f26d769ae4b16f3808812 100644 (file)
@@ -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"))
index 3d3fd26ece768b912cab3ccee8c35d7677039cb3..cfa2291e8fd0048fe76ab1cec0e7ecaa49fa6edf 100644 (file)
@@ -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"))
index 66b2602d3bf2efdec29a63a3fc9428566e983fbf..97d5fb082cdcdbd09368b96ef5f37290b167d16c 100644 (file)
@@ -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 {
index f76f316f6494f277a43e8bb436ad5d0a52688060..6e0813dc158038937b8cf775362c45063f1807c0 100644 (file)
@@ -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 {
index 87e8677a289fc3855a91efdd677f2fe725fb8021..9e251d0f69f217088bd8ae33e7c493b01c09522d 100644 (file)
@@ -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
        }
index 36cbd69f92c98a080d4c9ad01966136ec150c82f..3acb601067df16cb224c2836bef85ede68e39a28 100644 (file)
@@ -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 (file)
index 0000000..6df5bc1
--- /dev/null
@@ -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
+}
index e42e38a0f02a4ce26c043d7133a4770b5659df13..10a89d04ddb4456544779d1ab17db084b95c302b 100644 (file)
@@ -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()
index a515fc32cda71f5fb6e5f659d7755ee81a8f373e..eb4228e72cc3ccf9e25fda5418bd7e276b4770b5 100644 (file)
@@ -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 (file)
index 0000000..662f878
--- /dev/null
@@ -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
+               }
+       }
+}
index 4eb8f3470762bfcfa3e640cbc7b066b1caa3d493..db9dbb90a510020e3faa150ca06ef2ac985cf4c1 100644 (file)
@@ -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
index ca6cd66625ed0b114b6c219de2e1affd7691af54..523f1c78d7dc23b1eff3614f7c4e8503843706eb 100644 (file)
@@ -2696,6 +2696,7 @@ monitor.execute_time = Execution Time
 monitor.process.cancel = Cancel process
 monitor.process.cancel_desc =  Cancelling a process may cause data loss
 monitor.process.cancel_notices =  Cancel: <strong>%s</strong>?
+monitor.process.children = Children
 monitor.queues = Queues
 monitor.queue = Queue: %s
 monitor.queue.name = Name
index 70e5b3e2b87229c842666dd6601253b764fc7bf8..e511152e57454afe0b0e67efb8a8f899292a67b7 100644 (file)
@@ -405,7 +405,7 @@ func CreateBranchProtection(ctx *context.APIContext) {
        repo := ctx.Repo.Repository
 
        // 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()
                return
        }
index 7c5c72f5cc38afb724e99977ae3c7eebea8837fa..75d3777e4978f368d314db3428f6f9ef01f75e0c 100644 (file)
@@ -11,6 +11,7 @@ import (
 
        "code.gitea.io/gitea/modules/context"
        "code.gitea.io/gitea/modules/log"
+       "code.gitea.io/gitea/modules/process"
        "code.gitea.io/gitea/modules/setting"
 
        "github.com/chi-middleware/proxy"
@@ -22,7 +23,9 @@ func Middlewares() []func(http.Handler) http.Handler {
        var handlers = []func(http.Handler) http.Handler{
                func(next http.Handler) http.Handler {
                        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))
                        })
                },
        }
index 8cbe852718526560fd7ef28673db1a216c0f5f54..7e25f96ee004cfd0d03a78272963573841548c41 100644 (file)
@@ -326,7 +326,7 @@ func Monitor(ctx *context.Context) {
        ctx.Data["Title"] = ctx.Tr("admin.monitor")
        ctx.Data["PageIsAdmin"] = 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["Queues"] = queue.GetManager().ManagedQueues()
        ctx.HTML(http.StatusOK, tplMonitor)
@@ -334,8 +334,8 @@ func Monitor(ctx *context.Context) {
 
 // MonitorCancel cancels a process
 func MonitorCancel(ctx *context.Context) {
-       pid := ctx.ParamsInt64("pid")
-       process.GetManager().Cancel(pid)
+       pid := ctx.Params("pid")
+       process.GetManager().Cancel(process.IDType(pid))
        ctx.JSON(http.StatusOK, map[string]interface{}{
                "redirect": setting.AppSubURL + "/admin/monitor",
        })
index 64fb1afb2df9bff166ed4825ffdaf87c813f456a..05b45eba4b2048cd89b6d96e13d6aea79af81815 100644 (file)
@@ -124,7 +124,7 @@ func RestoreBranchPost(ctx *context.Context) {
                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(),
                Branch: fmt.Sprintf("%s:%s%s", deletedBranch.Commit, git.BranchPrefix, deletedBranch.Name),
                Env:    models.PushingEnvironment(ctx.User, ctx.Repo.Repository),
index d1978aefe201b2078eaa60edfe94a00b808b27e9..3aa8e84f57fa1eb9e6b01f113fcb590289da15e8 100644 (file)
@@ -8,7 +8,6 @@ package repo
 import (
        "bytes"
        "compress/gzip"
-       gocontext "context"
        "fmt"
        "net/http"
        "os"
@@ -485,8 +484,10 @@ func serviceRPC(h serviceHandler, service string) {
                h.environ = append(h.environ, "GIT_PROTOCOL="+protocol)
        }
 
-       ctx, cancel := gocontext.WithCancel(git.DefaultContext)
-       defer cancel()
+       // ctx, cancel := gocontext.WithCancel(git.DefaultContext)
+       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
        cmd := exec.CommandContext(ctx, git.GitExecutable, service, "--stateless-rpc", h.dir)
        cmd.Dir = h.dir
@@ -495,9 +496,6 @@ func serviceRPC(h serviceHandler, service string) {
        cmd.Stdin = reqBody
        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 {
                log.Error("Fail to serve RPC(%s) in %s: %v - %s", service, h.dir, err, stderr.String())
                return
index 2e0118e03fbb373928ea2892ed9331d0da19bab0..f0857b18c0eb6a940a2bc8e33dc4dfc7ae25720d 100644 (file)
@@ -1594,7 +1594,7 @@ func ViewIssue(ctx *context.Context) {
                }
                ctx.Data["IsPullBranchDeletable"] = canDelete &&
                        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"])
 
                stillCanManualMerge := func() bool {
index 19e757dad885f69d4d78d67f350aa45710668244..7593e7fbc885484239e4f11b1960ab11512e087d 100644 (file)
@@ -436,7 +436,7 @@ func PrepareViewPullInfo(ctx *context.Context, issue *models.Issue) *git.Compare
                if pull.Flow == models.PullRequestFlowGithub {
                        headBranchExist = headGitRepo.IsBranchExist(pull.HeadBranch)
                } else {
-                       headBranchExist = git.IsReferenceExist(baseGitRepo.Path, pull.GetGitRefName())
+                       headBranchExist = git.IsReferenceExist(ctx, baseGitRepo.Path, pull.GetGitRefName())
                }
 
                if headBranchExist {
index 76a24d9f407fd7fe3f9ac260c37ff333973b314a..4fc1e91c252920e40082740240e7ec2ed5d3e71f 100644 (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() {
                        form.MirrorPassword, _ = u.User.Password()
                }
index 75bb4993c02b998a34918d8521ce86fdccae87d6..29062233c8ae434f0a050fe11ee22788958ce34f 100644 (file)
@@ -82,11 +82,10 @@ func (t *Task) RunWithUser(doer *user_model.User, config Config) {
                }
        }()
        graceful.GetManager().RunWithShutdownContext(func(baseCtx context.Context) {
-               ctx, cancel := context.WithCancel(baseCtx)
-               defer cancel()
                pm := process.GetManager()
-               pid := pm.Add(config.FormatMessage(t.Name, "process", doer), cancel)
-               defer pm.Remove(pid)
+               ctx, _, finished := pm.AddContext(baseCtx, config.FormatMessage(t.Name, "process", doer))
+               defer finished()
+
                if err := t.fun(ctx, doer, config); err != nil {
                        if db.IsErrCancelled(err) {
                                message := err.(db.ErrCancelled).Message
index 44c1727719d3a8cde2b7ad98c926a331d207189b..166660b87e1833e9dca2b88e89e5a8320d821b21 100644 (file)
@@ -1303,8 +1303,9 @@ func GetDiff(gitRepo *git.Repository, opts *DiffOptions, files ...string) (*Diff
                return nil, err
        }
 
-       ctx, cancel := context.WithTimeout(git.DefaultContext, time.Duration(setting.Git.Timeout.Default)*time.Second)
-       defer cancel()
+       timeout := time.Duration(setting.Git.Timeout.Default) * time.Second
+       ctx, _, finished := process.GetManager().AddContextTimeout(gitRepo.Ctx, timeout, fmt.Sprintf("GetDiffRange [repo_path: %s]", repoPath))
+       defer finished()
 
        argsLength := 6
        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)
        }
 
-       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)
        if err != nil {
                return nil, fmt.Errorf("unable to ParsePatch: %w", err)
index 0c0c6266276a1968021e27836055a34440c1d57d..4b1492df0b8985ef0eafc2d5c6f324b66d584e80 100644 (file)
@@ -7,7 +7,6 @@ package mailer
 
 import (
        "bytes"
-       "context"
        "crypto/tls"
        "fmt"
        "io"
@@ -258,11 +257,10 @@ func (s *sendmailSender) Send(from string, to []string, msg io.WriterTo) error {
        args = append(args, to...)
        log.Trace("Sending with: %s %v", setting.MailService.SendmailPath, args)
 
-       pm := process.GetManager()
        desc := fmt.Sprintf("SendMail: %s %v", setting.MailService.SendmailPath, args)
 
-       ctx, cancel := context.WithTimeout(graceful.GetManager().HammerContext(), setting.MailService.SendmailTimeout)
-       defer cancel()
+       ctx, _, finished := process.GetManager().AddContextTimeout(graceful.GetManager().HammerContext(), setting.MailService.SendmailTimeout, desc)
+       defer finished()
 
        cmd := exec.CommandContext(ctx, setting.MailService.SendmailPath, args...)
        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 {
+               _ = pipe.Close()
                return err
        }
 
-       pid := pm.Add(desc, cancel)
-
        _, err = msg.WriteTo(pipe)
 
        // 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
        closeError = pipe.Close()
        waitError = cmd.Wait()
-       pm.Remove(pid)
+
        if err != nil {
                return err
        } else if closeError != nil {
index 75b235e21a39d900b4abb10759c242463ba15bb0..9c8897fe79ae97b585e76b927067fc75d694226f 100644 (file)
@@ -18,6 +18,7 @@ import (
        "code.gitea.io/gitea/modules/lfs"
        "code.gitea.io/gitea/modules/log"
        "code.gitea.io/gitea/modules/notification"
+       "code.gitea.io/gitea/modules/process"
        repo_module "code.gitea.io/gitea/modules/repository"
        "code.gitea.io/gitea/modules/setting"
        "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())
 
-       remoteAddr, remoteErr := git.GetRemoteAddress(repoPath, m.GetRemoteName())
+       remoteAddr, remoteErr := git.GetRemoteAddress(ctx, repoPath, m.GetRemoteName())
        if remoteErr != nil {
                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
                        // contain a password
 
-                       remoteAddr, remoteErr := git.GetRemoteAddress(wikiPath, m.GetRemoteName())
+                       remoteAddr, remoteErr := git.GetRemoteAddress(ctx, wikiPath, m.GetRemoteName())
                        if remoteErr != nil {
                                log.Error("GetRemoteAddress Error %v", remoteErr)
                        }
@@ -367,6 +368,9 @@ func SyncPullMirror(ctx context.Context, repoID int64) bool {
                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)
        results, ok := runSync(ctx, m)
        if !ok {
@@ -385,7 +389,7 @@ func SyncPullMirror(ctx context.Context, repoID int64) bool {
                log.Trace("SyncMirrors [repo: %-v]: no branches updated", m.Repo)
        } else {
                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 {
                        log.Error("OpenRepository [%d]: %v", m.RepoID, err)
                        return false
index 7e33ffed3e7e1226e03baaad1bf06705ec21bb37..cf205e7b51d8a911a713eb0b74c73389a571fa23 100644 (file)
@@ -7,6 +7,7 @@ package mirror
 import (
        "context"
        "errors"
+       "fmt"
        "io"
        "regexp"
        "time"
@@ -15,6 +16,7 @@ import (
        "code.gitea.io/gitea/modules/git"
        "code.gitea.io/gitea/modules/lfs"
        "code.gitea.io/gitea/modules/log"
+       "code.gitea.io/gitea/modules/process"
        "code.gitea.io/gitea/modules/repository"
        "code.gitea.io/gitea/modules/setting"
        "code.gitea.io/gitea/modules/timeutil"
@@ -92,6 +94,9 @@ func SyncPushMirror(ctx context.Context, mirrorID int64) bool {
 
        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)
        err = runPushSync(ctx, m)
        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
 
        performPush := func(path string) error {
-               remoteAddr, err := git.GetRemoteAddress(path, m.RemoteName)
+               remoteAddr, err := git.GetRemoteAddress(ctx, path, m.RemoteName)
                if err != nil {
                        log.Error("GetRemoteAddress(%s) Error %v", path, err)
                        return errors.New("Unexpected error")
@@ -125,7 +130,7 @@ func runPushSync(ctx context.Context, m *models.PushMirror) error {
                if setting.LFS.StartServer {
                        log.Trace("SyncMirrors [repo: %-v]: syncing LFS objects...", m.Repo)
 
-                       gitRepo, err := git.OpenRepository(path)
+                       gitRepo, err := git.OpenRepositoryCtx(ctx, path)
                        if err != nil {
                                log.Error("OpenRepository: %v", err)
                                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)
 
-               if err := git.Push(path, git.PushOptions{
+               if err := git.Push(ctx, path, git.PushOptions{
                        Remote:  m.RemoteName,
                        Force:   true,
                        Mirror:  true,
@@ -162,7 +167,7 @@ func runPushSync(ctx context.Context, m *models.PushMirror) error {
 
        if m.Repo.HasWiki() {
                wikiPath := m.Repo.WikiPath()
-               _, err := git.GetRemoteAddress(wikiPath, m.RemoteName)
+               _, err := git.GetRemoteAddress(ctx, wikiPath, m.RemoteName)
                if err == nil {
                        err := performPush(wikiPath)
                        if err != nil {
index f1f351138b8e19ba08b4672e8e950c0a5f89c177..2b834c25f1c8bba35063437a4f3626fcf68a4a1c 100644 (file)
@@ -112,7 +112,7 @@ func GetPullRequestCommitStatusState(pr *models.PullRequest) (structs.CommitStat
        if pr.Flow == models.PullRequestFlowGithub && !headGitRepo.IsBranchExist(pr.HeadBranch) {
                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")
        }
 
index 339fb1e22d163323c53fc9b5652bba6567e2f15a..afbdf1ce254bf87907744540b72aec0248a94fa8 100644 (file)
@@ -313,7 +313,7 @@ func AddTestPullRequestTask(doer *user_model.User, repoID int64, branch string,
                for _, pr := range prs {
                        divergence, err := GetDiverging(pr)
                        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)
                                } else {
                                        log.Error("GetDiverging: %v", err)
@@ -431,7 +431,7 @@ func pushToBaseRepoHelper(pr *models.PullRequest, prefixHeadBranch string) (err
 
        gitRefName := pr.GetGitRefName()
 
-       if err := git.Push(headRepoPath, git.PushOptions{
+       if err := git.Push(git.DefaultContext, headRepoPath, git.PushOptions{
                Remote: baseRepoPath,
                Branch: prefixHeadBranch + pr.HeadBranch + ":" + gitRefName,
                Force:  true,
index 54d09c815852ce20a683231364752a704131a058..e30dba7add60f08d78bed92cb0c20ec05f4bdd10 100644 (file)
@@ -152,7 +152,7 @@ func createTemporaryRepo(pr *models.PullRequest) (string, error) {
                if err := models.RemoveTemporaryPath(tmpBasePath); err != nil {
                        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{
                                BranchName: pr.HeadBranch,
                        }
index 92e662f3d6d45a7cccb047b657ba0f054610bd42..09bfd86081579a8ac5d2336a0c6abac615d63185 100644 (file)
@@ -24,13 +24,13 @@ func CreateNewBranch(doer *user_model.User, repo *models.Repository, oldBranchNa
                return err
        }
 
-       if !git.IsBranchExist(repo.RepoPath(), oldBranchName) {
+       if !git.IsBranchExist(git.DefaultContext, repo.RepoPath(), oldBranchName) {
                return models.ErrBranchDoesNotExist{
                        BranchName: oldBranchName,
                }
        }
 
-       if err := git.Push(repo.RepoPath(), git.PushOptions{
+       if err := git.Push(git.DefaultContext, repo.RepoPath(), git.PushOptions{
                Remote: repo.RepoPath(),
                Branch: fmt.Sprintf("%s:%s%s", oldBranchName, git.BranchPrefix, branchName),
                Env:    models.PushingEnvironment(doer, repo),
@@ -106,7 +106,7 @@ func CreateNewBranchFromCommit(doer *user_model.User, repo *models.Repository, c
                return err
        }
 
-       if err := git.Push(repo.RepoPath(), git.PushOptions{
+       if err := git.Push(git.DefaultContext, repo.RepoPath(), git.PushOptions{
                Remote: repo.RepoPath(),
                Branch: fmt.Sprintf("%s:%s%s", commit, git.BranchPrefix, branchName),
                Env:    models.PushingEnvironment(doer, repo),
index 0b6bea637954b6e6245cef7fce9de2ea3d087245..4b10ed0b7712ae6f94fc673134c5ced35ad89193 100644 (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 {
        // Because calls hooks we need to pass in the environment
        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(),
                Branch: strings.TrimSpace(commitHash) + ":refs/heads/" + strings.TrimSpace(branch),
                Env:    env,
index 46ea80b00285c8c169d3261e667389b967b9a747..8e9f6115bc71bfbe9c421dde826c8b824c8e72e9 100644 (file)
@@ -5,7 +5,6 @@
 package task
 
 import (
-       "context"
        "errors"
        "fmt"
        "strings"
@@ -99,11 +98,9 @@ func runMigrateTask(t *models.Task) (err error) {
 
        opts.MigrateToRepoID = t.RepoID
 
-       ctx, cancel := context.WithCancel(graceful.GetManager().ShutdownContext())
-       defer cancel()
        pm := process.GetManager()
-       pid := pm.Add(fmt.Sprintf("MigrateTask: %s/%s", t.Owner.Name, opts.RepoName), cancel)
-       defer pm.Remove(pid)
+       ctx, _, finished := pm.AddContext(graceful.GetManager().ShutdownContext(), fmt.Sprintf("MigrateTask: %s/%s", t.Owner.Name, opts.RepoName))
+       defer finished()
 
        t.StartTime = timeutil.TimeStampNow()
        t.Status = structs.TaskStatusRunning
index 8b2344467330e74474117631b142cb7532aa5a37..cf25c33274b2917fd2ddb899ccee87de3d31355a 100644 (file)
@@ -128,7 +128,7 @@ func updateWikiPage(doer *user_model.User, repo *models.Repository, oldWikiName,
                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")
        if err != nil {
@@ -243,7 +243,7 @@ func updateWikiPage(doer *user_model.User, repo *models.Repository, oldWikiName,
                return err
        }
 
-       if err := git.Push(basePath, git.PushOptions{
+       if err := git.Push(gitRepo.Ctx, basePath, git.PushOptions{
                Remote: "origin",
                Branch: fmt.Sprintf("%s:%s%s", commitHash.String(), git.BranchPrefix, "master"),
                Env: models.FullPushingEnvironment(
@@ -357,7 +357,7 @@ func DeleteWikiPage(doer *user_model.User, repo *models.Repository, wikiName str
                return err
        }
 
-       if err := git.Push(basePath, git.PushOptions{
+       if err := git.Push(gitRepo.Ctx, basePath, git.PushOptions{
                Remote: "origin",
                Branch: fmt.Sprintf("%s:%s%s", commitHash.String(), git.BranchPrefix, "master"),
                Env:    models.PushingEnvironment(doer, repo),
index 16c4d88002a20d2ee9419f2965123b94d879e0ae..8a90f9b6474cbd99cfc8c9e2dc032f68024d733d 100644 (file)
                        </table>
                </div>
 
-               <h4 class="ui top attached header">
-                       {{.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>
+               {{template "admin/process" .}}
        </div>
 </div>
 <div class="ui small basic delete modal">
diff --git a/templates/admin/process-row.tmpl b/templates/admin/process-row.tmpl
new file mode 100644 (file)
index 0000000..814727e
--- /dev/null
@@ -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>
diff --git a/templates/admin/process.tmpl b/templates/admin/process.tmpl
new file mode 100644 (file)
index 0000000..719c10c
--- /dev/null
@@ -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>