Read `previous` info from git blame (#28306)

Fixes #28280

Reads the `previous` info from the `git blame` output instead of
calculating it afterwards.
This commit is contained in:
KN4CK3R 2023-12-01 02:26:52 +01:00 committed by GitHub
parent 3618715fab
commit 14354e4f8e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 49 additions and 54 deletions

View File

@ -11,6 +11,7 @@ import (
"io" "io"
"os" "os"
"regexp" "regexp"
"strings"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/util"
@ -18,8 +19,10 @@ import (
// BlamePart represents block of blame - continuous lines with one sha // BlamePart represents block of blame - continuous lines with one sha
type BlamePart struct { type BlamePart struct {
Sha string Sha string
Lines []string Lines []string
PreviousSha string
PreviousPath string
} }
// BlameReader returns part of file blame one by one // BlameReader returns part of file blame one by one
@ -43,30 +46,38 @@ func (r *BlameReader) NextPart() (*BlamePart, error) {
var blamePart *BlamePart var blamePart *BlamePart
if r.lastSha != nil { if r.lastSha != nil {
blamePart = &BlamePart{*r.lastSha, make([]string, 0)} blamePart = &BlamePart{
Sha: *r.lastSha,
Lines: make([]string, 0),
}
} }
var line []byte var lineBytes []byte
var isPrefix bool var isPrefix bool
var err error var err error
for err != io.EOF { for err != io.EOF {
line, isPrefix, err = r.bufferedReader.ReadLine() lineBytes, isPrefix, err = r.bufferedReader.ReadLine()
if err != nil && err != io.EOF { if err != nil && err != io.EOF {
return blamePart, err return blamePart, err
} }
if len(line) == 0 { if len(lineBytes) == 0 {
// isPrefix will be false // isPrefix will be false
continue continue
} }
lines := shaLineRegex.FindSubmatch(line) line := string(lineBytes)
lines := shaLineRegex.FindStringSubmatch(line)
if lines != nil { if lines != nil {
sha1 := string(lines[1]) sha1 := lines[1]
if blamePart == nil { if blamePart == nil {
blamePart = &BlamePart{sha1, make([]string, 0)} blamePart = &BlamePart{
Sha: sha1,
Lines: make([]string, 0),
}
} }
if blamePart.Sha != sha1 { if blamePart.Sha != sha1 {
@ -81,9 +92,11 @@ func (r *BlameReader) NextPart() (*BlamePart, error) {
return blamePart, nil return blamePart, nil
} }
} else if line[0] == '\t' { } else if line[0] == '\t' {
code := line[1:] blamePart.Lines = append(blamePart.Lines, line[1:])
} else if strings.HasPrefix(line, "previous ") {
blamePart.Lines = append(blamePart.Lines, string(code)) parts := strings.SplitN(line[len("previous "):], " ", 2)
blamePart.PreviousSha = parts[0]
blamePart.PreviousPath = parts[1]
} }
// need to munch to end of line... // need to munch to end of line...

View File

@ -24,15 +24,17 @@ func TestReadingBlameOutput(t *testing.T) {
parts := []*BlamePart{ parts := []*BlamePart{
{ {
"72866af952e98d02a73003501836074b286a78f6", Sha: "72866af952e98d02a73003501836074b286a78f6",
[]string{ Lines: []string{
"# test_repo", "# test_repo",
"Test repository for testing migration from github to gitea", "Test repository for testing migration from github to gitea",
}, },
}, },
{ {
"f32b0a9dfd09a60f616f29158f772cedd89942d2", Sha: "f32b0a9dfd09a60f616f29158f772cedd89942d2",
[]string{"", "Do not make any changes to this repo it is used for unit testing"}, Lines: []string{"", "Do not make any changes to this repo it is used for unit testing"},
PreviousSha: "72866af952e98d02a73003501836074b286a78f6",
PreviousPath: "README.md",
}, },
} }
@ -64,16 +66,18 @@ func TestReadingBlameOutput(t *testing.T) {
full := []*BlamePart{ full := []*BlamePart{
{ {
"af7486bd54cfc39eea97207ca666aa69c9d6df93", Sha: "af7486bd54cfc39eea97207ca666aa69c9d6df93",
[]string{"line", "line"}, Lines: []string{"line", "line"},
}, },
{ {
"45fb6cbc12f970b04eacd5cd4165edd11c8d7376", Sha: "45fb6cbc12f970b04eacd5cd4165edd11c8d7376",
[]string{"changed line"}, Lines: []string{"changed line"},
PreviousSha: "af7486bd54cfc39eea97207ca666aa69c9d6df93",
PreviousPath: "blame.txt",
}, },
{ {
"af7486bd54cfc39eea97207ca666aa69c9d6df93", Sha: "af7486bd54cfc39eea97207ca666aa69c9d6df93",
[]string{"line", "line", ""}, Lines: []string{"line", "line", ""},
}, },
} }
@ -89,8 +93,8 @@ func TestReadingBlameOutput(t *testing.T) {
Bypass: false, Bypass: false,
Parts: []*BlamePart{ Parts: []*BlamePart{
{ {
"af7486bd54cfc39eea97207ca666aa69c9d6df93", Sha: "af7486bd54cfc39eea97207ca666aa69c9d6df93",
[]string{"line", "line", "changed line", "line", "line", ""}, Lines: []string{"line", "line", "changed line", "line", "line", ""},
}, },
}, },
}, },

View File

@ -114,12 +114,12 @@ func RefBlame(ctx *context.Context) {
return return
} }
commitNames, previousCommits := processBlameParts(ctx, result.Parts) commitNames := processBlameParts(ctx, result.Parts)
if ctx.Written() { if ctx.Written() {
return return
} }
renderBlame(ctx, result.Parts, commitNames, previousCommits) renderBlame(ctx, result.Parts, commitNames)
ctx.HTML(http.StatusOK, tplRepoHome) ctx.HTML(http.StatusOK, tplRepoHome)
} }
@ -185,12 +185,9 @@ func fillBlameResult(br *git.BlameReader, r *blameResult) error {
return nil return nil
} }
func processBlameParts(ctx *context.Context, blameParts []git.BlamePart) (map[string]*user_model.UserCommit, map[string]string) { func processBlameParts(ctx *context.Context, blameParts []git.BlamePart) map[string]*user_model.UserCommit {
// store commit data by SHA to look up avatar info etc // store commit data by SHA to look up avatar info etc
commitNames := make(map[string]*user_model.UserCommit) commitNames := make(map[string]*user_model.UserCommit)
// previousCommits contains links from SHA to parent SHA,
// if parent also contains the current TreePath.
previousCommits := make(map[string]string)
// and as blameParts can reference the same commits multiple // and as blameParts can reference the same commits multiple
// times, we cache the lookup work locally // times, we cache the lookup work locally
commits := make([]*git.Commit, 0, len(blameParts)) commits := make([]*git.Commit, 0, len(blameParts))
@ -214,29 +211,11 @@ func processBlameParts(ctx *context.Context, blameParts []git.BlamePart) (map[st
} else { } else {
ctx.ServerError("Repo.GitRepo.GetCommit", err) ctx.ServerError("Repo.GitRepo.GetCommit", err)
} }
return nil, nil return nil
} }
commitCache[sha] = commit commitCache[sha] = commit
} }
// find parent commit
if commit.ParentCount() > 0 {
psha := commit.Parents[0]
previousCommit, ok := commitCache[psha.String()]
if !ok {
previousCommit, _ = commit.Parent(0)
if previousCommit != nil {
commitCache[psha.String()] = previousCommit
}
}
// only store parent commit ONCE, if it has the file
if previousCommit != nil {
if haz1, _ := previousCommit.HasFile(ctx.Repo.TreePath); haz1 {
previousCommits[commit.ID.String()] = previousCommit.ID.String()
}
}
}
commits = append(commits, commit) commits = append(commits, commit)
} }
@ -245,10 +224,10 @@ func processBlameParts(ctx *context.Context, blameParts []git.BlamePart) (map[st
commitNames[c.ID.String()] = c commitNames[c.ID.String()] = c
} }
return commitNames, previousCommits return commitNames
} }
func renderBlame(ctx *context.Context, blameParts []git.BlamePart, commitNames map[string]*user_model.UserCommit, previousCommits map[string]string) { func renderBlame(ctx *context.Context, blameParts []git.BlamePart, commitNames map[string]*user_model.UserCommit) {
repoLink := ctx.Repo.RepoLink repoLink := ctx.Repo.RepoLink
language := "" language := ""
@ -295,7 +274,6 @@ func renderBlame(ctx *context.Context, blameParts []git.BlamePart, commitNames m
} }
commit := commitNames[part.Sha] commit := commitNames[part.Sha]
previousSha := previousCommits[part.Sha]
if index == 0 { if index == 0 {
// Count commit number // Count commit number
commitCnt++ commitCnt++
@ -313,8 +291,8 @@ func renderBlame(ctx *context.Context, blameParts []git.BlamePart, commitNames m
br.Avatar = gotemplate.HTML(avatar) br.Avatar = gotemplate.HTML(avatar)
br.RepoLink = repoLink br.RepoLink = repoLink
br.PartSha = part.Sha br.PartSha = part.Sha
br.PreviousSha = previousSha br.PreviousSha = part.PreviousSha
br.PreviousShaURL = fmt.Sprintf("%s/blame/commit/%s/%s", repoLink, url.PathEscape(previousSha), util.PathEscapeSegments(ctx.Repo.TreePath)) br.PreviousShaURL = fmt.Sprintf("%s/blame/commit/%s/%s", repoLink, url.PathEscape(part.PreviousSha), util.PathEscapeSegments(part.PreviousPath))
br.CommitURL = fmt.Sprintf("%s/commit/%s", repoLink, url.PathEscape(part.Sha)) br.CommitURL = fmt.Sprintf("%s/commit/%s", repoLink, url.PathEscape(part.Sha))
br.CommitMessage = commit.CommitMessage br.CommitMessage = commit.CommitMessage
br.CommitSince = commitSince br.CommitSince = commitSince