55package repo
66
77import (
8- "bytes"
98 "container/list"
109 "fmt"
1110 "html"
@@ -18,7 +17,6 @@ import (
1817 "code.gitea.io/gitea/modules/context"
1918 "code.gitea.io/gitea/modules/git"
2019 "code.gitea.io/gitea/modules/highlight"
21- "code.gitea.io/gitea/modules/log"
2220 "code.gitea.io/gitea/modules/templates"
2321 "code.gitea.io/gitea/modules/timeutil"
2422)
@@ -27,6 +25,20 @@ const (
2725 tplBlame base.TplName = "repo/home"
2826)
2927
28+ type blameRow struct {
29+ RowNumber int
30+ Avatar gotemplate.HTML
31+ RepoLink string
32+ PartSha string
33+ PreviousSha string
34+ PreviousShaURL string
35+ IsFirstCommit bool
36+ CommitURL string
37+ CommitMessage string
38+ CommitSince gotemplate.HTML
39+ Code gotemplate.HTML
40+ }
41+
3042// RefBlame render blame page
3143func RefBlame (ctx * context.Context ) {
3244 fileName := ctx .Repo .TreePath
@@ -39,19 +51,6 @@ func RefBlame(ctx *context.Context) {
3951 repoName := ctx .Repo .Repository .Name
4052 commitID := ctx .Repo .CommitID
4153
42- commit , err := ctx .Repo .GitRepo .GetCommit (commitID )
43- if err != nil {
44- if git .IsErrNotExist (err ) {
45- ctx .NotFound ("Repo.GitRepo.GetCommit" , err )
46- } else {
47- ctx .ServerError ("Repo.GitRepo.GetCommit" , err )
48- }
49- return
50- }
51- if len (commitID ) != 40 {
52- commitID = commit .ID .String ()
53- }
54-
5554 branchLink := ctx .Repo .RepoLink + "/src/" + ctx .Repo .BranchNameSubURL ()
5655 treeLink := branchLink
5756 rawLink := ctx .Repo .RepoLink + "/raw/" + ctx .Repo .BranchNameSubURL ()
@@ -74,25 +73,6 @@ func RefBlame(ctx *context.Context) {
7473 }
7574 }
7675
77- // Show latest commit info of repository in table header,
78- // or of directory if not in root directory.
79- latestCommit := ctx .Repo .Commit
80- if len (ctx .Repo .TreePath ) > 0 {
81- latestCommit , err = ctx .Repo .Commit .GetCommitByPath (ctx .Repo .TreePath )
82- if err != nil {
83- ctx .ServerError ("GetCommitByPath" , err )
84- return
85- }
86- }
87- ctx .Data ["LatestCommit" ] = latestCommit
88- ctx .Data ["LatestCommitVerification" ] = models .ParseCommitWithSignature (latestCommit )
89- ctx .Data ["LatestCommitUser" ] = models .ValidateCommitWithEmail (latestCommit )
90-
91- statuses , err := models .GetLatestCommitStatus (ctx .Repo .Repository .ID , ctx .Repo .Commit .ID .String (), models.ListOptions {})
92- if err != nil {
93- log .Error ("GetLatestCommitStatus: %v" , err )
94- }
95-
9676 // Get current entry user currently looking at.
9777 entry , err := ctx .Repo .Commit .GetTreeEntryByPath (ctx .Repo .TreePath )
9878 if err != nil {
@@ -102,9 +82,6 @@ func RefBlame(ctx *context.Context) {
10282
10383 blob := entry .Blob ()
10484
105- ctx .Data ["LatestCommitStatus" ] = models .CalcCommitStatus (statuses )
106- ctx .Data ["LatestCommitStatuses" ] = statuses
107-
10885 ctx .Data ["Paths" ] = paths
10986 ctx .Data ["TreeLink" ] = treeLink
11087 ctx .Data ["TreeNames" ] = treeNames
@@ -145,70 +122,112 @@ func RefBlame(ctx *context.Context) {
145122 blameParts = append (blameParts , * blamePart )
146123 }
147124
125+ // Get Topics of this repo
126+ renderRepoTopics (ctx )
127+ if ctx .Written () {
128+ return
129+ }
130+
131+ commitNames , previousCommits := processBlameParts (ctx , blameParts )
132+ if ctx .Written () {
133+ return
134+ }
135+
136+ renderBlame (ctx , blameParts , commitNames , previousCommits )
137+
138+ ctx .HTML (http .StatusOK , tplBlame )
139+ }
140+
141+ func processBlameParts (ctx * context.Context , blameParts []git.BlamePart ) (map [string ]models.UserCommit , map [string ]string ) {
142+ // store commit data by SHA to look up avatar info etc
148143 commitNames := make (map [string ]models.UserCommit )
144+ // previousCommits contains links from SHA to parent SHA,
145+ // if parent also contains the current TreePath.
146+ previousCommits := make (map [string ]string )
147+ // and as blameParts can reference the same commits multiple
148+ // times, we cache the lookup work locally
149149 commits := list .New ()
150+ commitCache := map [string ]* git.Commit {}
151+ commitCache [ctx .Repo .Commit .ID .String ()] = ctx .Repo .Commit
150152
151153 for _ , part := range blameParts {
152154 sha := part .Sha
153155 if _ , ok := commitNames [sha ]; ok {
154156 continue
155157 }
156158
157- commit , err := ctx .Repo .GitRepo .GetCommit (sha )
158- if err != nil {
159- if git .IsErrNotExist (err ) {
160- ctx .NotFound ("Repo.GitRepo.GetCommit" , err )
161- } else {
162- ctx .ServerError ("Repo.GitRepo.GetCommit" , err )
159+ // find the blamePart commit, to look up parent & email address for avatars
160+ commit , ok := commitCache [sha ]
161+ var err error
162+ if ! ok {
163+ commit , err = ctx .Repo .GitRepo .GetCommit (sha )
164+ if err != nil {
165+ if git .IsErrNotExist (err ) {
166+ ctx .NotFound ("Repo.GitRepo.GetCommit" , err )
167+ } else {
168+ ctx .ServerError ("Repo.GitRepo.GetCommit" , err )
169+ }
170+ return nil , nil
171+ }
172+ commitCache [sha ] = commit
173+ }
174+
175+ // find parent commit
176+ if commit .ParentCount () > 0 {
177+ psha := commit .Parents [0 ]
178+ previousCommit , ok := commitCache [psha .String ()]
179+ if ! ok {
180+ previousCommit , _ = commit .Parent (0 )
181+ if previousCommit != nil {
182+ commitCache [psha .String ()] = previousCommit
183+ }
184+ }
185+ // only store parent commit ONCE, if it has the file
186+ if previousCommit != nil {
187+ if haz1 , _ := previousCommit .HasFile (ctx .Repo .TreePath ); haz1 {
188+ previousCommits [commit .ID .String ()] = previousCommit .ID .String ()
189+ }
163190 }
164- return
165191 }
166192
167193 commits .PushBack (commit )
168194
169195 commitNames [commit .ID .String ()] = models.UserCommit {}
170196 }
171197
198+ // populate commit email addresses to later look up avatars.
172199 commits = models .ValidateCommitsWithEmails (commits )
173-
174200 for e := commits .Front (); e != nil ; e = e .Next () {
175201 c := e .Value .(models.UserCommit )
176-
177202 commitNames [c .ID .String ()] = c
178203 }
179204
180- // Get Topics of this repo
181- renderRepoTopics (ctx )
182- if ctx .Written () {
183- return
184- }
185-
186- renderBlame (ctx , blameParts , commitNames )
187-
188- ctx .HTML (http .StatusOK , tplBlame )
205+ return commitNames , previousCommits
189206}
190207
191- func renderBlame (ctx * context.Context , blameParts []git.BlamePart , commitNames map [string ]models.UserCommit ) {
208+ func renderBlame (ctx * context.Context , blameParts []git.BlamePart , commitNames map [string ]models.UserCommit , previousCommits map [ string ] string ) {
192209 repoLink := ctx .Repo .RepoLink
193210
194211 var lines = make ([]string , 0 )
195-
196- var commitInfo bytes.Buffer
197- var lineNumbers bytes.Buffer
198- var codeLines bytes.Buffer
212+ rows := make ([]* blameRow , 0 )
199213
200214 var i = 0
201- for pi , part := range blameParts {
215+ var commitCnt = 0
216+ for _ , part := range blameParts {
202217 for index , line := range part .Lines {
203218 i ++
204219 lines = append (lines , line )
205220
206- var attr = ""
207- if len (part .Lines )- 1 == index && len (blameParts )- 1 != pi {
208- attr = " bottom-line"
221+ br := & blameRow {
222+ RowNumber : i ,
209223 }
224+
210225 commit := commitNames [part .Sha ]
226+ previousSha := previousCommits [part .Sha ]
211227 if index == 0 {
228+ // Count commit number
229+ commitCnt ++
230+
212231 // User avatar image
213232 commitSince := timeutil .TimeSinceUnix (timeutil .TimeStamp (commit .Author .When .Unix ()), ctx .Data ["Lang" ].(string ))
214233
@@ -219,33 +238,27 @@ func renderBlame(ctx *context.Context, blameParts []git.BlamePart, commitNames m
219238 avatar = string (templates .AvatarByEmail (commit .Author .Email , commit .Author .Name , 18 , "mr-3" ))
220239 }
221240
222- 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 ))
223- } else {
224- commitInfo .WriteString (fmt .Sprintf (`<div class="blame-info%s">​</div>` , attr ))
225- }
226-
227- //Line number
228- if len (part .Lines )- 1 == index && len (blameParts )- 1 != pi {
229- lineNumbers .WriteString (fmt .Sprintf (`<span id="L%d" data-line-number="%d" class="bottom-line"></span>` , i , i ))
230- } else {
231- lineNumbers .WriteString (fmt .Sprintf (`<span id="L%d" data-line-number="%d"></span>` , i , i ))
241+ br .Avatar = gotemplate .HTML (avatar )
242+ br .RepoLink = repoLink
243+ br .PartSha = part .Sha
244+ br .PreviousSha = previousSha
245+ br .PreviousShaURL = fmt .Sprintf ("%s/blame/commit/%s/%s" , repoLink , previousSha , ctx .Repo .TreePath )
246+ br .CommitURL = fmt .Sprintf ("%s/commit/%s" , repoLink , part .Sha )
247+ br .CommitMessage = html .EscapeString (commit .CommitMessage )
248+ br .CommitSince = commitSince
232249 }
233250
234251 if i != len (lines )- 1 {
235252 line += "\n "
236253 }
237254 fileName := fmt .Sprintf ("%v" , ctx .Data ["FileName" ])
238255 line = highlight .Code (fileName , line )
239- line = `<code class="code-inner">` + line + `</code>`
240- if len (part .Lines )- 1 == index && len (blameParts )- 1 != pi {
241- codeLines .WriteString (fmt .Sprintf (`<li class="L%d bottom-line" rel="L%d">%s</li>` , i , i , line ))
242- } else {
243- codeLines .WriteString (fmt .Sprintf (`<li class="L%d" rel="L%d">%s</li>` , i , i , line ))
244- }
256+
257+ br .Code = gotemplate .HTML (line )
258+ rows = append (rows , br )
245259 }
246260 }
247261
248- ctx .Data ["BlameContent" ] = gotemplate .HTML (codeLines .String ())
249- ctx .Data ["BlameCommitInfo" ] = gotemplate .HTML (commitInfo .String ())
250- ctx .Data ["BlameLineNums" ] = gotemplate .HTML (lineNumbers .String ())
262+ ctx .Data ["BlameRows" ] = rows
263+ ctx .Data ["CommitCnt" ] = commitCnt
251264}
0 commit comments