mirror of
https://codeberg.org/forgejo/forgejo
synced 2024-11-24 18:56:11 +01:00
858c35b731
#11716 reports multiple git blame processes hanging around this was thought to be due to timeouts, however on closer look this appears to be due to the Close() function of the BlameReader hanging with a blocked stdout pipe. This PR fixes this Close function to: * Cancel the context of the cmd * Close the StdoutReader - ensuring that the output pipe is closed Further it makes the context of the `git blame` command a child of the request context - ensuring that even if Close() is not called, on cancellation of the Request the blame is command will also be cancelled. Fixes #11716 Closes #11727 Signed-off-by: Andrew Thornton <art27@cantab.net>
135 lines
2.9 KiB
Go
135 lines
2.9 KiB
Go
// Copyright 2019 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 git
|
|
|
|
import (
|
|
"bufio"
|
|
"context"
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"os/exec"
|
|
"regexp"
|
|
|
|
"code.gitea.io/gitea/modules/process"
|
|
)
|
|
|
|
// BlamePart represents block of blame - continuous lines with one sha
|
|
type BlamePart struct {
|
|
Sha string
|
|
Lines []string
|
|
}
|
|
|
|
// BlameReader returns part of file blame one by one
|
|
type BlameReader struct {
|
|
cmd *exec.Cmd
|
|
pid int64
|
|
output io.ReadCloser
|
|
scanner *bufio.Scanner
|
|
lastSha *string
|
|
cancel context.CancelFunc
|
|
}
|
|
|
|
var shaLineRegex = regexp.MustCompile("^([a-z0-9]{40})")
|
|
|
|
// NextPart returns next part of blame (sequencial code lines with the same commit)
|
|
func (r *BlameReader) NextPart() (*BlamePart, error) {
|
|
var blamePart *BlamePart
|
|
|
|
scanner := r.scanner
|
|
|
|
if r.lastSha != nil {
|
|
blamePart = &BlamePart{*r.lastSha, make([]string, 0)}
|
|
}
|
|
|
|
for scanner.Scan() {
|
|
line := scanner.Text()
|
|
|
|
// Skip empty lines
|
|
if len(line) == 0 {
|
|
continue
|
|
}
|
|
|
|
lines := shaLineRegex.FindStringSubmatch(line)
|
|
if lines != nil {
|
|
sha1 := lines[1]
|
|
|
|
if blamePart == nil {
|
|
blamePart = &BlamePart{sha1, make([]string, 0)}
|
|
}
|
|
|
|
if blamePart.Sha != sha1 {
|
|
r.lastSha = &sha1
|
|
return blamePart, nil
|
|
}
|
|
} else if line[0] == '\t' {
|
|
code := line[1:]
|
|
|
|
blamePart.Lines = append(blamePart.Lines, code)
|
|
}
|
|
}
|
|
|
|
r.lastSha = nil
|
|
|
|
return blamePart, nil
|
|
}
|
|
|
|
// Close BlameReader - don't run NextPart after invoking that
|
|
func (r *BlameReader) Close() error {
|
|
defer process.GetManager().Remove(r.pid)
|
|
r.cancel()
|
|
|
|
_ = r.output.Close()
|
|
|
|
if err := r.cmd.Wait(); err != nil {
|
|
return fmt.Errorf("Wait: %v", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// CreateBlameReader creates reader for given repository, commit and file
|
|
func CreateBlameReader(ctx context.Context, repoPath, commitID, file string) (*BlameReader, error) {
|
|
gitRepo, err := OpenRepository(repoPath)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
gitRepo.Close()
|
|
|
|
return createBlameReader(ctx, repoPath, GitExecutable, "blame", commitID, "--porcelain", "--", file)
|
|
}
|
|
|
|
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)
|
|
cmd := exec.CommandContext(ctx, command[0], command[1:]...)
|
|
cmd.Dir = dir
|
|
cmd.Stderr = os.Stderr
|
|
|
|
stdout, err := cmd.StdoutPipe()
|
|
if err != nil {
|
|
defer cancel()
|
|
return nil, fmt.Errorf("StdoutPipe: %v", err)
|
|
}
|
|
|
|
if err = cmd.Start(); err != nil {
|
|
defer cancel()
|
|
return nil, fmt.Errorf("Start: %v", err)
|
|
}
|
|
|
|
pid := process.GetManager().Add(fmt.Sprintf("GetBlame [repo_path: %s]", dir), cancel)
|
|
|
|
scanner := bufio.NewScanner(stdout)
|
|
|
|
return &BlameReader{
|
|
cmd,
|
|
pid,
|
|
stdout,
|
|
scanner,
|
|
nil,
|
|
cancel,
|
|
}, nil
|
|
}
|