diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini
index e679e1e874979..ff28e17feabc6 100644
--- a/options/locale/locale_en-US.ini
+++ b/options/locale/locale_en-US.ini
@@ -735,6 +735,7 @@ delete_preexisting_label = Delete
delete_preexisting = Delete pre-existing files
delete_preexisting_content = Delete files in %s
delete_preexisting_success = Deleted unadopted files in %s
+blame_prior = View blame prior to this change
transfer.accept = Accept Transfer
transfer.accept_desc = Transfer to "%s"
diff --git a/routers/repo/blame.go b/routers/repo/blame.go
index 9be1ea05af9b4..d8e8c93da979b 100644
--- a/routers/repo/blame.go
+++ b/routers/repo/blame.go
@@ -5,7 +5,6 @@
package repo
import (
- "bytes"
"container/list"
"fmt"
"html"
@@ -26,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
@@ -145,6 +158,7 @@ func RefBlame(ctx *context.Context) {
}
commitNames := make(map[string]models.UserCommit)
+ previousCommits := make(map[string]string)
commits := list.New()
for _, part := range blameParts {
@@ -163,6 +177,17 @@ func RefBlame(ctx *context.Context) {
return
}
+ // Get previous closest commit sha from the commit
+ previousCommit, err := commit.Parent(0)
+ if err == nil {
+ // Verify the commit
+ if haz, _ := commit.HasPreviousCommit(previousCommit.ID); haz {
+ if haz1, _ := previousCommit.HasFile(ctx.Repo.TreePath); haz1 {
+ previousCommits[commit.ID.String()] = previousCommit.ID.String()
+ }
+ }
+ }
+
commits.PushBack(commit)
commitNames[commit.ID.String()] = models.UserCommit{}
@@ -182,32 +207,34 @@ func RefBlame(ctx *context.Context) {
return
}
- renderBlame(ctx, blameParts, commitNames)
+ renderBlame(ctx, blameParts, commitNames, previousCommits)
ctx.HTML(200, tplBlame)
}
-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))
@@ -218,16 +245,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(`
`, attr, avatar, repoLink, part.Sha, html.EscapeString(commit.CommitMessage), commitSince))
- } else {
- commitInfo.WriteString(fmt.Sprintf(`
`, attr))
- }
-
- //Line number
- if len(part.Lines)-1 == index && len(blameParts)-1 != pi {
- lineNumbers.WriteString(fmt.Sprintf(``, i, i))
- } else {
- lineNumbers.WriteString(fmt.Sprintf(``, 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 {
@@ -235,16 +260,12 @@ func renderBlame(ctx *context.Context, blameParts []git.BlamePart, commitNames m
}
fileName := fmt.Sprintf("%v", ctx.Data["FileName"])
line = highlight.Code(fileName, line)
- line = `` + line + `
`
- if len(part.Lines)-1 == index && len(blameParts)-1 != pi {
- codeLines.WriteString(fmt.Sprintf(`%s`, i, i, line))
- } else {
- codeLines.WriteString(fmt.Sprintf(`%s`, 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["Codes"] = rows
+ ctx.Data["CommitCnt"] = commitCnt
}
diff --git a/templates/repo/blame.tmpl b/templates/repo/blame.tmpl
index 5dd93d3d4611a..c0a98ef46afe7 100644
--- a/templates/repo/blame.tmpl
+++ b/templates/repo/blame.tmpl
@@ -23,11 +23,40 @@
-
- {{.BlameCommitInfo}} |
- {{.BlameLineNums}} |
- {{.BlameContent}}
|
-
+ {{range $row := .Codes}}
+
+
+
+
+
+ {{$row.Avatar}}
+
+
+
+ {{$row.CommitSince}}
+
+
+
+ |
+
+ {{if $row.PreviousSha}}
+
+ {{svg "octicon-versions"}}
+
+ {{end}}
+ |
+
+
+ |
+
+ {{$row.Code}}
+ |
+
+ {{end}}
diff --git a/web_src/js/index.js b/web_src/js/index.js
index de9b99d4efaad..08e1696f0c5fe 100644
--- a/web_src/js/index.js
+++ b/web_src/js/index.js
@@ -2219,7 +2219,7 @@ function initCodeView() {
const $select = $(this);
let $list;
if ($('div.blame').length) {
- $list = $('.code-view td.lines-code li');
+ $list = $('.code-view td.lines-code.blame-code');
} else {
$list = $('.code-view td.lines-code');
}
@@ -2227,14 +2227,17 @@ function initCodeView() {
deSelect();
// show code view menu marker
- showCodeViewMenu();
+ // not show in blame page
+ if ($('div.blame').length === 0) {
+ showCodeViewMenu();
+ }
});
$(window).on('hashchange', () => {
let m = window.location.hash.match(/^#(L\d+)-(L\d+)$/);
let $list;
if ($('div.blame').length) {
- $list = $('.code-view td.lines-code li');
+ $list = $('.code-view td.lines-code.blame-code');
} else {
$list = $('.code-view td.lines-code');
}
@@ -2244,7 +2247,10 @@ function initCodeView() {
selectRange($list, $first, $list.filter(`[rel=${m[2]}]`));
// show code view menu marker
- showCodeViewMenu();
+ // not show in blame page
+ if ($('div.blame').length === 0) {
+ showCodeViewMenu();
+ }
$('html, body').scrollTop($first.offset().top - 200);
return;
@@ -2255,7 +2261,10 @@ function initCodeView() {
selectRange($list, $first);
// show code view menu marker
- showCodeViewMenu();
+ // not show in blame page
+ if ($('div.blame').length === 0) {
+ showCodeViewMenu();
+ }
$('html, body').scrollTop($first.offset().top - 200);
}
@@ -2824,13 +2833,14 @@ function selectRange($list, $select, $from) {
// add hashchange to permalink
const $issue = $('a.ref-in-new-issue');
- const matched = $issue.attr('href').match(/%23L\d+$|%23L\d+-L\d+$/);
- if (matched) {
- $issue.attr('href', $issue.attr('href').replace($issue.attr('href').substr(matched.index), `%23L${a}-L${b}`));
- } else {
- $issue.attr('href', `${$issue.attr('href')}%23L${a}-L${b}`);
+ if ($issue && $issue.attr('href')) {
+ const matched = $issue.attr('href').match(/%23L\d+$|%23L\d+-L\d+$/);
+ if (matched) {
+ $issue.attr('href', $issue.attr('href').replace($issue.attr('href').substr(matched.index), `%23L${a}-L${b}`));
+ } else {
+ $issue.attr('href', `${$issue.attr('href')}%23L${a}-L${b}`);
+ }
}
-
return;
}
}
@@ -2839,11 +2849,13 @@ function selectRange($list, $select, $from) {
// add hashchange to permalink
const $issue = $('a.ref-in-new-issue');
- const matched = $issue.attr('href').match(/%23L\d+$|%23L\d+-L\d+$/);
- if (matched) {
- $issue.attr('href', $issue.attr('href').replace($issue.attr('href').substr(matched.index), `%23${$select.attr('rel')}`));
- } else {
- $issue.attr('href', `${$issue.attr('href')}%23${$select.attr('rel')}`);
+ if ($issue && $issue.attr('href')) {
+ const matched = $issue.attr('href').match(/%23L\d+$|%23L\d+-L\d+$/);
+ if (matched) {
+ $issue.attr('href', $issue.attr('href').replace($issue.attr('href').substr(matched.index), `%23${$select.attr('rel')}`));
+ } else {
+ $issue.attr('href', `${$issue.attr('href')}%23${$select.attr('rel')}`);
+ }
}
}
diff --git a/web_src/less/_base.less b/web_src/less/_base.less
index 1ce5e9d0ad9d3..f418191fdb9a5 100644
--- a/web_src/less/_base.less
+++ b/web_src/less/_base.less
@@ -1382,6 +1382,14 @@ a.ui.label:hover {
margin-right: 0;
}
+.lines-blame-btn {
+ padding-left: 10px;
+ padding-right: 10px;
+ text-align: right !important;
+ background-color: #f5f5f5;
+ width: 2%;
+}
+
.lines-num {
padding-left: 10px;
padding-right: 10px;
@@ -1510,6 +1518,10 @@ a.ui.label:hover {
}
}
+.bottom-line-blame {
+ border-bottom: 1px solid var(--color-secondary);
+}
+
.lines-code,
.lines-commit {
.bottom-line {