summaryrefslogtreecommitdiffstats
path: root/routers
diff options
context:
space:
mode:
authorNorwin <noerw@users.noreply.github.com>2021-06-28 01:13:20 +0200
committerGitHub <noreply@github.com>2021-06-28 01:13:20 +0200
commit9c6aeb47f7979a434bf30408992c06118c142771 (patch)
tree0aed39da360583610dee02a2d96a7a43b022d45d /routers
parent59c58553baa5597f5c2156b47fd2e47e46aa58ac (diff)
downloadgitea-9c6aeb47f7979a434bf30408992c06118c142771.tar.gz
gitea-9c6aeb47f7979a434bf30408992c06118c142771.zip
Link to previous blames in file blame page (#16259)
Adds a link to each blame hunk, to view the blame of an earlier version of the file, similar to GitHub. Also refactors the blame render from fmtstring based to template based. * Fix blame bottom line and add blame prior button * Jump to previous parent commit from the commit. * Fix previous commit link * Fix previous blame link * Fix the given file not exist in the previous commit. * Fix blameRow struct not export * fix theming issues, rename template var * remove unused LastCommit fetch * fix location of blame-hunk divider * rewrite previous commit checks * remove duplicate commit lookup its already resolved and stored in ctx.Repo.Commit! * split out blamePart processing into function Co-authored-by: rogerluo410 <rogerluo410@gmail.com>
Diffstat (limited to 'routers')
-rw-r--r--routers/web/repo/blame.go179
1 files changed, 96 insertions, 83 deletions
diff --git a/routers/web/repo/blame.go b/routers/web/repo/blame.go
index 1a3e1dcb9c..4ade9e9a93 100644
--- a/routers/web/repo/blame.go
+++ b/routers/web/repo/blame.go
@@ -5,7 +5,6 @@
package repo
import (
- "bytes"
"container/list"
"fmt"
"html"
@@ -18,7 +17,6 @@ import (
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/highlight"
- "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/templates"
"code.gitea.io/gitea/modules/timeutil"
)
@@ -27,6 +25,20 @@ const (
tplBlame base.TplName = "repo/home"
)
+type blameRow struct {
+ RowNumber int
+ Avatar gotemplate.HTML
+ RepoLink string
+ PartSha string
+ PreviousSha string
+ PreviousShaURL string
+ IsFirstCommit bool
+ CommitURL string
+ CommitMessage string
+ CommitSince gotemplate.HTML
+ Code gotemplate.HTML
+}
+
// RefBlame render blame page
func RefBlame(ctx *context.Context) {
fileName := ctx.Repo.TreePath
@@ -39,19 +51,6 @@ func RefBlame(ctx *context.Context) {
repoName := ctx.Repo.Repository.Name
commitID := ctx.Repo.CommitID
- commit, err := ctx.Repo.GitRepo.GetCommit(commitID)
- if err != nil {
- if git.IsErrNotExist(err) {
- ctx.NotFound("Repo.GitRepo.GetCommit", err)
- } else {
- ctx.ServerError("Repo.GitRepo.GetCommit", err)
- }
- return
- }
- if len(commitID) != 40 {
- commitID = commit.ID.String()
- }
-
branchLink := ctx.Repo.RepoLink + "/src/" + ctx.Repo.BranchNameSubURL()
treeLink := branchLink
rawLink := ctx.Repo.RepoLink + "/raw/" + ctx.Repo.BranchNameSubURL()
@@ -74,25 +73,6 @@ func RefBlame(ctx *context.Context) {
}
}
- // Show latest commit info of repository in table header,
- // or of directory if not in root directory.
- latestCommit := ctx.Repo.Commit
- if len(ctx.Repo.TreePath) > 0 {
- latestCommit, err = ctx.Repo.Commit.GetCommitByPath(ctx.Repo.TreePath)
- if err != nil {
- ctx.ServerError("GetCommitByPath", err)
- return
- }
- }
- ctx.Data["LatestCommit"] = latestCommit
- ctx.Data["LatestCommitVerification"] = models.ParseCommitWithSignature(latestCommit)
- ctx.Data["LatestCommitUser"] = models.ValidateCommitWithEmail(latestCommit)
-
- statuses, err := models.GetLatestCommitStatus(ctx.Repo.Repository.ID, ctx.Repo.Commit.ID.String(), models.ListOptions{})
- if err != nil {
- log.Error("GetLatestCommitStatus: %v", err)
- }
-
// Get current entry user currently looking at.
entry, err := ctx.Repo.Commit.GetTreeEntryByPath(ctx.Repo.TreePath)
if err != nil {
@@ -102,9 +82,6 @@ func RefBlame(ctx *context.Context) {
blob := entry.Blob()
- ctx.Data["LatestCommitStatus"] = models.CalcCommitStatus(statuses)
- ctx.Data["LatestCommitStatuses"] = statuses
-
ctx.Data["Paths"] = paths
ctx.Data["TreeLink"] = treeLink
ctx.Data["TreeNames"] = treeNames
@@ -145,8 +122,33 @@ func RefBlame(ctx *context.Context) {
blameParts = append(blameParts, *blamePart)
}
+ // Get Topics of this repo
+ renderRepoTopics(ctx)
+ if ctx.Written() {
+ return
+ }
+
+ commitNames, previousCommits := processBlameParts(ctx, blameParts)
+ if ctx.Written() {
+ return
+ }
+
+ renderBlame(ctx, blameParts, commitNames, previousCommits)
+
+ ctx.HTML(http.StatusOK, tplBlame)
+}
+
+func processBlameParts(ctx *context.Context, blameParts []git.BlamePart) (map[string]models.UserCommit, map[string]string) {
+ // store commit data by SHA to look up avatar info etc
commitNames := make(map[string]models.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
+ // times, we cache the lookup work locally
commits := list.New()
+ commitCache := map[string]*git.Commit{}
+ commitCache[ctx.Repo.Commit.ID.String()] = ctx.Repo.Commit
for _, part := range blameParts {
sha := part.Sha
@@ -154,14 +156,38 @@ func RefBlame(ctx *context.Context) {
continue
}
- commit, err := ctx.Repo.GitRepo.GetCommit(sha)
- if err != nil {
- if git.IsErrNotExist(err) {
- ctx.NotFound("Repo.GitRepo.GetCommit", err)
- } else {
- ctx.ServerError("Repo.GitRepo.GetCommit", err)
+ // find the blamePart commit, to look up parent & email address for avatars
+ commit, ok := commitCache[sha]
+ var err error
+ if !ok {
+ commit, err = ctx.Repo.GitRepo.GetCommit(sha)
+ if err != nil {
+ if git.IsErrNotExist(err) {
+ ctx.NotFound("Repo.GitRepo.GetCommit", err)
+ } else {
+ ctx.ServerError("Repo.GitRepo.GetCommit", err)
+ }
+ return nil, nil
+ }
+ 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()
+ }
}
- return
}
commits.PushBack(commit)
@@ -169,46 +195,39 @@ func RefBlame(ctx *context.Context) {
commitNames[commit.ID.String()] = models.UserCommit{}
}
+ // populate commit email addresses to later look up avatars.
commits = models.ValidateCommitsWithEmails(commits)
-
for e := commits.Front(); e != nil; e = e.Next() {
c := e.Value.(models.UserCommit)
-
commitNames[c.ID.String()] = c
}
- // Get Topics of this repo
- renderRepoTopics(ctx)
- if ctx.Written() {
- return
- }
-
- renderBlame(ctx, blameParts, commitNames)
-
- ctx.HTML(http.StatusOK, tplBlame)
+ return commitNames, previousCommits
}
-func renderBlame(ctx *context.Context, blameParts []git.BlamePart, commitNames map[string]models.UserCommit) {
+func renderBlame(ctx *context.Context, blameParts []git.BlamePart, commitNames map[string]models.UserCommit, previousCommits map[string]string) {
repoLink := ctx.Repo.RepoLink
var lines = make([]string, 0)
-
- var commitInfo bytes.Buffer
- var lineNumbers bytes.Buffer
- var codeLines bytes.Buffer
+ rows := make([]*blameRow, 0)
var i = 0
- for pi, part := range blameParts {
+ var commitCnt = 0
+ for _, part := range blameParts {
for index, line := range part.Lines {
i++
lines = append(lines, line)
- var attr = ""
- if len(part.Lines)-1 == index && len(blameParts)-1 != pi {
- attr = " bottom-line"
+ br := &blameRow{
+ RowNumber: i,
}
+
commit := commitNames[part.Sha]
+ previousSha := previousCommits[part.Sha]
if index == 0 {
+ // Count commit number
+ commitCnt++
+
// User avatar image
commitSince := timeutil.TimeSinceUnix(timeutil.TimeStamp(commit.Author.When.Unix()), ctx.Data["Lang"].(string))
@@ -219,16 +238,14 @@ func renderBlame(ctx *context.Context, blameParts []git.BlamePart, commitNames m
avatar = string(templates.AvatarByEmail(commit.Author.Email, commit.Author.Name, 18, "mr-3"))
}
- commitInfo.WriteString(fmt.Sprintf(`<div class="blame-info%s"><div class="blame-data"><div class="blame-avatar">%s</div><div class="blame-message"><a href="%s/commit/%s" title="%[5]s">%[5]s</a></div><div class="blame-time">%s</div></div></div>`, attr, avatar, repoLink, part.Sha, html.EscapeString(commit.CommitMessage), commitSince))
- } else {
- commitInfo.WriteString(fmt.Sprintf(`<div class="blame-info%s">&#8203;</div>`, attr))
- }
-
- //Line number
- if len(part.Lines)-1 == index && len(blameParts)-1 != pi {
- lineNumbers.WriteString(fmt.Sprintf(`<span id="L%d" data-line-number="%d" class="bottom-line"></span>`, i, i))
- } else {
- lineNumbers.WriteString(fmt.Sprintf(`<span id="L%d" data-line-number="%d"></span>`, i, i))
+ br.Avatar = gotemplate.HTML(avatar)
+ br.RepoLink = repoLink
+ br.PartSha = part.Sha
+ br.PreviousSha = previousSha
+ br.PreviousShaURL = fmt.Sprintf("%s/blame/commit/%s/%s", repoLink, previousSha, ctx.Repo.TreePath)
+ br.CommitURL = fmt.Sprintf("%s/commit/%s", repoLink, part.Sha)
+ br.CommitMessage = html.EscapeString(commit.CommitMessage)
+ br.CommitSince = commitSince
}
if i != len(lines)-1 {
@@ -236,16 +253,12 @@ func renderBlame(ctx *context.Context, blameParts []git.BlamePart, commitNames m
}
fileName := fmt.Sprintf("%v", ctx.Data["FileName"])
line = highlight.Code(fileName, line)
- line = `<code class="code-inner">` + line + `</code>`
- if len(part.Lines)-1 == index && len(blameParts)-1 != pi {
- codeLines.WriteString(fmt.Sprintf(`<li class="L%d bottom-line" rel="L%d">%s</li>`, i, i, line))
- } else {
- codeLines.WriteString(fmt.Sprintf(`<li class="L%d" rel="L%d">%s</li>`, i, i, line))
- }
+
+ br.Code = gotemplate.HTML(line)
+ rows = append(rows, br)
}
}
- ctx.Data["BlameContent"] = gotemplate.HTML(codeLines.String())
- ctx.Data["BlameCommitInfo"] = gotemplate.HTML(commitInfo.String())
- ctx.Data["BlameLineNums"] = gotemplate.HTML(lineNumbers.String())
+ ctx.Data["BlameRows"] = rows
+ ctx.Data["CommitCnt"] = commitCnt
}