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(`
%s
%s
`, 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 @@
    - - - - - + {{range $row := .Codes}} + + + + + + + {{end}}
    {{.BlameCommitInfo}}{{.BlameLineNums}}
      {{.BlameContent}}
    +
    +
    +
    + {{$row.Avatar}} +
    + +
    + {{$row.CommitSince}} +
    +
    +
    +
    + {{if $row.PreviousSha}} + + {{svg "octicon-versions"}} + + {{end}} + + + + {{$row.Code}} +
    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 {