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