@ -5,7 +5,6 @@
package repo
package repo
import (
import (
"bytes"
"container/list"
"container/list"
"fmt"
"fmt"
"html"
"html"
@ -18,7 +17,6 @@ import (
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/highlight"
"code.gitea.io/gitea/modules/highlight"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/templates"
"code.gitea.io/gitea/modules/templates"
"code.gitea.io/gitea/modules/timeutil"
"code.gitea.io/gitea/modules/timeutil"
)
)
@ -27,6 +25,20 @@ const (
tplBlame base . TplName = "repo/home"
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
// RefBlame render blame page
func RefBlame ( ctx * context . Context ) {
func RefBlame ( ctx * context . Context ) {
fileName := ctx . Repo . TreePath
fileName := ctx . Repo . TreePath
@ -39,19 +51,6 @@ func RefBlame(ctx *context.Context) {
repoName := ctx . Repo . Repository . Name
repoName := ctx . Repo . Repository . Name
commitID := ctx . Repo . CommitID
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 ( )
branchLink := ctx . Repo . RepoLink + "/src/" + ctx . Repo . BranchNameSubURL ( )
treeLink := branchLink
treeLink := branchLink
rawLink := ctx . Repo . RepoLink + "/raw/" + ctx . Repo . BranchNameSubURL ( )
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.
// Get current entry user currently looking at.
entry , err := ctx . Repo . Commit . GetTreeEntryByPath ( ctx . Repo . TreePath )
entry , err := ctx . Repo . Commit . GetTreeEntryByPath ( ctx . Repo . TreePath )
if err != nil {
if err != nil {
@ -102,9 +82,6 @@ func RefBlame(ctx *context.Context) {
blob := entry . Blob ( )
blob := entry . Blob ( )
ctx . Data [ "LatestCommitStatus" ] = models . CalcCommitStatus ( statuses )
ctx . Data [ "LatestCommitStatuses" ] = statuses
ctx . Data [ "Paths" ] = paths
ctx . Data [ "Paths" ] = paths
ctx . Data [ "TreeLink" ] = treeLink
ctx . Data [ "TreeLink" ] = treeLink
ctx . Data [ "TreeNames" ] = treeNames
ctx . Data [ "TreeNames" ] = treeNames
@ -145,8 +122,33 @@ func RefBlame(ctx *context.Context) {
blameParts = append ( blameParts , * blamePart )
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 )
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 ( )
commits := list . New ( )
commitCache := map [ string ] * git . Commit { }
commitCache [ ctx . Repo . Commit . ID . String ( ) ] = ctx . Repo . Commit
for _ , part := range blameParts {
for _ , part := range blameParts {
sha := part . Sha
sha := part . Sha
@ -154,14 +156,38 @@ func RefBlame(ctx *context.Context) {
continue
continue
}
}
commit , err := ctx . Repo . GitRepo . GetCommit ( sha )
// find the blamePart commit, to look up parent & email address for avatars
if err != nil {
commit , ok := commitCache [ sha ]
if git . IsErrNotExist ( err ) {
var err error
ctx . NotFound ( "Repo.GitRepo.GetCommit" , err )
if ! ok {
} else {
commit , err = ctx . Repo . GitRepo . GetCommit ( sha )
ctx . ServerError ( "Repo.GitRepo.GetCommit" , err )
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 )
commits . PushBack ( commit )
@ -169,46 +195,39 @@ func RefBlame(ctx *context.Context) {
commitNames [ commit . ID . String ( ) ] = models . UserCommit { }
commitNames [ commit . ID . String ( ) ] = models . UserCommit { }
}
}
// populate commit email addresses to later look up avatars.
commits = models . ValidateCommitsWithEmails ( commits )
commits = models . ValidateCommitsWithEmails ( commits )
for e := commits . Front ( ) ; e != nil ; e = e . Next ( ) {
for e := commits . Front ( ) ; e != nil ; e = e . Next ( ) {
c := e . Value . ( models . UserCommit )
c := e . Value . ( models . UserCommit )
commitNames [ c . ID . String ( ) ] = c
commitNames [ c . ID . String ( ) ] = c
}
}
// Get Topics of this repo
return commitNames , previousCommits
renderRepoTopics ( ctx )
if ctx . Written ( ) {
return
}
renderBlame ( ctx , blameParts , commitNames )
ctx . HTML ( http . StatusOK , 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
repoLink := ctx . Repo . RepoLink
var lines = make ( [ ] string , 0 )
var lines = make ( [ ] string , 0 )
rows := make ( [ ] * blameRow , 0 )
var commitInfo bytes . Buffer
var lineNumbers bytes . Buffer
var codeLines bytes . Buffer
var i = 0
var i = 0
for pi , part := range blameParts {
var commitCnt = 0
for _ , part := range blameParts {
for index , line := range part . Lines {
for index , line := range part . Lines {
i ++
i ++
lines = append ( lines , line )
lines = append ( lines , line )
var attr = ""
br := & blameRow {
if len ( part . Lines ) - 1 == index && len ( blameParts ) - 1 != pi {
RowNumber : i ,
attr = " bottom-line"
}
}
commit := commitNames [ part . Sha ]
commit := commitNames [ part . Sha ]
previousSha := previousCommits [ part . Sha ]
if index == 0 {
if index == 0 {
// Count commit number
commitCnt ++
// User avatar image
// User avatar image
commitSince := timeutil . TimeSinceUnix ( timeutil . TimeStamp ( commit . Author . When . Unix ( ) ) , ctx . Data [ "Lang" ] . ( string ) )
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" ) )
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 ) )
br . Avatar = gotemplate . HTML ( avatar )
} else {
br . RepoLink = repoLink
commitInfo . WriteString ( fmt . Sprintf ( ` <div class="blame-info%s">​</div> ` , attr ) )
br . PartSha = part . Sha
}
br . PreviousSha = previousSha
br . PreviousShaURL = fmt . Sprintf ( "%s/blame/commit/%s/%s" , repoLink , previousSha , ctx . Repo . TreePath )
//Line number
br . CommitURL = fmt . Sprintf ( "%s/commit/%s" , repoLink , part . Sha )
if len ( part . Lines ) - 1 == index && len ( blameParts ) - 1 != pi {
br . CommitMessage = html . EscapeString ( commit . CommitMessage )
lineNumbers . WriteString ( fmt . Sprintf ( ` <span id="L%d" data-line-number="%d" class="bottom-line"></span> ` , i , i ) )
br . CommitSince = commitSince
} else {
lineNumbers . WriteString ( fmt . Sprintf ( ` <span id="L%d" data-line-number="%d"></span> ` , i , i ) )
}
}
if i != len ( lines ) - 1 {
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" ] )
fileName := fmt . Sprintf ( "%v" , ctx . Data [ "FileName" ] )
line = highlight . Code ( fileName , line )
line = highlight . Code ( fileName , line )
line = ` <code class="code-inner"> ` + line + ` </code> `
if len ( part . Lines ) - 1 == index && len ( blameParts ) - 1 != pi {
br . Code = gotemplate . HTML ( line )
codeLines . WriteString ( fmt . Sprintf ( ` <li class="L%d bottom-line" rel="L%d">%s</li> ` , i , i , line ) )
rows = append ( rows , br )
} else {
codeLines . WriteString ( fmt . Sprintf ( ` <li class="L%d" rel="L%d">%s</li> ` , i , i , line ) )
}
}
}
}
}
ctx . Data [ "BlameContent" ] = gotemplate . HTML ( codeLines . String ( ) )
ctx . Data [ "BlameRows" ] = rows
ctx . Data [ "BlameCommitInfo" ] = gotemplate . HTML ( commitInfo . String ( ) )
ctx . Data [ "CommitCnt" ] = commitCnt
ctx . Data [ "BlameLineNums" ] = gotemplate . HTML ( lineNumbers . String ( ) )
}
}