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