code.gitea.io/gitea@v1.19.3/modules/context/repo.go (about) 1 // Copyright 2014 The Gogs Authors. All rights reserved. 2 // Copyright 2017 The Gitea Authors. All rights reserved. 3 // SPDX-License-Identifier: MIT 4 5 package context 6 7 import ( 8 "context" 9 "fmt" 10 "html" 11 "net/http" 12 "net/url" 13 "path" 14 "strings" 15 16 "code.gitea.io/gitea/models" 17 "code.gitea.io/gitea/models/db" 18 git_model "code.gitea.io/gitea/models/git" 19 issues_model "code.gitea.io/gitea/models/issues" 20 access_model "code.gitea.io/gitea/models/perm/access" 21 repo_model "code.gitea.io/gitea/models/repo" 22 unit_model "code.gitea.io/gitea/models/unit" 23 user_model "code.gitea.io/gitea/models/user" 24 "code.gitea.io/gitea/modules/cache" 25 "code.gitea.io/gitea/modules/git" 26 code_indexer "code.gitea.io/gitea/modules/indexer/code" 27 "code.gitea.io/gitea/modules/issue/template" 28 "code.gitea.io/gitea/modules/log" 29 repo_module "code.gitea.io/gitea/modules/repository" 30 "code.gitea.io/gitea/modules/setting" 31 api "code.gitea.io/gitea/modules/structs" 32 "code.gitea.io/gitea/modules/util" 33 asymkey_service "code.gitea.io/gitea/services/asymkey" 34 35 "github.com/editorconfig/editorconfig-core-go/v2" 36 ) 37 38 // IssueTemplateDirCandidates issue templates directory 39 var IssueTemplateDirCandidates = []string{ 40 "ISSUE_TEMPLATE", 41 "issue_template", 42 ".gitea/ISSUE_TEMPLATE", 43 ".gitea/issue_template", 44 ".github/ISSUE_TEMPLATE", 45 ".github/issue_template", 46 ".gitlab/ISSUE_TEMPLATE", 47 ".gitlab/issue_template", 48 } 49 50 // PullRequest contains information to make a pull request 51 type PullRequest struct { 52 BaseRepo *repo_model.Repository 53 Allowed bool 54 SameRepo bool 55 HeadInfoSubURL string // [<user>:]<branch> url segment 56 } 57 58 // Repository contains information to operate a repository 59 type Repository struct { 60 access_model.Permission 61 IsWatching bool 62 IsViewBranch bool 63 IsViewTag bool 64 IsViewCommit bool 65 Repository *repo_model.Repository 66 Owner *user_model.User 67 Commit *git.Commit 68 Tag *git.Tag 69 GitRepo *git.Repository 70 RefName string 71 BranchName string 72 TagName string 73 TreePath string 74 CommitID string 75 RepoLink string 76 CloneLink repo_model.CloneLink 77 CommitsCount int64 78 Mirror *repo_model.Mirror 79 80 PullRequest *PullRequest 81 } 82 83 // CanWriteToBranch checks if the branch is writable by the user 84 func (r *Repository) CanWriteToBranch(user *user_model.User, branch string) bool { 85 return issues_model.CanMaintainerWriteToBranch(r.Permission, branch, user) 86 } 87 88 // CanEnableEditor returns true if repository is editable and user has proper access level. 89 func (r *Repository) CanEnableEditor(user *user_model.User) bool { 90 return r.IsViewBranch && r.CanWriteToBranch(user, r.BranchName) && r.Repository.CanEnableEditor() && !r.Repository.IsArchived 91 } 92 93 // CanCreateBranch returns true if repository is editable and user has proper access level. 94 func (r *Repository) CanCreateBranch() bool { 95 return r.Permission.CanWrite(unit_model.TypeCode) && r.Repository.CanCreateBranch() 96 } 97 98 // RepoMustNotBeArchived checks if a repo is archived 99 func RepoMustNotBeArchived() func(ctx *Context) { 100 return func(ctx *Context) { 101 if ctx.Repo.Repository.IsArchived { 102 ctx.NotFound("IsArchived", fmt.Errorf(ctx.Tr("repo.archive.title"))) 103 } 104 } 105 } 106 107 // CanCommitToBranchResults represents the results of CanCommitToBranch 108 type CanCommitToBranchResults struct { 109 CanCommitToBranch bool 110 EditorEnabled bool 111 UserCanPush bool 112 RequireSigned bool 113 WillSign bool 114 SigningKey string 115 WontSignReason string 116 } 117 118 // CanCommitToBranch returns true if repository is editable and user has proper access level 119 // 120 // and branch is not protected for push 121 func (r *Repository) CanCommitToBranch(ctx context.Context, doer *user_model.User) (CanCommitToBranchResults, error) { 122 protectedBranch, err := git_model.GetFirstMatchProtectedBranchRule(ctx, r.Repository.ID, r.BranchName) 123 if err != nil { 124 return CanCommitToBranchResults{}, err 125 } 126 userCanPush := true 127 requireSigned := false 128 if protectedBranch != nil { 129 protectedBranch.Repo = r.Repository 130 userCanPush = protectedBranch.CanUserPush(ctx, doer) 131 requireSigned = protectedBranch.RequireSignedCommits 132 } 133 134 sign, keyID, _, err := asymkey_service.SignCRUDAction(ctx, r.Repository.RepoPath(), doer, r.Repository.RepoPath(), git.BranchPrefix+r.BranchName) 135 136 canCommit := r.CanEnableEditor(doer) && userCanPush 137 if requireSigned { 138 canCommit = canCommit && sign 139 } 140 wontSignReason := "" 141 if err != nil { 142 if asymkey_service.IsErrWontSign(err) { 143 wontSignReason = string(err.(*asymkey_service.ErrWontSign).Reason) 144 err = nil 145 } else { 146 wontSignReason = "error" 147 } 148 } 149 150 return CanCommitToBranchResults{ 151 CanCommitToBranch: canCommit, 152 EditorEnabled: r.CanEnableEditor(doer), 153 UserCanPush: userCanPush, 154 RequireSigned: requireSigned, 155 WillSign: sign, 156 SigningKey: keyID, 157 WontSignReason: wontSignReason, 158 }, err 159 } 160 161 // CanUseTimetracker returns whether or not a user can use the timetracker. 162 func (r *Repository) CanUseTimetracker(issue *issues_model.Issue, user *user_model.User) bool { 163 // Checking for following: 164 // 1. Is timetracker enabled 165 // 2. Is the user a contributor, admin, poster or assignee and do the repository policies require this? 166 isAssigned, _ := issues_model.IsUserAssignedToIssue(db.DefaultContext, issue, user) 167 return r.Repository.IsTimetrackerEnabled(db.DefaultContext) && (!r.Repository.AllowOnlyContributorsToTrackTime(db.DefaultContext) || 168 r.Permission.CanWriteIssuesOrPulls(issue.IsPull) || issue.IsPoster(user.ID) || isAssigned) 169 } 170 171 // CanCreateIssueDependencies returns whether or not a user can create dependencies. 172 func (r *Repository) CanCreateIssueDependencies(user *user_model.User, isPull bool) bool { 173 return r.Repository.IsDependenciesEnabled(db.DefaultContext) && r.Permission.CanWriteIssuesOrPulls(isPull) 174 } 175 176 // GetCommitsCount returns cached commit count for current view 177 func (r *Repository) GetCommitsCount() (int64, error) { 178 var contextName string 179 if r.IsViewBranch { 180 contextName = r.BranchName 181 } else if r.IsViewTag { 182 contextName = r.TagName 183 } else { 184 contextName = r.CommitID 185 } 186 return cache.GetInt64(r.Repository.GetCommitsCountCacheKey(contextName, r.IsViewBranch || r.IsViewTag), func() (int64, error) { 187 return r.Commit.CommitsCount() 188 }) 189 } 190 191 // GetCommitGraphsCount returns cached commit count for current view 192 func (r *Repository) GetCommitGraphsCount(ctx context.Context, hidePRRefs bool, branches, files []string) (int64, error) { 193 cacheKey := fmt.Sprintf("commits-count-%d-graph-%t-%s-%s", r.Repository.ID, hidePRRefs, branches, files) 194 195 return cache.GetInt64(cacheKey, func() (int64, error) { 196 if len(branches) == 0 { 197 return git.AllCommitsCount(ctx, r.Repository.RepoPath(), hidePRRefs, files...) 198 } 199 return git.CommitsCountFiles(ctx, r.Repository.RepoPath(), branches, files) 200 }) 201 } 202 203 // BranchNameSubURL sub-URL for the BranchName field 204 func (r *Repository) BranchNameSubURL() string { 205 switch { 206 case r.IsViewBranch: 207 return "branch/" + util.PathEscapeSegments(r.BranchName) 208 case r.IsViewTag: 209 return "tag/" + util.PathEscapeSegments(r.TagName) 210 case r.IsViewCommit: 211 return "commit/" + util.PathEscapeSegments(r.CommitID) 212 } 213 log.Error("Unknown view type for repo: %v", r) 214 return "" 215 } 216 217 // FileExists returns true if a file exists in the given repo branch 218 func (r *Repository) FileExists(path, branch string) (bool, error) { 219 if branch == "" { 220 branch = r.Repository.DefaultBranch 221 } 222 commit, err := r.GitRepo.GetBranchCommit(branch) 223 if err != nil { 224 return false, err 225 } 226 if _, err := commit.GetTreeEntryByPath(path); err != nil { 227 return false, err 228 } 229 return true, nil 230 } 231 232 // GetEditorconfig returns the .editorconfig definition if found in the 233 // HEAD of the default repo branch. 234 func (r *Repository) GetEditorconfig(optCommit ...*git.Commit) (*editorconfig.Editorconfig, error) { 235 if r.GitRepo == nil { 236 return nil, nil 237 } 238 var ( 239 err error 240 commit *git.Commit 241 ) 242 if len(optCommit) != 0 { 243 commit = optCommit[0] 244 } else { 245 commit, err = r.GitRepo.GetBranchCommit(r.Repository.DefaultBranch) 246 if err != nil { 247 return nil, err 248 } 249 } 250 treeEntry, err := commit.GetTreeEntryByPath(".editorconfig") 251 if err != nil { 252 return nil, err 253 } 254 if treeEntry.Blob().Size() >= setting.UI.MaxDisplayFileSize { 255 return nil, git.ErrNotExist{ID: "", RelPath: ".editorconfig"} 256 } 257 reader, err := treeEntry.Blob().DataAsync() 258 if err != nil { 259 return nil, err 260 } 261 defer reader.Close() 262 return editorconfig.Parse(reader) 263 } 264 265 // RetrieveBaseRepo retrieves base repository 266 func RetrieveBaseRepo(ctx *Context, repo *repo_model.Repository) { 267 // Non-fork repository will not return error in this method. 268 if err := repo.GetBaseRepo(ctx); err != nil { 269 if repo_model.IsErrRepoNotExist(err) { 270 repo.IsFork = false 271 repo.ForkID = 0 272 return 273 } 274 ctx.ServerError("GetBaseRepo", err) 275 return 276 } else if err = repo.BaseRepo.LoadOwner(ctx); err != nil { 277 ctx.ServerError("BaseRepo.LoadOwner", err) 278 return 279 } 280 } 281 282 // RetrieveTemplateRepo retrieves template repository used to generate this repository 283 func RetrieveTemplateRepo(ctx *Context, repo *repo_model.Repository) { 284 // Non-generated repository will not return error in this method. 285 templateRepo, err := repo_model.GetTemplateRepo(ctx, repo) 286 if err != nil { 287 if repo_model.IsErrRepoNotExist(err) { 288 repo.TemplateID = 0 289 return 290 } 291 ctx.ServerError("GetTemplateRepo", err) 292 return 293 } else if err = templateRepo.LoadOwner(ctx); err != nil { 294 ctx.ServerError("TemplateRepo.LoadOwner", err) 295 return 296 } 297 298 perm, err := access_model.GetUserRepoPermission(ctx, templateRepo, ctx.Doer) 299 if err != nil { 300 ctx.ServerError("GetUserRepoPermission", err) 301 return 302 } 303 304 if !perm.CanRead(unit_model.TypeCode) { 305 repo.TemplateID = 0 306 } 307 } 308 309 // ComposeGoGetImport returns go-get-import meta content. 310 func ComposeGoGetImport(owner, repo string) string { 311 /// setting.AppUrl is guaranteed to be parse as url 312 appURL, _ := url.Parse(setting.AppURL) 313 314 return path.Join(appURL.Host, setting.AppSubURL, url.PathEscape(owner), url.PathEscape(repo)) 315 } 316 317 // EarlyResponseForGoGetMeta responses appropriate go-get meta with status 200 318 // if user does not have actual access to the requested repository, 319 // or the owner or repository does not exist at all. 320 // This is particular a workaround for "go get" command which does not respect 321 // .netrc file. 322 func EarlyResponseForGoGetMeta(ctx *Context) { 323 username := ctx.Params(":username") 324 reponame := strings.TrimSuffix(ctx.Params(":reponame"), ".git") 325 if username == "" || reponame == "" { 326 ctx.PlainText(http.StatusBadRequest, "invalid repository path") 327 return 328 } 329 goImportContent := fmt.Sprintf("%s git %s", ComposeGoGetImport(username, reponame), repo_model.ComposeHTTPSCloneURL(username, reponame)) 330 htmlMeta := fmt.Sprintf(`<meta name="go-import" content="%s">`, html.EscapeString(goImportContent)) 331 ctx.PlainText(http.StatusOK, htmlMeta) 332 } 333 334 // RedirectToRepo redirect to a differently-named repository 335 func RedirectToRepo(ctx *Context, redirectRepoID int64) { 336 ownerName := ctx.Params(":username") 337 previousRepoName := ctx.Params(":reponame") 338 339 repo, err := repo_model.GetRepositoryByID(ctx, redirectRepoID) 340 if err != nil { 341 ctx.ServerError("GetRepositoryByID", err) 342 return 343 } 344 345 redirectPath := strings.Replace( 346 ctx.Req.URL.EscapedPath(), 347 url.PathEscape(ownerName)+"/"+url.PathEscape(previousRepoName), 348 url.PathEscape(repo.OwnerName)+"/"+url.PathEscape(repo.Name), 349 1, 350 ) 351 if ctx.Req.URL.RawQuery != "" { 352 redirectPath += "?" + ctx.Req.URL.RawQuery 353 } 354 ctx.Redirect(path.Join(setting.AppSubURL, redirectPath), http.StatusTemporaryRedirect) 355 } 356 357 func repoAssignment(ctx *Context, repo *repo_model.Repository) { 358 var err error 359 if err = repo.LoadOwner(ctx); err != nil { 360 ctx.ServerError("LoadOwner", err) 361 return 362 } 363 364 ctx.Repo.Permission, err = access_model.GetUserRepoPermission(ctx, repo, ctx.Doer) 365 if err != nil { 366 ctx.ServerError("GetUserRepoPermission", err) 367 return 368 } 369 370 // Check access. 371 if !ctx.Repo.Permission.HasAccess() { 372 if ctx.FormString("go-get") == "1" { 373 EarlyResponseForGoGetMeta(ctx) 374 return 375 } 376 ctx.NotFound("no access right", nil) 377 return 378 } 379 ctx.Data["HasAccess"] = true 380 ctx.Data["Permission"] = &ctx.Repo.Permission 381 382 if repo.IsMirror { 383 ctx.Repo.Mirror, err = repo_model.GetMirrorByRepoID(ctx, repo.ID) 384 if err == nil { 385 ctx.Repo.Mirror.Repo = repo 386 ctx.Data["IsPullMirror"] = true 387 ctx.Data["MirrorEnablePrune"] = ctx.Repo.Mirror.EnablePrune 388 ctx.Data["MirrorInterval"] = ctx.Repo.Mirror.Interval 389 ctx.Data["Mirror"] = ctx.Repo.Mirror 390 } else if err != repo_model.ErrMirrorNotExist { 391 ctx.ServerError("GetMirrorByRepoID", err) 392 return 393 } 394 } 395 396 pushMirrors, _, err := repo_model.GetPushMirrorsByRepoID(ctx, repo.ID, db.ListOptions{}) 397 if err != nil { 398 ctx.ServerError("GetPushMirrorsByRepoID", err) 399 return 400 } 401 402 ctx.Repo.Repository = repo 403 ctx.Data["PushMirrors"] = pushMirrors 404 ctx.Data["RepoName"] = ctx.Repo.Repository.Name 405 ctx.Data["IsEmptyRepo"] = ctx.Repo.Repository.IsEmpty 406 } 407 408 // RepoIDAssignment returns a handler which assigns the repo to the context. 409 func RepoIDAssignment() func(ctx *Context) { 410 return func(ctx *Context) { 411 repoID := ctx.ParamsInt64(":repoid") 412 413 // Get repository. 414 repo, err := repo_model.GetRepositoryByID(ctx, repoID) 415 if err != nil { 416 if repo_model.IsErrRepoNotExist(err) { 417 ctx.NotFound("GetRepositoryByID", nil) 418 } else { 419 ctx.ServerError("GetRepositoryByID", err) 420 } 421 return 422 } 423 424 repoAssignment(ctx, repo) 425 } 426 } 427 428 // RepoAssignment returns a middleware to handle repository assignment 429 func RepoAssignment(ctx *Context) (cancel context.CancelFunc) { 430 if _, repoAssignmentOnce := ctx.Data["repoAssignmentExecuted"]; repoAssignmentOnce { 431 log.Trace("RepoAssignment was exec already, skipping second call ...") 432 return 433 } 434 ctx.Data["repoAssignmentExecuted"] = true 435 436 var ( 437 owner *user_model.User 438 err error 439 ) 440 441 userName := ctx.Params(":username") 442 repoName := ctx.Params(":reponame") 443 repoName = strings.TrimSuffix(repoName, ".git") 444 if setting.EnableFeed { 445 repoName = strings.TrimSuffix(repoName, ".rss") 446 repoName = strings.TrimSuffix(repoName, ".atom") 447 } 448 449 // Check if the user is the same as the repository owner 450 if ctx.IsSigned && ctx.Doer.LowerName == strings.ToLower(userName) { 451 owner = ctx.Doer 452 } else { 453 owner, err = user_model.GetUserByName(ctx, userName) 454 if err != nil { 455 if user_model.IsErrUserNotExist(err) { 456 // go-get does not support redirects 457 // https://github.com/golang/go/issues/19760 458 if ctx.FormString("go-get") == "1" { 459 EarlyResponseForGoGetMeta(ctx) 460 return 461 } 462 463 if redirectUserID, err := user_model.LookupUserRedirect(userName); err == nil { 464 RedirectToUser(ctx, userName, redirectUserID) 465 } else if user_model.IsErrUserRedirectNotExist(err) { 466 ctx.NotFound("GetUserByName", nil) 467 } else { 468 ctx.ServerError("LookupUserRedirect", err) 469 } 470 } else { 471 ctx.ServerError("GetUserByName", err) 472 } 473 return 474 } 475 } 476 ctx.Repo.Owner = owner 477 ctx.ContextUser = owner 478 ctx.Data["Username"] = ctx.Repo.Owner.Name 479 480 // redirect link to wiki 481 if strings.HasSuffix(repoName, ".wiki") { 482 // ctx.Req.URL.Path does not have the preceding appSubURL - any redirect must have this added 483 // Now we happen to know that all of our paths are: /:username/:reponame/whatever_else 484 originalRepoName := ctx.Params(":reponame") 485 redirectRepoName := strings.TrimSuffix(repoName, ".wiki") 486 redirectRepoName += originalRepoName[len(redirectRepoName)+5:] 487 redirectPath := strings.Replace( 488 ctx.Req.URL.EscapedPath(), 489 url.PathEscape(userName)+"/"+url.PathEscape(originalRepoName), 490 url.PathEscape(userName)+"/"+url.PathEscape(redirectRepoName)+"/wiki", 491 1, 492 ) 493 if ctx.Req.URL.RawQuery != "" { 494 redirectPath += "?" + ctx.Req.URL.RawQuery 495 } 496 ctx.Redirect(path.Join(setting.AppSubURL, redirectPath)) 497 return 498 } 499 500 // Get repository. 501 repo, err := repo_model.GetRepositoryByName(owner.ID, repoName) 502 if err != nil { 503 if repo_model.IsErrRepoNotExist(err) { 504 redirectRepoID, err := repo_model.LookupRedirect(owner.ID, repoName) 505 if err == nil { 506 RedirectToRepo(ctx, redirectRepoID) 507 } else if repo_model.IsErrRedirectNotExist(err) { 508 if ctx.FormString("go-get") == "1" { 509 EarlyResponseForGoGetMeta(ctx) 510 return 511 } 512 ctx.NotFound("GetRepositoryByName", nil) 513 } else { 514 ctx.ServerError("LookupRepoRedirect", err) 515 } 516 } else { 517 ctx.ServerError("GetRepositoryByName", err) 518 } 519 return 520 } 521 repo.Owner = owner 522 523 repoAssignment(ctx, repo) 524 if ctx.Written() { 525 return 526 } 527 528 ctx.Repo.RepoLink = repo.Link() 529 ctx.Data["RepoLink"] = ctx.Repo.RepoLink 530 ctx.Data["RepoRelPath"] = ctx.Repo.Owner.Name + "/" + ctx.Repo.Repository.Name 531 532 if setting.EnableFeed { 533 ctx.Data["EnableFeed"] = true 534 ctx.Data["FeedURL"] = ctx.Repo.RepoLink 535 } 536 537 unit, err := ctx.Repo.Repository.GetUnit(ctx, unit_model.TypeExternalTracker) 538 if err == nil { 539 ctx.Data["RepoExternalIssuesLink"] = unit.ExternalTrackerConfig().ExternalTrackerURL 540 } 541 542 ctx.Data["NumTags"], err = repo_model.GetReleaseCountByRepoID(ctx, ctx.Repo.Repository.ID, repo_model.FindReleasesOptions{ 543 IncludeDrafts: true, 544 IncludeTags: true, 545 HasSha1: util.OptionalBoolTrue, // only draft releases which are created with existing tags 546 }) 547 if err != nil { 548 ctx.ServerError("GetReleaseCountByRepoID", err) 549 return 550 } 551 ctx.Data["NumReleases"], err = repo_model.GetReleaseCountByRepoID(ctx, ctx.Repo.Repository.ID, repo_model.FindReleasesOptions{}) 552 if err != nil { 553 ctx.ServerError("GetReleaseCountByRepoID", err) 554 return 555 } 556 557 ctx.Data["Title"] = owner.Name + "/" + repo.Name 558 ctx.Data["Repository"] = repo 559 ctx.Data["Owner"] = ctx.Repo.Repository.Owner 560 ctx.Data["IsRepositoryOwner"] = ctx.Repo.IsOwner() 561 ctx.Data["IsRepositoryAdmin"] = ctx.Repo.IsAdmin() 562 ctx.Data["RepoOwnerIsOrganization"] = repo.Owner.IsOrganization() 563 ctx.Data["CanWriteCode"] = ctx.Repo.CanWrite(unit_model.TypeCode) 564 ctx.Data["CanWriteIssues"] = ctx.Repo.CanWrite(unit_model.TypeIssues) 565 ctx.Data["CanWritePulls"] = ctx.Repo.CanWrite(unit_model.TypePullRequests) 566 567 canSignedUserFork, err := repo_module.CanUserForkRepo(ctx.Doer, ctx.Repo.Repository) 568 if err != nil { 569 ctx.ServerError("CanUserForkRepo", err) 570 return 571 } 572 ctx.Data["CanSignedUserFork"] = canSignedUserFork 573 574 userAndOrgForks, err := repo_model.GetForksByUserAndOrgs(ctx, ctx.Doer, ctx.Repo.Repository) 575 if err != nil { 576 ctx.ServerError("GetForksByUserAndOrgs", err) 577 return 578 } 579 ctx.Data["UserAndOrgForks"] = userAndOrgForks 580 581 // canSignedUserFork is true if the current user doesn't have a fork of this repo yet or 582 // if he owns an org that doesn't have a fork of this repo yet 583 // If multiple forks are available or if the user can fork to another account, but there is already a fork: open selection dialog 584 ctx.Data["ShowForkModal"] = len(userAndOrgForks) > 1 || (canSignedUserFork && len(userAndOrgForks) > 0) 585 586 ctx.Data["RepoCloneLink"] = repo.CloneLink() 587 588 cloneButtonShowHTTPS := !setting.Repository.DisableHTTPGit 589 cloneButtonShowSSH := !setting.SSH.Disabled && (ctx.IsSigned || setting.SSH.ExposeAnonymous) 590 if !cloneButtonShowHTTPS && !cloneButtonShowSSH { 591 // We have to show at least one link, so we just show the HTTPS 592 cloneButtonShowHTTPS = true 593 } 594 ctx.Data["CloneButtonShowHTTPS"] = cloneButtonShowHTTPS 595 ctx.Data["CloneButtonShowSSH"] = cloneButtonShowSSH 596 ctx.Data["CloneButtonOriginLink"] = ctx.Data["RepoCloneLink"] // it may be rewritten to the WikiCloneLink by the router middleware 597 598 ctx.Data["RepoSearchEnabled"] = setting.Indexer.RepoIndexerEnabled 599 if setting.Indexer.RepoIndexerEnabled { 600 ctx.Data["CodeIndexerUnavailable"] = !code_indexer.IsAvailable() 601 } 602 603 if ctx.IsSigned { 604 ctx.Data["IsWatchingRepo"] = repo_model.IsWatching(ctx.Doer.ID, repo.ID) 605 ctx.Data["IsStaringRepo"] = repo_model.IsStaring(ctx, ctx.Doer.ID, repo.ID) 606 } 607 608 if repo.IsFork { 609 RetrieveBaseRepo(ctx, repo) 610 if ctx.Written() { 611 return 612 } 613 } 614 615 if repo.IsGenerated() { 616 RetrieveTemplateRepo(ctx, repo) 617 if ctx.Written() { 618 return 619 } 620 } 621 622 isHomeOrSettings := ctx.Link == ctx.Repo.RepoLink || ctx.Link == ctx.Repo.RepoLink+"/settings" || strings.HasPrefix(ctx.Link, ctx.Repo.RepoLink+"/settings/") 623 624 // Disable everything when the repo is being created 625 if ctx.Repo.Repository.IsBeingCreated() || ctx.Repo.Repository.IsBroken() { 626 ctx.Data["BranchName"] = ctx.Repo.Repository.DefaultBranch 627 if !isHomeOrSettings { 628 ctx.Redirect(ctx.Repo.RepoLink) 629 } 630 return 631 } 632 633 gitRepo, err := git.OpenRepository(ctx, repo_model.RepoPath(userName, repoName)) 634 if err != nil { 635 if strings.Contains(err.Error(), "repository does not exist") || strings.Contains(err.Error(), "no such file or directory") { 636 log.Error("Repository %-v has a broken repository on the file system: %s Error: %v", ctx.Repo.Repository, ctx.Repo.Repository.RepoPath(), err) 637 ctx.Repo.Repository.Status = repo_model.RepositoryBroken 638 ctx.Repo.Repository.IsEmpty = true 639 ctx.Data["BranchName"] = ctx.Repo.Repository.DefaultBranch 640 // Only allow access to base of repo or settings 641 if !isHomeOrSettings { 642 ctx.Redirect(ctx.Repo.RepoLink) 643 } 644 return 645 } 646 ctx.ServerError("RepoAssignment Invalid repo "+repo_model.RepoPath(userName, repoName), err) 647 return 648 } 649 if ctx.Repo.GitRepo != nil { 650 ctx.Repo.GitRepo.Close() 651 } 652 ctx.Repo.GitRepo = gitRepo 653 654 // We opened it, we should close it 655 cancel = func() { 656 // If it's been set to nil then assume someone else has closed it. 657 if ctx.Repo.GitRepo != nil { 658 ctx.Repo.GitRepo.Close() 659 } 660 } 661 662 // Stop at this point when the repo is empty. 663 if ctx.Repo.Repository.IsEmpty { 664 ctx.Data["BranchName"] = ctx.Repo.Repository.DefaultBranch 665 return 666 } 667 668 tags, err := ctx.Repo.GitRepo.GetTags(0, 0) 669 if err != nil { 670 if strings.Contains(err.Error(), "fatal: not a git repository ") { 671 log.Error("Repository %-v has a broken repository on the file system: %s Error: %v", ctx.Repo.Repository, ctx.Repo.Repository.RepoPath(), err) 672 ctx.Repo.Repository.Status = repo_model.RepositoryBroken 673 ctx.Repo.Repository.IsEmpty = true 674 ctx.Data["BranchName"] = ctx.Repo.Repository.DefaultBranch 675 // Only allow access to base of repo or settings 676 if !isHomeOrSettings { 677 ctx.Redirect(ctx.Repo.RepoLink) 678 } 679 return 680 } 681 ctx.ServerError("GetTags", err) 682 return 683 } 684 ctx.Data["Tags"] = tags 685 686 brs, _, err := ctx.Repo.GitRepo.GetBranchNames(0, 0) 687 if err != nil { 688 ctx.ServerError("GetBranches", err) 689 return 690 } 691 ctx.Data["Branches"] = brs 692 ctx.Data["BranchesCount"] = len(brs) 693 694 // If not branch selected, try default one. 695 // If default branch doesn't exists, fall back to some other branch. 696 if len(ctx.Repo.BranchName) == 0 { 697 if len(ctx.Repo.Repository.DefaultBranch) > 0 && gitRepo.IsBranchExist(ctx.Repo.Repository.DefaultBranch) { 698 ctx.Repo.BranchName = ctx.Repo.Repository.DefaultBranch 699 } else if len(brs) > 0 { 700 ctx.Repo.BranchName = brs[0] 701 } 702 ctx.Repo.RefName = ctx.Repo.BranchName 703 } 704 ctx.Data["BranchName"] = ctx.Repo.BranchName 705 706 // People who have push access or have forked repository can propose a new pull request. 707 canPush := ctx.Repo.CanWrite(unit_model.TypeCode) || 708 (ctx.IsSigned && repo_model.HasForkedRepo(ctx.Doer.ID, ctx.Repo.Repository.ID)) 709 canCompare := false 710 711 // Pull request is allowed if this is a fork repository 712 // and base repository accepts pull requests. 713 if repo.BaseRepo != nil && repo.BaseRepo.AllowsPulls() { 714 canCompare = true 715 ctx.Data["BaseRepo"] = repo.BaseRepo 716 ctx.Repo.PullRequest.BaseRepo = repo.BaseRepo 717 ctx.Repo.PullRequest.Allowed = canPush 718 ctx.Repo.PullRequest.HeadInfoSubURL = url.PathEscape(ctx.Repo.Owner.Name) + ":" + util.PathEscapeSegments(ctx.Repo.BranchName) 719 } else if repo.AllowsPulls() { 720 // Or, this is repository accepts pull requests between branches. 721 canCompare = true 722 ctx.Data["BaseRepo"] = repo 723 ctx.Repo.PullRequest.BaseRepo = repo 724 ctx.Repo.PullRequest.Allowed = canPush 725 ctx.Repo.PullRequest.SameRepo = true 726 ctx.Repo.PullRequest.HeadInfoSubURL = util.PathEscapeSegments(ctx.Repo.BranchName) 727 } 728 ctx.Data["CanCompareOrPull"] = canCompare 729 ctx.Data["PullRequestCtx"] = ctx.Repo.PullRequest 730 731 if ctx.Repo.Repository.Status == repo_model.RepositoryPendingTransfer { 732 repoTransfer, err := models.GetPendingRepositoryTransfer(ctx, ctx.Repo.Repository) 733 if err != nil { 734 ctx.ServerError("GetPendingRepositoryTransfer", err) 735 return 736 } 737 738 if err := repoTransfer.LoadAttributes(ctx); err != nil { 739 ctx.ServerError("LoadRecipient", err) 740 return 741 } 742 743 ctx.Data["RepoTransfer"] = repoTransfer 744 if ctx.Doer != nil { 745 ctx.Data["CanUserAcceptTransfer"] = repoTransfer.CanUserAcceptTransfer(ctx.Doer) 746 } 747 } 748 749 if ctx.FormString("go-get") == "1" { 750 ctx.Data["GoGetImport"] = ComposeGoGetImport(owner.Name, repo.Name) 751 fullURLPrefix := repo.HTMLURL() + "/src/branch/" + util.PathEscapeSegments(ctx.Repo.BranchName) 752 ctx.Data["GoDocDirectory"] = fullURLPrefix + "{/dir}" 753 ctx.Data["GoDocFile"] = fullURLPrefix + "{/dir}/{file}#L{line}" 754 } 755 return cancel 756 } 757 758 // RepoRefType type of repo reference 759 type RepoRefType int 760 761 const ( 762 // RepoRefLegacy unknown type, make educated guess and redirect. 763 // for backward compatibility with previous URL scheme 764 RepoRefLegacy RepoRefType = iota 765 // RepoRefAny is for usage where educated guess is needed 766 // but redirect can not be made 767 RepoRefAny 768 // RepoRefBranch branch 769 RepoRefBranch 770 // RepoRefTag tag 771 RepoRefTag 772 // RepoRefCommit commit 773 RepoRefCommit 774 // RepoRefBlob blob 775 RepoRefBlob 776 ) 777 778 // RepoRef handles repository reference names when the ref name is not 779 // explicitly given 780 func RepoRef() func(*Context) context.CancelFunc { 781 // since no ref name is explicitly specified, ok to just use branch 782 return RepoRefByType(RepoRefBranch) 783 } 784 785 // RefTypeIncludesBranches returns true if ref type can be a branch 786 func (rt RepoRefType) RefTypeIncludesBranches() bool { 787 if rt == RepoRefLegacy || rt == RepoRefAny || rt == RepoRefBranch { 788 return true 789 } 790 return false 791 } 792 793 // RefTypeIncludesTags returns true if ref type can be a tag 794 func (rt RepoRefType) RefTypeIncludesTags() bool { 795 if rt == RepoRefLegacy || rt == RepoRefAny || rt == RepoRefTag { 796 return true 797 } 798 return false 799 } 800 801 func getRefNameFromPath(ctx *Context, path string, isExist func(string) bool) string { 802 refName := "" 803 parts := strings.Split(path, "/") 804 for i, part := range parts { 805 refName = strings.TrimPrefix(refName+"/"+part, "/") 806 if isExist(refName) { 807 ctx.Repo.TreePath = strings.Join(parts[i+1:], "/") 808 return refName 809 } 810 } 811 return "" 812 } 813 814 func getRefName(ctx *Context, pathType RepoRefType) string { 815 path := ctx.Params("*") 816 switch pathType { 817 case RepoRefLegacy, RepoRefAny: 818 if refName := getRefName(ctx, RepoRefBranch); len(refName) > 0 { 819 return refName 820 } 821 if refName := getRefName(ctx, RepoRefTag); len(refName) > 0 { 822 return refName 823 } 824 // For legacy and API support only full commit sha 825 parts := strings.Split(path, "/") 826 if len(parts) > 0 && len(parts[0]) == git.SHAFullLength { 827 ctx.Repo.TreePath = strings.Join(parts[1:], "/") 828 return parts[0] 829 } 830 if refName := getRefName(ctx, RepoRefBlob); len(refName) > 0 { 831 return refName 832 } 833 ctx.Repo.TreePath = path 834 return ctx.Repo.Repository.DefaultBranch 835 case RepoRefBranch: 836 ref := getRefNameFromPath(ctx, path, ctx.Repo.GitRepo.IsBranchExist) 837 if len(ref) == 0 { 838 // maybe it's a renamed branch 839 return getRefNameFromPath(ctx, path, func(s string) bool { 840 b, exist, err := git_model.FindRenamedBranch(ctx, ctx.Repo.Repository.ID, s) 841 if err != nil { 842 log.Error("FindRenamedBranch", err) 843 return false 844 } 845 846 if !exist { 847 return false 848 } 849 850 ctx.Data["IsRenamedBranch"] = true 851 ctx.Data["RenamedBranchName"] = b.To 852 853 return true 854 }) 855 } 856 857 return ref 858 case RepoRefTag: 859 return getRefNameFromPath(ctx, path, ctx.Repo.GitRepo.IsTagExist) 860 case RepoRefCommit: 861 parts := strings.Split(path, "/") 862 if len(parts) > 0 && len(parts[0]) >= 7 && len(parts[0]) <= git.SHAFullLength { 863 ctx.Repo.TreePath = strings.Join(parts[1:], "/") 864 return parts[0] 865 } 866 case RepoRefBlob: 867 _, err := ctx.Repo.GitRepo.GetBlob(path) 868 if err != nil { 869 return "" 870 } 871 return path 872 default: 873 log.Error("Unrecognized path type: %v", path) 874 } 875 return "" 876 } 877 878 // RepoRefByType handles repository reference name for a specific type 879 // of repository reference 880 func RepoRefByType(refType RepoRefType, ignoreNotExistErr ...bool) func(*Context) context.CancelFunc { 881 return func(ctx *Context) (cancel context.CancelFunc) { 882 // Empty repository does not have reference information. 883 if ctx.Repo.Repository.IsEmpty { 884 return 885 } 886 887 var ( 888 refName string 889 err error 890 ) 891 892 if ctx.Repo.GitRepo == nil { 893 repoPath := repo_model.RepoPath(ctx.Repo.Owner.Name, ctx.Repo.Repository.Name) 894 ctx.Repo.GitRepo, err = git.OpenRepository(ctx, repoPath) 895 if err != nil { 896 ctx.ServerError("RepoRef Invalid repo "+repoPath, err) 897 return 898 } 899 // We opened it, we should close it 900 cancel = func() { 901 // If it's been set to nil then assume someone else has closed it. 902 if ctx.Repo.GitRepo != nil { 903 ctx.Repo.GitRepo.Close() 904 } 905 } 906 } 907 908 // Get default branch. 909 if len(ctx.Params("*")) == 0 { 910 refName = ctx.Repo.Repository.DefaultBranch 911 if !ctx.Repo.GitRepo.IsBranchExist(refName) { 912 brs, _, err := ctx.Repo.GitRepo.GetBranchNames(0, 0) 913 if err != nil { 914 ctx.ServerError("GetBranches", err) 915 return 916 } else if len(brs) == 0 { 917 err = fmt.Errorf("No branches in non-empty repository %s", 918 ctx.Repo.GitRepo.Path) 919 ctx.ServerError("GetBranches", err) 920 return 921 } 922 refName = brs[0] 923 } 924 ctx.Repo.RefName = refName 925 ctx.Repo.BranchName = refName 926 ctx.Repo.Commit, err = ctx.Repo.GitRepo.GetBranchCommit(refName) 927 if err != nil { 928 ctx.ServerError("GetBranchCommit", err) 929 return 930 } 931 ctx.Repo.CommitID = ctx.Repo.Commit.ID.String() 932 ctx.Repo.IsViewBranch = true 933 934 } else { 935 refName = getRefName(ctx, refType) 936 ctx.Repo.RefName = refName 937 isRenamedBranch, has := ctx.Data["IsRenamedBranch"].(bool) 938 if isRenamedBranch && has { 939 renamedBranchName := ctx.Data["RenamedBranchName"].(string) 940 ctx.Flash.Info(ctx.Tr("repo.branch.renamed", refName, renamedBranchName)) 941 link := setting.AppSubURL + strings.Replace(ctx.Req.URL.EscapedPath(), util.PathEscapeSegments(refName), util.PathEscapeSegments(renamedBranchName), 1) 942 ctx.Redirect(link) 943 return 944 } 945 946 if refType.RefTypeIncludesBranches() && ctx.Repo.GitRepo.IsBranchExist(refName) { 947 ctx.Repo.IsViewBranch = true 948 ctx.Repo.BranchName = refName 949 950 ctx.Repo.Commit, err = ctx.Repo.GitRepo.GetBranchCommit(refName) 951 if err != nil { 952 ctx.ServerError("GetBranchCommit", err) 953 return 954 } 955 ctx.Repo.CommitID = ctx.Repo.Commit.ID.String() 956 957 } else if refType.RefTypeIncludesTags() && ctx.Repo.GitRepo.IsTagExist(refName) { 958 ctx.Repo.IsViewTag = true 959 ctx.Repo.TagName = refName 960 961 ctx.Repo.Commit, err = ctx.Repo.GitRepo.GetTagCommit(refName) 962 if err != nil { 963 if git.IsErrNotExist(err) { 964 ctx.NotFound("GetTagCommit", err) 965 return 966 } 967 ctx.ServerError("GetTagCommit", err) 968 return 969 } 970 ctx.Repo.CommitID = ctx.Repo.Commit.ID.String() 971 } else if len(refName) >= 7 && len(refName) <= git.SHAFullLength { 972 ctx.Repo.IsViewCommit = true 973 ctx.Repo.CommitID = refName 974 975 ctx.Repo.Commit, err = ctx.Repo.GitRepo.GetCommit(refName) 976 if err != nil { 977 ctx.NotFound("GetCommit", err) 978 return 979 } 980 // If short commit ID add canonical link header 981 if len(refName) < git.SHAFullLength { 982 ctx.RespHeader().Set("Link", fmt.Sprintf("<%s>; rel=\"canonical\"", 983 util.URLJoin(setting.AppURL, strings.Replace(ctx.Req.URL.RequestURI(), util.PathEscapeSegments(refName), url.PathEscape(ctx.Repo.Commit.ID.String()), 1)))) 984 } 985 } else { 986 if len(ignoreNotExistErr) > 0 && ignoreNotExistErr[0] { 987 return 988 } 989 ctx.NotFound("RepoRef invalid repo", fmt.Errorf("branch or tag not exist: %s", refName)) 990 return 991 } 992 993 if refType == RepoRefLegacy { 994 // redirect from old URL scheme to new URL scheme 995 prefix := strings.TrimPrefix(setting.AppSubURL+strings.ToLower(strings.TrimSuffix(ctx.Req.URL.Path, ctx.Params("*"))), strings.ToLower(ctx.Repo.RepoLink)) 996 997 ctx.Redirect(path.Join( 998 ctx.Repo.RepoLink, 999 util.PathEscapeSegments(prefix), 1000 ctx.Repo.BranchNameSubURL(), 1001 util.PathEscapeSegments(ctx.Repo.TreePath))) 1002 return 1003 } 1004 } 1005 1006 ctx.Data["BranchName"] = ctx.Repo.BranchName 1007 ctx.Data["RefName"] = ctx.Repo.RefName 1008 ctx.Data["BranchNameSubURL"] = ctx.Repo.BranchNameSubURL() 1009 ctx.Data["TagName"] = ctx.Repo.TagName 1010 ctx.Data["CommitID"] = ctx.Repo.CommitID 1011 ctx.Data["TreePath"] = ctx.Repo.TreePath 1012 ctx.Data["IsViewBranch"] = ctx.Repo.IsViewBranch 1013 ctx.Data["IsViewTag"] = ctx.Repo.IsViewTag 1014 ctx.Data["IsViewCommit"] = ctx.Repo.IsViewCommit 1015 ctx.Data["CanCreateBranch"] = ctx.Repo.CanCreateBranch() 1016 1017 ctx.Repo.CommitsCount, err = ctx.Repo.GetCommitsCount() 1018 if err != nil { 1019 ctx.ServerError("GetCommitsCount", err) 1020 return 1021 } 1022 ctx.Data["CommitsCount"] = ctx.Repo.CommitsCount 1023 ctx.Repo.GitRepo.LastCommitCache = git.NewLastCommitCache(ctx.Repo.CommitsCount, ctx.Repo.Repository.FullName(), ctx.Repo.GitRepo, cache.GetCache()) 1024 1025 return cancel 1026 } 1027 } 1028 1029 // GitHookService checks if repository Git hooks service has been enabled. 1030 func GitHookService() func(ctx *Context) { 1031 return func(ctx *Context) { 1032 if !ctx.Doer.CanEditGitHook() { 1033 ctx.NotFound("GitHookService", nil) 1034 return 1035 } 1036 } 1037 } 1038 1039 // UnitTypes returns a middleware to set unit types to context variables. 1040 func UnitTypes() func(ctx *Context) { 1041 return func(ctx *Context) { 1042 ctx.Data["UnitTypeCode"] = unit_model.TypeCode 1043 ctx.Data["UnitTypeIssues"] = unit_model.TypeIssues 1044 ctx.Data["UnitTypePullRequests"] = unit_model.TypePullRequests 1045 ctx.Data["UnitTypeReleases"] = unit_model.TypeReleases 1046 ctx.Data["UnitTypeWiki"] = unit_model.TypeWiki 1047 ctx.Data["UnitTypeExternalWiki"] = unit_model.TypeExternalWiki 1048 ctx.Data["UnitTypeExternalTracker"] = unit_model.TypeExternalTracker 1049 ctx.Data["UnitTypeProjects"] = unit_model.TypeProjects 1050 ctx.Data["UnitTypePackages"] = unit_model.TypePackages 1051 ctx.Data["UnitTypeActions"] = unit_model.TypeActions 1052 } 1053 } 1054 1055 // IssueTemplatesFromDefaultBranch checks for valid issue templates in the repo's default branch, 1056 func (ctx *Context) IssueTemplatesFromDefaultBranch() []*api.IssueTemplate { 1057 ret, _ := ctx.IssueTemplatesErrorsFromDefaultBranch() 1058 return ret 1059 } 1060 1061 // IssueTemplatesErrorsFromDefaultBranch checks for issue templates in the repo's default branch, 1062 // returns valid templates and the errors of invalid template files. 1063 func (ctx *Context) IssueTemplatesErrorsFromDefaultBranch() ([]*api.IssueTemplate, map[string]error) { 1064 var issueTemplates []*api.IssueTemplate 1065 1066 if ctx.Repo.Repository.IsEmpty { 1067 return issueTemplates, nil 1068 } 1069 1070 if ctx.Repo.Commit == nil { 1071 var err error 1072 ctx.Repo.Commit, err = ctx.Repo.GitRepo.GetBranchCommit(ctx.Repo.Repository.DefaultBranch) 1073 if err != nil { 1074 return issueTemplates, nil 1075 } 1076 } 1077 1078 invalidFiles := map[string]error{} 1079 for _, dirName := range IssueTemplateDirCandidates { 1080 tree, err := ctx.Repo.Commit.SubTree(dirName) 1081 if err != nil { 1082 log.Debug("get sub tree of %s: %v", dirName, err) 1083 continue 1084 } 1085 entries, err := tree.ListEntries() 1086 if err != nil { 1087 log.Debug("list entries in %s: %v", dirName, err) 1088 return issueTemplates, nil 1089 } 1090 for _, entry := range entries { 1091 if !template.CouldBe(entry.Name()) { 1092 continue 1093 } 1094 fullName := path.Join(dirName, entry.Name()) 1095 if it, err := template.UnmarshalFromEntry(entry, dirName); err != nil { 1096 invalidFiles[fullName] = err 1097 } else { 1098 if !strings.HasPrefix(it.Ref, "refs/") { // Assume that the ref intended is always a branch - for tags users should use refs/tags/<ref> 1099 it.Ref = git.BranchPrefix + it.Ref 1100 } 1101 issueTemplates = append(issueTemplates, it) 1102 } 1103 } 1104 } 1105 return issueTemplates, invalidFiles 1106 }