新增:获取仓库blame内容接口
This commit is contained in:
parent
64352810b1
commit
484a4ce01a
|
@ -0,0 +1,181 @@
|
|||
package git
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
gitea_git "code.gitea.io/gitea/modules/git"
|
||||
"code.gitea.io/gitea/modules/process"
|
||||
hat_api "code.gitlink.org.cn/Gitlink/gitea_hat.git/modules/structs"
|
||||
)
|
||||
|
||||
type BlameReader struct {
|
||||
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})")
|
||||
|
||||
func GetBlameCommit(repo *gitea_git.Repository, sha string) *hat_api.BlameCommit {
|
||||
commit, err := repo.GetCommit(sha)
|
||||
|
||||
if err != nil {
|
||||
return &hat_api.BlameCommit{}
|
||||
}
|
||||
|
||||
var apiParents []string
|
||||
for i := 0; i < commit.ParentCount(); i++ {
|
||||
sha, _ := commit.ParentID(i)
|
||||
apiParents = append(apiParents, sha.String())
|
||||
}
|
||||
|
||||
return &hat_api.BlameCommit{
|
||||
ID: sha,
|
||||
Author: commit.Author,
|
||||
Commiter: commit.Committer,
|
||||
CommitMessage: commit.CommitMessage,
|
||||
Parents: apiParents,
|
||||
AuthoredTime: commit.Author.When,
|
||||
CommittedTime: commit.Committer.When,
|
||||
CreatedTime: commit.Committer.When,
|
||||
}
|
||||
}
|
||||
|
||||
func (r *BlameReader) NextApiPart(repo *gitea_git.Repository) (*hat_api.BlamePart, error) {
|
||||
var blamePart *hat_api.BlamePart
|
||||
reader := r.reader
|
||||
effectLine := 0
|
||||
|
||||
if r.lastSha != nil {
|
||||
blamePart = &hat_api.BlamePart{
|
||||
Sha: *r.lastSha,
|
||||
Commit: GetBlameCommit(repo, *r.lastSha),
|
||||
PreviousNumber: 0,
|
||||
CurrentNumber: 0,
|
||||
EffectLine: effectLine,
|
||||
Lines: make([]string, 0),
|
||||
}
|
||||
}
|
||||
|
||||
var line []byte
|
||||
var isPrefix bool
|
||||
var err error
|
||||
|
||||
for err != io.EOF {
|
||||
line, isPrefix, err = reader.ReadLine()
|
||||
if err != nil && err != io.EOF {
|
||||
return blamePart, err
|
||||
}
|
||||
|
||||
if len(line) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
lines := shaLineRegex.FindSubmatch(line)
|
||||
if lines != nil {
|
||||
lineString := string(line)
|
||||
sha1 := string(lines[1])
|
||||
lineString = strings.Replace(lineString, sha1, "", 1)
|
||||
lineArray := strings.Split(lineString, " ")
|
||||
previousNumber, _ := strconv.Atoi(lineArray[1])
|
||||
if blamePart == nil {
|
||||
blamePart = &hat_api.BlamePart{
|
||||
Sha: sha1,
|
||||
Commit: GetBlameCommit(repo, sha1),
|
||||
PreviousNumber: previousNumber,
|
||||
CurrentNumber: 0,
|
||||
EffectLine: effectLine,
|
||||
Lines: make([]string, 0),
|
||||
}
|
||||
}
|
||||
|
||||
if blamePart.Sha != sha1 {
|
||||
r.lastSha = &sha1
|
||||
// need to munch to end of line...
|
||||
for isPrefix {
|
||||
_, isPrefix, err = reader.ReadLine()
|
||||
if err != nil && err != io.EOF {
|
||||
return blamePart, err
|
||||
}
|
||||
}
|
||||
return blamePart, nil
|
||||
}
|
||||
} else if line[0] == '\t' {
|
||||
code := line[1:]
|
||||
effectLine += 1
|
||||
blamePart.Lines = append(blamePart.Lines, string(code))
|
||||
}
|
||||
blamePart.EffectLine = effectLine
|
||||
|
||||
for isPrefix {
|
||||
_, isPrefix, err = reader.ReadLine()
|
||||
if err != nil && err != io.EOF {
|
||||
return blamePart, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
r.lastSha = nil
|
||||
|
||||
return blamePart, nil
|
||||
}
|
||||
|
||||
func (r *BlameReader) Close() error {
|
||||
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()
|
||||
|
||||
if err := r.cmd.Wait(); err != nil {
|
||||
return fmt.Errorf("Wait: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func CreateBlameReader(ctx context.Context, repoPath, commitID, file string) (*BlameReader, error) {
|
||||
return createBlameReader(ctx, repoPath, gitea_git.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, 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
|
||||
process.SetSysProcAttribute(cmd)
|
||||
|
||||
stdout, err := cmd.StdoutPipe()
|
||||
if err != nil {
|
||||
defer finished()
|
||||
return nil, fmt.Errorf("StdoutPipe: %w", err)
|
||||
}
|
||||
|
||||
if err = cmd.Start(); err != nil {
|
||||
defer finished()
|
||||
_ = stdout.Close()
|
||||
return nil, fmt.Errorf("Start: %w", err)
|
||||
}
|
||||
|
||||
reader := bufio.NewReader(stdout)
|
||||
|
||||
return &BlameReader{
|
||||
cmd: cmd,
|
||||
output: stdout,
|
||||
reader: reader,
|
||||
cancel: cancel,
|
||||
finished: finished,
|
||||
}, nil
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
package structs
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
)
|
||||
|
||||
type BlameCommit struct {
|
||||
ID string `json:"id"`
|
||||
Author *git.Signature `json:"author"`
|
||||
Commiter *git.Signature `json:"commiter"`
|
||||
CommitMessage string `json:"commit_message"`
|
||||
Parents []string `json:"parents"`
|
||||
AuthoredTime time.Time `json:"authored_time"`
|
||||
CommittedTime time.Time `json:"committed_time"`
|
||||
CreatedTime time.Time `json:"created_time"`
|
||||
}
|
||||
|
||||
type BlamePart struct {
|
||||
Sha string `json:"-"`
|
||||
Commit *BlameCommit `json:"commit"`
|
||||
PreviousNumber int `json:"previous_number"`
|
||||
CurrentNumber int `json:"current_number"`
|
||||
EffectLine int `json:"effect_line"`
|
||||
Lines []string `json:"lines"`
|
||||
}
|
|
@ -134,6 +134,7 @@ func Routers(ctx gocontext.Context) *web.Route {
|
|||
m.Get("/{sha}", repo.GetSingleCommit)
|
||||
})
|
||||
}, context.ReferencesGitRepo(), reqRepoReader(unit.TypeCode))
|
||||
m.Get("/blame", context.RepoRef(), repo.GetRepoRefBlame)
|
||||
}, repoAssignment())
|
||||
})
|
||||
m.Group("/users", func() {
|
||||
|
|
|
@ -0,0 +1,71 @@
|
|||
package repo
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"code.gitea.io/gitea/modules/context"
|
||||
gitea_git "code.gitea.io/gitea/modules/git"
|
||||
hat_git "code.gitlink.org.cn/Gitlink/gitea_hat.git/modules/git"
|
||||
hat_api "code.gitlink.org.cn/Gitlink/gitea_hat.git/modules/structs"
|
||||
)
|
||||
|
||||
func GetRepoRefBlame(ctx *context.APIContext) {
|
||||
if ctx.Repo.Repository.IsEmpty {
|
||||
ctx.NotFound()
|
||||
return
|
||||
}
|
||||
|
||||
var commit *gitea_git.Commit
|
||||
if sha := ctx.FormString("sha"); len(sha) > 0 {
|
||||
var err error
|
||||
commit, err = ctx.Repo.GitRepo.GetCommit(sha)
|
||||
if err != nil {
|
||||
if gitea_git.IsErrNotExist(err) {
|
||||
ctx.NotFound()
|
||||
} else {
|
||||
ctx.Error(http.StatusInternalServerError, "GetCommit", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
filepath := ctx.FormString("filepath")
|
||||
entry, err := commit.GetTreeEntryByPath(filepath)
|
||||
if err != nil {
|
||||
ctx.NotFoundOrServerError("commit.GetTreeEntryByPath", gitea_git.IsErrNotExist, err)
|
||||
return
|
||||
}
|
||||
|
||||
blob := entry.Blob()
|
||||
numLines, err := blob.GetBlobLineCount()
|
||||
if err != nil {
|
||||
ctx.Error(http.StatusInternalServerError, "GetBlobLineCount", err)
|
||||
return
|
||||
}
|
||||
blameReader, err := hat_git.CreateBlameReader(ctx, ctx.Repo.Repository.RepoPath(), commit.ID.String(), filepath)
|
||||
if err != nil {
|
||||
ctx.Error(http.StatusInternalServerError, "CreateBlameReader", err)
|
||||
return
|
||||
}
|
||||
defer blameReader.Close()
|
||||
|
||||
blameParts := make([]hat_api.BlamePart, 0)
|
||||
currentNumber := 1
|
||||
for {
|
||||
blamePart, err := blameReader.NextApiPart(ctx.Repo.GitRepo)
|
||||
if err != nil {
|
||||
ctx.Error(http.StatusInternalServerError, "NextApiPart", err)
|
||||
return
|
||||
}
|
||||
if blamePart == nil {
|
||||
break
|
||||
}
|
||||
blamePart.CurrentNumber = currentNumber
|
||||
blameParts = append(blameParts, *blamePart)
|
||||
currentNumber += blamePart.EffectLine
|
||||
}
|
||||
ctx.JSON(http.StatusOK, map[string]interface{}{
|
||||
"num_lines": numLines,
|
||||
"blame_parts": blameParts,
|
||||
})
|
||||
}
|
Loading…
Reference in New Issue