diff options
author | Norwin <noerw@users.noreply.github.com> | 2021-06-28 01:13:20 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-06-28 01:13:20 +0200 |
commit | 9c6aeb47f7979a434bf30408992c06118c142771 (patch) | |
tree | 0aed39da360583610dee02a2d96a7a43b022d45d /routers | |
parent | 59c58553baa5597f5c2156b47fd2e47e46aa58ac (diff) | |
download | gitea-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.go | 179 |
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">​</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 } |