code.gitea.io/gitea@v1.22.3/routers/web/repo/commit.go (about) 1 // Copyright 2014 The Gogs Authors. All rights reserved. 2 // Copyright 2019 The Gitea Authors. All rights reserved. 3 // SPDX-License-Identifier: MIT 4 5 package repo 6 7 import ( 8 "errors" 9 "fmt" 10 "html/template" 11 "net/http" 12 "path" 13 "strings" 14 15 asymkey_model "code.gitea.io/gitea/models/asymkey" 16 "code.gitea.io/gitea/models/db" 17 git_model "code.gitea.io/gitea/models/git" 18 repo_model "code.gitea.io/gitea/models/repo" 19 user_model "code.gitea.io/gitea/models/user" 20 "code.gitea.io/gitea/modules/base" 21 "code.gitea.io/gitea/modules/charset" 22 "code.gitea.io/gitea/modules/git" 23 "code.gitea.io/gitea/modules/gitgraph" 24 "code.gitea.io/gitea/modules/gitrepo" 25 "code.gitea.io/gitea/modules/log" 26 "code.gitea.io/gitea/modules/markup" 27 "code.gitea.io/gitea/modules/setting" 28 "code.gitea.io/gitea/modules/util" 29 "code.gitea.io/gitea/services/context" 30 "code.gitea.io/gitea/services/gitdiff" 31 git_service "code.gitea.io/gitea/services/repository" 32 ) 33 34 const ( 35 tplCommits base.TplName = "repo/commits" 36 tplGraph base.TplName = "repo/graph" 37 tplGraphDiv base.TplName = "repo/graph/div" 38 tplCommitPage base.TplName = "repo/commit_page" 39 ) 40 41 // RefCommits render commits page 42 func RefCommits(ctx *context.Context) { 43 switch { 44 case len(ctx.Repo.TreePath) == 0: 45 Commits(ctx) 46 case ctx.Repo.TreePath == "search": 47 SearchCommits(ctx) 48 default: 49 FileHistory(ctx) 50 } 51 } 52 53 // Commits render branch's commits 54 func Commits(ctx *context.Context) { 55 ctx.Data["PageIsCommits"] = true 56 if ctx.Repo.Commit == nil { 57 ctx.NotFound("Commit not found", nil) 58 return 59 } 60 ctx.Data["PageIsViewCode"] = true 61 62 commitsCount, err := ctx.Repo.GetCommitsCount() 63 if err != nil { 64 ctx.ServerError("GetCommitsCount", err) 65 return 66 } 67 68 page := ctx.FormInt("page") 69 if page <= 1 { 70 page = 1 71 } 72 73 pageSize := ctx.FormInt("limit") 74 if pageSize <= 0 { 75 pageSize = setting.Git.CommitsRangeSize 76 } 77 78 // Both `git log branchName` and `git log commitId` work. 79 commits, err := ctx.Repo.Commit.CommitsByRange(page, pageSize, "") 80 if err != nil { 81 ctx.ServerError("CommitsByRange", err) 82 return 83 } 84 ctx.Data["Commits"] = git_model.ConvertFromGitCommit(ctx, commits, ctx.Repo.Repository) 85 86 ctx.Data["Username"] = ctx.Repo.Owner.Name 87 ctx.Data["Reponame"] = ctx.Repo.Repository.Name 88 ctx.Data["CommitCount"] = commitsCount 89 90 pager := context.NewPagination(int(commitsCount), pageSize, page, 5) 91 pager.SetDefaultParams(ctx) 92 ctx.Data["Page"] = pager 93 94 ctx.HTML(http.StatusOK, tplCommits) 95 } 96 97 // Graph render commit graph - show commits from all branches. 98 func Graph(ctx *context.Context) { 99 ctx.Data["Title"] = ctx.Tr("repo.commit_graph") 100 ctx.Data["PageIsCommits"] = true 101 ctx.Data["PageIsViewCode"] = true 102 mode := strings.ToLower(ctx.FormTrim("mode")) 103 if mode != "monochrome" { 104 mode = "color" 105 } 106 ctx.Data["Mode"] = mode 107 hidePRRefs := ctx.FormBool("hide-pr-refs") 108 ctx.Data["HidePRRefs"] = hidePRRefs 109 branches := ctx.FormStrings("branch") 110 realBranches := make([]string, len(branches)) 111 copy(realBranches, branches) 112 for i, branch := range realBranches { 113 if strings.HasPrefix(branch, "--") { 114 realBranches[i] = git.BranchPrefix + branch 115 } 116 } 117 ctx.Data["SelectedBranches"] = realBranches 118 files := ctx.FormStrings("file") 119 120 commitsCount, err := ctx.Repo.GetCommitsCount() 121 if err != nil { 122 ctx.ServerError("GetCommitsCount", err) 123 return 124 } 125 126 graphCommitsCount, err := ctx.Repo.GetCommitGraphsCount(ctx, hidePRRefs, realBranches, files) 127 if err != nil { 128 log.Warn("GetCommitGraphsCount error for generate graph exclude prs: %t branches: %s in %-v, Will Ignore branches and try again. Underlying Error: %v", hidePRRefs, branches, ctx.Repo.Repository, err) 129 realBranches = []string{} 130 branches = []string{} 131 graphCommitsCount, err = ctx.Repo.GetCommitGraphsCount(ctx, hidePRRefs, realBranches, files) 132 if err != nil { 133 ctx.ServerError("GetCommitGraphsCount", err) 134 return 135 } 136 } 137 138 page := ctx.FormInt("page") 139 140 graph, err := gitgraph.GetCommitGraph(ctx.Repo.GitRepo, page, 0, hidePRRefs, realBranches, files) 141 if err != nil { 142 ctx.ServerError("GetCommitGraph", err) 143 return 144 } 145 146 if err := graph.LoadAndProcessCommits(ctx, ctx.Repo.Repository, ctx.Repo.GitRepo); err != nil { 147 ctx.ServerError("LoadAndProcessCommits", err) 148 return 149 } 150 151 ctx.Data["Graph"] = graph 152 153 gitRefs, err := ctx.Repo.GitRepo.GetRefs() 154 if err != nil { 155 ctx.ServerError("GitRepo.GetRefs", err) 156 return 157 } 158 159 ctx.Data["AllRefs"] = gitRefs 160 161 ctx.Data["Username"] = ctx.Repo.Owner.Name 162 ctx.Data["Reponame"] = ctx.Repo.Repository.Name 163 ctx.Data["CommitCount"] = commitsCount 164 165 paginator := context.NewPagination(int(graphCommitsCount), setting.UI.GraphMaxCommitNum, page, 5) 166 paginator.AddParamString("mode", mode) 167 paginator.AddParamString("hide-pr-refs", fmt.Sprint(hidePRRefs)) 168 for _, branch := range branches { 169 paginator.AddParamString("branch", branch) 170 } 171 for _, file := range files { 172 paginator.AddParamString("file", file) 173 } 174 ctx.Data["Page"] = paginator 175 if ctx.FormBool("div-only") { 176 ctx.HTML(http.StatusOK, tplGraphDiv) 177 return 178 } 179 180 ctx.HTML(http.StatusOK, tplGraph) 181 } 182 183 // SearchCommits render commits filtered by keyword 184 func SearchCommits(ctx *context.Context) { 185 ctx.Data["PageIsCommits"] = true 186 ctx.Data["PageIsViewCode"] = true 187 188 query := ctx.FormTrim("q") 189 if len(query) == 0 { 190 ctx.Redirect(ctx.Repo.RepoLink + "/commits/" + ctx.Repo.BranchNameSubURL()) 191 return 192 } 193 194 all := ctx.FormBool("all") 195 opts := git.NewSearchCommitsOptions(query, all) 196 commits, err := ctx.Repo.Commit.SearchCommits(opts) 197 if err != nil { 198 ctx.ServerError("SearchCommits", err) 199 return 200 } 201 ctx.Data["CommitCount"] = len(commits) 202 ctx.Data["Commits"] = git_model.ConvertFromGitCommit(ctx, commits, ctx.Repo.Repository) 203 204 ctx.Data["Keyword"] = query 205 if all { 206 ctx.Data["All"] = true 207 } 208 ctx.Data["Username"] = ctx.Repo.Owner.Name 209 ctx.Data["Reponame"] = ctx.Repo.Repository.Name 210 ctx.HTML(http.StatusOK, tplCommits) 211 } 212 213 // FileHistory show a file's reversions 214 func FileHistory(ctx *context.Context) { 215 fileName := ctx.Repo.TreePath 216 if len(fileName) == 0 { 217 Commits(ctx) 218 return 219 } 220 221 commitsCount, err := ctx.Repo.GitRepo.FileCommitsCount(ctx.Repo.RefName, fileName) 222 if err != nil { 223 ctx.ServerError("FileCommitsCount", err) 224 return 225 } else if commitsCount == 0 { 226 ctx.NotFound("FileCommitsCount", nil) 227 return 228 } 229 230 page := ctx.FormInt("page") 231 if page <= 1 { 232 page = 1 233 } 234 235 commits, err := ctx.Repo.GitRepo.CommitsByFileAndRange( 236 git.CommitsByFileAndRangeOptions{ 237 Revision: ctx.Repo.RefName, 238 File: fileName, 239 Page: page, 240 }) 241 if err != nil { 242 ctx.ServerError("CommitsByFileAndRange", err) 243 return 244 } 245 ctx.Data["Commits"] = git_model.ConvertFromGitCommit(ctx, commits, ctx.Repo.Repository) 246 247 ctx.Data["Username"] = ctx.Repo.Owner.Name 248 ctx.Data["Reponame"] = ctx.Repo.Repository.Name 249 ctx.Data["FileName"] = fileName 250 ctx.Data["CommitCount"] = commitsCount 251 252 pager := context.NewPagination(int(commitsCount), setting.Git.CommitsRangeSize, page, 5) 253 pager.SetDefaultParams(ctx) 254 ctx.Data["Page"] = pager 255 256 ctx.HTML(http.StatusOK, tplCommits) 257 } 258 259 func LoadBranchesAndTags(ctx *context.Context) { 260 response, err := git_service.LoadBranchesAndTags(ctx, ctx.Repo, ctx.Params("sha")) 261 if err == nil { 262 ctx.JSON(http.StatusOK, response) 263 return 264 } 265 ctx.NotFoundOrServerError(fmt.Sprintf("could not load branches and tags the commit %s belongs to", ctx.Params("sha")), git.IsErrNotExist, err) 266 } 267 268 // Diff show different from current commit to previous commit 269 func Diff(ctx *context.Context) { 270 ctx.Data["PageIsDiff"] = true 271 272 userName := ctx.Repo.Owner.Name 273 repoName := ctx.Repo.Repository.Name 274 commitID := ctx.Params(":sha") 275 var ( 276 gitRepo *git.Repository 277 err error 278 ) 279 280 if ctx.Data["PageIsWiki"] != nil { 281 gitRepo, err = gitrepo.OpenWikiRepository(ctx, ctx.Repo.Repository) 282 if err != nil { 283 ctx.ServerError("Repo.GitRepo.GetCommit", err) 284 return 285 } 286 defer gitRepo.Close() 287 } else { 288 gitRepo = ctx.Repo.GitRepo 289 } 290 291 commit, err := gitRepo.GetCommit(commitID) 292 if err != nil { 293 if git.IsErrNotExist(err) { 294 ctx.NotFound("Repo.GitRepo.GetCommit", err) 295 } else { 296 ctx.ServerError("Repo.GitRepo.GetCommit", err) 297 } 298 return 299 } 300 if len(commitID) != commit.ID.Type().FullLength() { 301 commitID = commit.ID.String() 302 } 303 304 fileOnly := ctx.FormBool("file-only") 305 maxLines, maxFiles := setting.Git.MaxGitDiffLines, setting.Git.MaxGitDiffFiles 306 files := ctx.FormStrings("files") 307 if fileOnly && (len(files) == 2 || len(files) == 1) { 308 maxLines, maxFiles = -1, -1 309 } 310 311 diff, err := gitdiff.GetDiff(ctx, gitRepo, &gitdiff.DiffOptions{ 312 AfterCommitID: commitID, 313 SkipTo: ctx.FormString("skip-to"), 314 MaxLines: maxLines, 315 MaxLineCharacters: setting.Git.MaxGitDiffLineCharacters, 316 MaxFiles: maxFiles, 317 WhitespaceBehavior: gitdiff.GetWhitespaceFlag(ctx.Data["WhitespaceBehavior"].(string)), 318 }, files...) 319 if err != nil { 320 ctx.NotFound("GetDiff", err) 321 return 322 } 323 324 parents := make([]string, commit.ParentCount()) 325 for i := 0; i < commit.ParentCount(); i++ { 326 sha, err := commit.ParentID(i) 327 if err != nil { 328 ctx.NotFound("repo.Diff", err) 329 return 330 } 331 parents[i] = sha.String() 332 } 333 334 ctx.Data["CommitID"] = commitID 335 ctx.Data["AfterCommitID"] = commitID 336 ctx.Data["Username"] = userName 337 ctx.Data["Reponame"] = repoName 338 339 var parentCommit *git.Commit 340 if commit.ParentCount() > 0 { 341 parentCommit, err = gitRepo.GetCommit(parents[0]) 342 if err != nil { 343 ctx.NotFound("GetParentCommit", err) 344 return 345 } 346 } 347 setCompareContext(ctx, parentCommit, commit, userName, repoName) 348 ctx.Data["Title"] = commit.Summary() + " ยท " + base.ShortSha(commitID) 349 ctx.Data["Commit"] = commit 350 ctx.Data["Diff"] = diff 351 352 statuses, _, err := git_model.GetLatestCommitStatus(ctx, ctx.Repo.Repository.ID, commitID, db.ListOptionsAll) 353 if err != nil { 354 log.Error("GetLatestCommitStatus: %v", err) 355 } 356 357 ctx.Data["CommitStatus"] = git_model.CalcCommitStatus(statuses) 358 ctx.Data["CommitStatuses"] = statuses 359 360 verification := asymkey_model.ParseCommitWithSignature(ctx, commit) 361 ctx.Data["Verification"] = verification 362 ctx.Data["Author"] = user_model.ValidateCommitWithEmail(ctx, commit) 363 ctx.Data["Parents"] = parents 364 ctx.Data["DiffNotAvailable"] = diff.NumFiles == 0 365 366 if err := asymkey_model.CalculateTrustStatus(verification, ctx.Repo.Repository.GetTrustModel(), func(user *user_model.User) (bool, error) { 367 return repo_model.IsOwnerMemberCollaborator(ctx, ctx.Repo.Repository, user.ID) 368 }, nil); err != nil { 369 ctx.ServerError("CalculateTrustStatus", err) 370 return 371 } 372 373 note := &git.Note{} 374 err = git.GetNote(ctx, ctx.Repo.GitRepo, commitID, note) 375 if err == nil { 376 ctx.Data["NoteCommit"] = note.Commit 377 ctx.Data["NoteAuthor"] = user_model.ValidateCommitWithEmail(ctx, note.Commit) 378 ctx.Data["NoteRendered"], err = markup.RenderCommitMessage(&markup.RenderContext{ 379 Links: markup.Links{ 380 Base: ctx.Repo.RepoLink, 381 BranchPath: path.Join("commit", util.PathEscapeSegments(commitID)), 382 }, 383 Metas: ctx.Repo.Repository.ComposeMetas(ctx), 384 GitRepo: ctx.Repo.GitRepo, 385 Ctx: ctx, 386 }, template.HTMLEscapeString(string(charset.ToUTF8WithFallback(note.Message, charset.ConvertOpts{})))) 387 if err != nil { 388 ctx.ServerError("RenderCommitMessage", err) 389 return 390 } 391 } 392 393 ctx.Data["BranchName"], err = commit.GetBranchName() 394 if err != nil { 395 ctx.ServerError("commit.GetBranchName", err) 396 return 397 } 398 399 ctx.HTML(http.StatusOK, tplCommitPage) 400 } 401 402 // RawDiff dumps diff results of repository in given commit ID to io.Writer 403 func RawDiff(ctx *context.Context) { 404 var gitRepo *git.Repository 405 if ctx.Data["PageIsWiki"] != nil { 406 wikiRepo, err := gitrepo.OpenWikiRepository(ctx, ctx.Repo.Repository) 407 if err != nil { 408 ctx.ServerError("OpenRepository", err) 409 return 410 } 411 defer wikiRepo.Close() 412 gitRepo = wikiRepo 413 } else { 414 gitRepo = ctx.Repo.GitRepo 415 if gitRepo == nil { 416 ctx.ServerError("GitRepo not open", fmt.Errorf("no open git repo for '%s'", ctx.Repo.Repository.FullName())) 417 return 418 } 419 } 420 if err := git.GetRawDiff( 421 gitRepo, 422 ctx.Params(":sha"), 423 git.RawDiffType(ctx.Params(":ext")), 424 ctx.Resp, 425 ); err != nil { 426 if git.IsErrNotExist(err) { 427 ctx.NotFound("GetRawDiff", 428 errors.New("commit "+ctx.Params(":sha")+" does not exist.")) 429 return 430 } 431 ctx.ServerError("GetRawDiff", err) 432 return 433 } 434 }