2019-04-20 04:47:00 +02:00
// Copyright 2019 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package repo
import (
"container/list"
"fmt"
"html"
gotemplate "html/template"
2021-04-05 17:30:52 +02:00
"net/http"
2019-04-20 04:47:00 +02:00
"strings"
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/highlight"
2020-12-03 19:46:11 +01:00
"code.gitea.io/gitea/modules/templates"
2019-08-15 22:46:21 +08:00
"code.gitea.io/gitea/modules/timeutil"
2019-04-20 04:47:00 +02:00
)
const (
tplBlame base . TplName = "repo/home"
)
2021-06-28 01:13:20 +02:00
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
}
2019-04-20 04:47:00 +02:00
// RefBlame render blame page
func RefBlame ( ctx * context . Context ) {
fileName := ctx . Repo . TreePath
if len ( fileName ) == 0 {
ctx . NotFound ( "Blame FileName" , nil )
return
}
userName := ctx . Repo . Owner . Name
repoName := ctx . Repo . Repository . Name
commitID := ctx . Repo . CommitID
branchLink := ctx . Repo . RepoLink + "/src/" + ctx . Repo . BranchNameSubURL ( )
treeLink := branchLink
rawLink := ctx . Repo . RepoLink + "/raw/" + ctx . Repo . BranchNameSubURL ( )
if len ( ctx . Repo . TreePath ) > 0 {
treeLink += "/" + ctx . Repo . TreePath
}
var treeNames [ ] string
paths := make ( [ ] string , 0 , 5 )
if len ( ctx . Repo . TreePath ) > 0 {
treeNames = strings . Split ( ctx . Repo . TreePath , "/" )
for i := range treeNames {
paths = append ( paths , strings . Join ( treeNames [ : i + 1 ] , "/" ) )
}
ctx . Data [ "HasParentPath" ] = true
if len ( paths ) - 2 >= 0 {
ctx . Data [ "ParentPath" ] = "/" + paths [ len ( paths ) - 1 ]
}
}
// Get current entry user currently looking at.
entry , err := ctx . Repo . Commit . GetTreeEntryByPath ( ctx . Repo . TreePath )
if err != nil {
ctx . NotFoundOrServerError ( "Repo.Commit.GetTreeEntryByPath" , git . IsErrNotExist , err )
return
}
blob := entry . Blob ( )
ctx . Data [ "Paths" ] = paths
ctx . Data [ "TreeLink" ] = treeLink
ctx . Data [ "TreeNames" ] = treeNames
ctx . Data [ "BranchLink" ] = branchLink
2020-06-30 17:34:03 -04:00
2019-04-20 04:47:00 +02:00
ctx . Data [ "RawFileLink" ] = rawLink + "/" + ctx . Repo . TreePath
ctx . Data [ "PageIsViewCode" ] = true
ctx . Data [ "IsBlame" ] = true
ctx . Data [ "FileSize" ] = blob . Size ( )
ctx . Data [ "FileName" ] = blob . Name ( )
2020-06-15 20:39:39 +02:00
ctx . Data [ "NumLines" ] , err = blob . GetBlobLineCount ( )
if err != nil {
ctx . NotFound ( "GetBlobLineCount" , err )
return
}
2021-05-31 07:18:11 +01:00
blameReader , err := git . CreateBlameReader ( ctx , models . RepoPath ( userName , repoName ) , commitID , fileName )
2019-04-20 04:47:00 +02:00
if err != nil {
ctx . NotFound ( "CreateBlameReader" , err )
return
}
defer blameReader . Close ( )
2019-06-27 02:15:26 +08:00
blameParts := make ( [ ] git . BlamePart , 0 )
2019-04-20 04:47:00 +02:00
for {
blamePart , err := blameReader . NextPart ( )
if err != nil {
ctx . NotFound ( "NextPart" , err )
return
}
if blamePart == nil {
break
}
blameParts = append ( blameParts , * blamePart )
}
2021-06-28 01:13:20 +02:00
// 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
2019-04-20 04:47:00 +02:00
commitNames := make ( map [ string ] models . UserCommit )
2021-06-28 01:13:20 +02:00
// 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
2019-04-20 04:47:00 +02:00
commits := list . New ( )
2021-06-28 01:13:20 +02:00
commitCache := map [ string ] * git . Commit { }
commitCache [ ctx . Repo . Commit . ID . String ( ) ] = ctx . Repo . Commit
2019-04-20 04:47:00 +02:00
for _ , part := range blameParts {
sha := part . Sha
if _ , ok := commitNames [ sha ] ; ok {
continue
}
2021-06-28 01:13:20 +02:00
// 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 ( )
}
2019-04-20 04:47:00 +02:00
}
}
commits . PushBack ( commit )
commitNames [ commit . ID . String ( ) ] = models . UserCommit { }
}
2021-06-28 01:13:20 +02:00
// populate commit email addresses to later look up avatars.
2019-04-20 04:47:00 +02:00
commits = models . ValidateCommitsWithEmails ( commits )
for e := commits . Front ( ) ; e != nil ; e = e . Next ( ) {
c := e . Value . ( models . UserCommit )
commitNames [ c . ID . String ( ) ] = c
}
2021-06-28 01:13:20 +02:00
return commitNames , previousCommits
2019-04-20 04:47:00 +02:00
}
2021-06-28 01:13:20 +02:00
func renderBlame ( ctx * context . Context , blameParts [ ] git . BlamePart , commitNames map [ string ] models . UserCommit , previousCommits map [ string ] string ) {
2019-04-20 04:47:00 +02:00
repoLink := ctx . Repo . RepoLink
2019-06-12 21:41:28 +02:00
var lines = make ( [ ] string , 0 )
2021-06-28 01:13:20 +02:00
rows := make ( [ ] * blameRow , 0 )
2019-04-20 04:47:00 +02:00
var i = 0
2021-06-28 01:13:20 +02:00
var commitCnt = 0
for _ , part := range blameParts {
2019-04-20 04:47:00 +02:00
for index , line := range part . Lines {
i ++
lines = append ( lines , line )
2021-06-28 01:13:20 +02:00
br := & blameRow {
RowNumber : i ,
2019-04-20 04:47:00 +02:00
}
2021-06-28 01:13:20 +02:00
2019-04-20 04:47:00 +02:00
commit := commitNames [ part . Sha ]
2021-06-28 01:13:20 +02:00
previousSha := previousCommits [ part . Sha ]
2019-04-20 04:47:00 +02:00
if index == 0 {
2021-06-28 01:13:20 +02:00
// Count commit number
commitCnt ++
2019-04-20 04:47:00 +02:00
// User avatar image
2019-08-15 22:46:21 +08:00
commitSince := timeutil . TimeSinceUnix ( timeutil . TimeStamp ( commit . Author . When . Unix ( ) ) , ctx . Data [ "Lang" ] . ( string ) )
2020-12-03 19:46:11 +01:00
var avatar string
2019-04-20 04:47:00 +02:00
if commit . User != nil {
2020-12-03 19:46:11 +01:00
avatar = string ( templates . Avatar ( commit . User , 18 , "mr-3" ) )
2019-04-20 04:47:00 +02:00
} else {
2020-12-03 19:46:11 +01:00
avatar = string ( templates . AvatarByEmail ( commit . Author . Email , commit . Author . Name , 18 , "mr-3" ) )
2019-04-20 04:47:00 +02:00
}
2020-12-03 19:46:11 +01:00
2021-06-28 01:13:20 +02:00
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
2019-04-20 04:47:00 +02:00
}
2019-05-11 23:27:39 +03:00
if i != len ( lines ) - 1 {
2019-04-20 04:47:00 +02:00
line += "\n"
}
2020-06-30 17:34:03 -04:00
fileName := fmt . Sprintf ( "%v" , ctx . Data [ "FileName" ] )
line = highlight . Code ( fileName , line )
2021-06-28 01:13:20 +02:00
br . Code = gotemplate . HTML ( line )
rows = append ( rows , br )
2019-04-20 04:47:00 +02:00
}
}
2021-06-28 01:13:20 +02:00
ctx . Data [ "BlameRows" ] = rows
ctx . Data [ "CommitCnt" ] = commitCnt
2019-04-20 04:47:00 +02:00
}