| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008 |
- // Copyright 2014 The Gogs Authors. All rights reserved.
- // Copyright 2017 The Gitea Authors. All rights reserved.
- // SPDX-License-Identifier: MIT
-
- package context
-
- import (
- "context"
- "errors"
- "fmt"
- "html"
- "net/http"
- "net/url"
- "path"
- "strings"
-
- asymkey_model "code.gitea.io/gitea/models/asymkey"
- "code.gitea.io/gitea/models/db"
- git_model "code.gitea.io/gitea/models/git"
- issues_model "code.gitea.io/gitea/models/issues"
- access_model "code.gitea.io/gitea/models/perm/access"
- repo_model "code.gitea.io/gitea/models/repo"
- unit_model "code.gitea.io/gitea/models/unit"
- user_model "code.gitea.io/gitea/models/user"
- "code.gitea.io/gitea/modules/cache"
- "code.gitea.io/gitea/modules/git"
- "code.gitea.io/gitea/modules/gitrepo"
- "code.gitea.io/gitea/modules/httplib"
- code_indexer "code.gitea.io/gitea/modules/indexer/code"
- "code.gitea.io/gitea/modules/log"
- "code.gitea.io/gitea/modules/optional"
- repo_module "code.gitea.io/gitea/modules/repository"
- "code.gitea.io/gitea/modules/setting"
- "code.gitea.io/gitea/modules/util"
- asymkey_service "code.gitea.io/gitea/services/asymkey"
-
- "github.com/editorconfig/editorconfig-core-go/v2"
- )
-
- // PullRequest contains information to make a pull request
- type PullRequest struct {
- BaseRepo *repo_model.Repository
- Allowed bool // it only used by the web tmpl: "PullRequestCtx.Allowed"
- SameRepo bool // it only used by the web tmpl: "PullRequestCtx.SameRepo"
- }
-
- // Repository contains information to operate a repository
- type Repository struct {
- access_model.Permission
-
- Repository *repo_model.Repository
- Owner *user_model.User
-
- RepoLink string
- GitRepo *git.Repository
-
- // RefFullName is the full ref name that the user is viewing
- RefFullName git.RefName
- BranchName string // it is the RefFullName's short name if its type is "branch"
- TreePath string
-
- // Commit it is always set to the commit for the branch or tag, or just the commit that the user is viewing
- Commit *git.Commit
- CommitID string
- CommitsCount int64
-
- PullRequest *PullRequest
- }
-
- // CanWriteToBranch checks if the branch is writable by the user
- func (r *Repository) CanWriteToBranch(ctx context.Context, user *user_model.User, branch string) bool {
- return issues_model.CanMaintainerWriteToBranch(ctx, r.Permission, branch, user)
- }
-
- // CanCreateBranch returns true if repository is editable and user has proper access level.
- func (r *Repository) CanCreateBranch() bool {
- return r.Permission.CanWrite(unit_model.TypeCode) && r.Repository.CanCreateBranch()
- }
-
- func (r *Repository) GetObjectFormat() git.ObjectFormat {
- return git.ObjectFormatFromName(r.Repository.ObjectFormatName)
- }
-
- // RepoMustNotBeArchived checks if a repo is archived
- func RepoMustNotBeArchived() func(ctx *Context) {
- return func(ctx *Context) {
- if ctx.Repo.Repository.IsArchived {
- ctx.NotFound(errors.New(ctx.Locale.TrString("repo.archive.title")))
- }
- }
- }
-
- type CommitFormOptions struct {
- NeedFork bool
-
- TargetRepo *repo_model.Repository
- TargetFormAction string
- WillSubmitToFork bool
- CanCommitToBranch bool
- UserCanPush bool
- RequireSigned bool
- WillSign bool
- SigningKeyFormDisplay string
- WontSignReason string
- CanCreatePullRequest bool
- CanCreateBasePullRequest bool
- }
-
- func PrepareCommitFormOptions(ctx *Context, doer *user_model.User, targetRepo *repo_model.Repository, doerRepoPerm access_model.Permission, refName git.RefName) (*CommitFormOptions, error) {
- if !refName.IsBranch() {
- // it shouldn't happen because middleware already checks
- return nil, util.NewInvalidArgumentErrorf("ref %q is not a branch", refName)
- }
-
- originRepo := targetRepo
- branchName := refName.ShortName()
- // TODO: CanMaintainerWriteToBranch is a bad name, but it really does what "CanWriteToBranch" does
- if !issues_model.CanMaintainerWriteToBranch(ctx, doerRepoPerm, branchName, doer) {
- targetRepo = repo_model.GetForkedRepo(ctx, doer.ID, targetRepo.ID)
- if targetRepo == nil {
- return &CommitFormOptions{NeedFork: true}, nil
- }
- // now, we get our own forked repo; it must be writable by us.
- }
- submitToForkedRepo := targetRepo.ID != originRepo.ID
- err := targetRepo.GetBaseRepo(ctx)
- if err != nil {
- return nil, err
- }
-
- protectedBranch, err := git_model.GetFirstMatchProtectedBranchRule(ctx, targetRepo.ID, branchName)
- if err != nil {
- return nil, err
- }
- canPushWithProtection := true
- protectionRequireSigned := false
- if protectedBranch != nil {
- protectedBranch.Repo = targetRepo
- canPushWithProtection = protectedBranch.CanUserPush(ctx, doer)
- protectionRequireSigned = protectedBranch.RequireSignedCommits
- }
-
- willSign, signKey, _, err := asymkey_service.SignCRUDAction(ctx, targetRepo.RepoPath(), doer, targetRepo.RepoPath(), refName.String())
- wontSignReason := ""
- if asymkey_service.IsErrWontSign(err) {
- wontSignReason = string(err.(*asymkey_service.ErrWontSign).Reason)
- } else if err != nil {
- return nil, err
- }
-
- canCommitToBranch := !submitToForkedRepo /* same repo */ && targetRepo.CanEnableEditor() && canPushWithProtection
- if protectionRequireSigned {
- canCommitToBranch = canCommitToBranch && willSign
- }
-
- canCreateBasePullRequest := targetRepo.BaseRepo != nil && targetRepo.BaseRepo.UnitEnabled(ctx, unit_model.TypePullRequests)
- canCreatePullRequest := targetRepo.UnitEnabled(ctx, unit_model.TypePullRequests) || canCreateBasePullRequest
-
- opts := &CommitFormOptions{
- TargetRepo: targetRepo,
- WillSubmitToFork: submitToForkedRepo,
- CanCommitToBranch: canCommitToBranch,
- UserCanPush: canPushWithProtection,
- RequireSigned: protectionRequireSigned,
- WillSign: willSign,
- SigningKeyFormDisplay: asymkey_model.GetDisplaySigningKey(signKey),
- WontSignReason: wontSignReason,
-
- CanCreatePullRequest: canCreatePullRequest,
- CanCreateBasePullRequest: canCreateBasePullRequest,
- }
- editorAction := ctx.PathParam("editor_action")
- editorPathParamRemaining := util.PathEscapeSegments(branchName) + "/" + util.PathEscapeSegments(ctx.Repo.TreePath)
- if submitToForkedRepo {
- // there is only "default branch" in forked repo, we will use "from_base_branch" to get a new branch from base repo
- editorPathParamRemaining = util.PathEscapeSegments(targetRepo.DefaultBranch) + "/" + util.PathEscapeSegments(ctx.Repo.TreePath) + "?from_base_branch=" + url.QueryEscape(branchName)
- }
- if editorAction == "_cherrypick" {
- opts.TargetFormAction = targetRepo.Link() + "/" + editorAction + "/" + ctx.PathParam("sha") + "/" + editorPathParamRemaining
- } else {
- opts.TargetFormAction = targetRepo.Link() + "/" + editorAction + "/" + editorPathParamRemaining
- }
- if ctx.Req.URL.RawQuery != "" {
- opts.TargetFormAction += util.Iif(strings.Contains(opts.TargetFormAction, "?"), "&", "?") + ctx.Req.URL.RawQuery
- }
- return opts, nil
- }
-
- // CanUseTimetracker returns whether a user can use the timetracker.
- func (r *Repository) CanUseTimetracker(ctx context.Context, issue *issues_model.Issue, user *user_model.User) bool {
- // Checking for following:
- // 1. Is timetracker enabled
- // 2. Is the user a contributor, admin, poster or assignee and do the repository policies require this?
- isAssigned, _ := issues_model.IsUserAssignedToIssue(ctx, issue, user)
- return r.Repository.IsTimetrackerEnabled(ctx) && (!r.Repository.AllowOnlyContributorsToTrackTime(ctx) ||
- r.Permission.CanWriteIssuesOrPulls(issue.IsPull) || issue.IsPoster(user.ID) || isAssigned)
- }
-
- // CanCreateIssueDependencies returns whether or not a user can create dependencies.
- func (r *Repository) CanCreateIssueDependencies(ctx context.Context, user *user_model.User, isPull bool) bool {
- return r.Repository.IsDependenciesEnabled(ctx) && r.Permission.CanWriteIssuesOrPulls(isPull)
- }
-
- // GetCommitsCount returns cached commit count for current view
- func (r *Repository) GetCommitsCount() (int64, error) {
- if r.Commit == nil {
- return 0, nil
- }
- contextName := r.RefFullName.ShortName()
- isRef := r.RefFullName.IsBranch() || r.RefFullName.IsTag()
- return cache.GetInt64(r.Repository.GetCommitsCountCacheKey(contextName, isRef), func() (int64, error) {
- return r.Commit.CommitsCount()
- })
- }
-
- // GetCommitGraphsCount returns cached commit count for current view
- func (r *Repository) GetCommitGraphsCount(ctx context.Context, hidePRRefs bool, branches, files []string) (int64, error) {
- cacheKey := fmt.Sprintf("commits-count-%d-graph-%t-%s-%s", r.Repository.ID, hidePRRefs, branches, files)
-
- return cache.GetInt64(cacheKey, func() (int64, error) {
- if len(branches) == 0 {
- return git.AllCommitsCount(ctx, r.Repository.RepoPath(), hidePRRefs, files...)
- }
- return git.CommitsCount(ctx,
- git.CommitsCountOptions{
- RepoPath: r.Repository.RepoPath(),
- Revision: branches,
- RelPath: files,
- })
- })
- }
-
- // RefTypeNameSubURL makes a sub-url for the current ref (branch/tag/commit) field, for example:
- // * "branch/master"
- // * "tag/v1.0.0"
- // * "commit/123456"
- // It is usually used to construct a link like ".../src/{{RefTypeNameSubURL}}/{{PathEscapeSegments TreePath}}"
- func (r *Repository) RefTypeNameSubURL() string {
- return r.RefFullName.RefWebLinkPath()
- }
-
- // GetEditorconfig returns the .editorconfig definition if found in the
- // HEAD of the default repo branch.
- func (r *Repository) GetEditorconfig(optCommit ...*git.Commit) (cfg *editorconfig.Editorconfig, warning, err error) {
- if r.GitRepo == nil {
- return nil, nil, nil
- }
-
- var commit *git.Commit
-
- if len(optCommit) != 0 {
- commit = optCommit[0]
- } else {
- commit, err = r.GitRepo.GetBranchCommit(r.Repository.DefaultBranch)
- if err != nil {
- return nil, nil, err
- }
- }
- treeEntry, err := commit.GetTreeEntryByPath(".editorconfig")
- if err != nil {
- return nil, nil, err
- }
- if treeEntry.Blob().Size() >= setting.UI.MaxDisplayFileSize {
- return nil, nil, git.ErrNotExist{ID: "", RelPath: ".editorconfig"}
- }
- reader, err := treeEntry.Blob().DataAsync()
- if err != nil {
- return nil, nil, err
- }
- defer reader.Close()
- return editorconfig.ParseGraceful(reader)
- }
-
- // RetrieveBaseRepo retrieves base repository
- func RetrieveBaseRepo(ctx *Context, repo *repo_model.Repository) {
- // Non-fork repository will not return error in this method.
- if err := repo.GetBaseRepo(ctx); err != nil {
- if repo_model.IsErrRepoNotExist(err) {
- repo.IsFork = false
- repo.ForkID = 0
- return
- }
- ctx.ServerError("GetBaseRepo", err)
- return
- } else if err = repo.BaseRepo.LoadOwner(ctx); err != nil {
- ctx.ServerError("BaseRepo.LoadOwner", err)
- return
- }
- }
-
- // RetrieveTemplateRepo retrieves template repository used to generate this repository
- func RetrieveTemplateRepo(ctx *Context, repo *repo_model.Repository) {
- // Non-generated repository will not return error in this method.
- templateRepo, err := repo_model.GetTemplateRepo(ctx, repo)
- if err != nil {
- if repo_model.IsErrRepoNotExist(err) {
- repo.TemplateID = 0
- return
- }
- ctx.ServerError("GetTemplateRepo", err)
- return
- } else if err = templateRepo.LoadOwner(ctx); err != nil {
- ctx.ServerError("TemplateRepo.LoadOwner", err)
- return
- }
-
- perm, err := access_model.GetUserRepoPermission(ctx, templateRepo, ctx.Doer)
- if err != nil {
- ctx.ServerError("GetUserRepoPermission", err)
- return
- }
-
- if !perm.CanRead(unit_model.TypeCode) {
- repo.TemplateID = 0
- }
- }
-
- // ComposeGoGetImport returns go-get-import meta content.
- func ComposeGoGetImport(ctx context.Context, owner, repo string) string {
- curAppURL, _ := url.Parse(httplib.GuessCurrentAppURL(ctx))
- return path.Join(curAppURL.Host, setting.AppSubURL, url.PathEscape(owner), url.PathEscape(repo))
- }
-
- // EarlyResponseForGoGetMeta responses appropriate go-get meta with status 200
- // if user does not have actual access to the requested repository,
- // or the owner or repository does not exist at all.
- // This is particular a workaround for "go get" command which does not respect
- // .netrc file.
- func EarlyResponseForGoGetMeta(ctx *Context) {
- username := ctx.PathParam("username")
- reponame := strings.TrimSuffix(ctx.PathParam("reponame"), ".git")
- if username == "" || reponame == "" {
- ctx.PlainText(http.StatusBadRequest, "invalid repository path")
- return
- }
-
- var cloneURL string
- if setting.Repository.GoGetCloneURLProtocol == "ssh" {
- cloneURL = repo_model.ComposeSSHCloneURL(ctx.Doer, username, reponame)
- } else {
- cloneURL = repo_model.ComposeHTTPSCloneURL(ctx, username, reponame)
- }
- goImportContent := fmt.Sprintf("%s git %s", ComposeGoGetImport(ctx, username, reponame), cloneURL)
- htmlMeta := fmt.Sprintf(`<meta name="go-import" content="%s">`, html.EscapeString(goImportContent))
- ctx.PlainText(http.StatusOK, htmlMeta)
- }
-
- // RedirectToRepo redirect to a differently-named repository
- func RedirectToRepo(ctx *Base, redirectRepoID int64) {
- ownerName := ctx.PathParam("username")
- previousRepoName := ctx.PathParam("reponame")
-
- repo, err := repo_model.GetRepositoryByID(ctx, redirectRepoID)
- if err != nil {
- log.Error("GetRepositoryByID: %v", err)
- ctx.HTTPError(http.StatusInternalServerError, "GetRepositoryByID")
- return
- }
-
- redirectPath := strings.Replace(
- ctx.Req.URL.EscapedPath(),
- url.PathEscape(ownerName)+"/"+url.PathEscape(previousRepoName),
- url.PathEscape(repo.OwnerName)+"/"+url.PathEscape(repo.Name),
- 1,
- )
- if ctx.Req.URL.RawQuery != "" {
- redirectPath += "?" + ctx.Req.URL.RawQuery
- }
- // Git client needs a 301 redirect by default to follow the new location
- // It's not documentated in git documentation, but it's the behavior of git client
- ctx.Redirect(path.Join(setting.AppSubURL, redirectPath), http.StatusMovedPermanently)
- }
-
- func repoAssignment(ctx *Context, repo *repo_model.Repository) {
- var err error
- if err = repo.LoadOwner(ctx); err != nil {
- ctx.ServerError("LoadOwner", err)
- return
- }
-
- if ctx.DoerNeedTwoFactorAuth() {
- ctx.Repo.Permission = access_model.PermissionNoAccess()
- } else {
- ctx.Repo.Permission, err = access_model.GetUserRepoPermission(ctx, repo, ctx.Doer)
- if err != nil {
- ctx.ServerError("GetUserRepoPermission", err)
- return
- }
- }
-
- if !ctx.Repo.Permission.HasAnyUnitAccessOrPublicAccess() && !canWriteAsMaintainer(ctx) {
- if ctx.FormString("go-get") == "1" {
- EarlyResponseForGoGetMeta(ctx)
- return
- }
- ctx.NotFound(nil)
- return
- }
- ctx.Data["Permission"] = &ctx.Repo.Permission
-
- if repo.IsMirror {
- pullMirror, err := repo_model.GetMirrorByRepoID(ctx, repo.ID)
- if err == nil {
- ctx.Data["PullMirror"] = pullMirror
- } else if err != repo_model.ErrMirrorNotExist {
- ctx.ServerError("GetMirrorByRepoID", err)
- return
- }
- }
-
- ctx.Repo.Repository = repo
- ctx.Data["RepoName"] = ctx.Repo.Repository.Name
- ctx.Data["IsEmptyRepo"] = ctx.Repo.Repository.IsEmpty
- }
-
- // RepoAssignment returns a middleware to handle repository assignment
- func RepoAssignment(ctx *Context) {
- if ctx.Data["Repository"] != nil {
- setting.PanicInDevOrTesting("RepoAssignment should not be executed twice")
- }
-
- var err error
- userName := ctx.PathParam("username")
- repoName := ctx.PathParam("reponame")
- repoName = strings.TrimSuffix(repoName, ".git")
- if setting.Other.EnableFeed {
- ctx.Data["EnableFeed"] = true
- repoName = strings.TrimSuffix(repoName, ".rss")
- repoName = strings.TrimSuffix(repoName, ".atom")
- }
-
- // Check if the user is the same as the repository owner
- if ctx.IsSigned && strings.EqualFold(ctx.Doer.LowerName, userName) {
- ctx.Repo.Owner = ctx.Doer
- } else {
- ctx.Repo.Owner, err = user_model.GetUserByName(ctx, userName)
- if err != nil {
- if user_model.IsErrUserNotExist(err) {
- // go-get does not support redirects
- // https://github.com/golang/go/issues/19760
- if ctx.FormString("go-get") == "1" {
- EarlyResponseForGoGetMeta(ctx)
- return
- }
-
- if redirectUserID, err := user_model.LookupUserRedirect(ctx, userName); err == nil {
- RedirectToUser(ctx.Base, userName, redirectUserID)
- } else if user_model.IsErrUserRedirectNotExist(err) {
- ctx.NotFound(nil)
- } else {
- ctx.ServerError("LookupUserRedirect", err)
- }
- } else {
- ctx.ServerError("GetUserByName", err)
- }
- return
- }
- }
- ctx.ContextUser = ctx.Repo.Owner
- ctx.Data["ContextUser"] = ctx.ContextUser
-
- // redirect link to wiki
- if strings.HasSuffix(repoName, ".wiki") {
- // ctx.Req.URL.Path does not have the preceding appSubURL - any redirect must have this added
- // Now we happen to know that all of our paths are: /:username/:reponame/whatever_else
- originalRepoName := ctx.PathParam("reponame")
- redirectRepoName := strings.TrimSuffix(repoName, ".wiki")
- redirectRepoName += originalRepoName[len(redirectRepoName)+5:]
- redirectPath := strings.Replace(
- ctx.Req.URL.EscapedPath(),
- url.PathEscape(userName)+"/"+url.PathEscape(originalRepoName),
- url.PathEscape(userName)+"/"+url.PathEscape(redirectRepoName)+"/wiki",
- 1,
- )
- if ctx.Req.URL.RawQuery != "" {
- redirectPath += "?" + ctx.Req.URL.RawQuery
- }
- ctx.Redirect(path.Join(setting.AppSubURL, redirectPath))
- return
- }
-
- // Get repository.
- repo, err := repo_model.GetRepositoryByName(ctx, ctx.Repo.Owner.ID, repoName)
- if err != nil {
- if repo_model.IsErrRepoNotExist(err) {
- redirectRepoID, err := repo_model.LookupRedirect(ctx, ctx.Repo.Owner.ID, repoName)
- if err == nil {
- RedirectToRepo(ctx.Base, redirectRepoID)
- } else if repo_model.IsErrRedirectNotExist(err) {
- if ctx.FormString("go-get") == "1" {
- EarlyResponseForGoGetMeta(ctx)
- return
- }
- ctx.NotFound(nil)
- } else {
- ctx.ServerError("LookupRepoRedirect", err)
- }
- } else {
- ctx.ServerError("GetRepositoryByName", err)
- }
- return
- }
- repo.Owner = ctx.Repo.Owner
-
- repoAssignment(ctx, repo)
- if ctx.Written() {
- return
- }
-
- ctx.Repo.RepoLink = repo.Link()
- ctx.Data["RepoLink"] = ctx.Repo.RepoLink
- ctx.Data["FeedURL"] = ctx.Repo.RepoLink
-
- unit, err := ctx.Repo.Repository.GetUnit(ctx, unit_model.TypeExternalTracker)
- if err == nil {
- ctx.Data["RepoExternalIssuesLink"] = unit.ExternalTrackerConfig().ExternalTrackerURL
- }
-
- ctx.Data["NumTags"], err = db.Count[repo_model.Release](ctx, repo_model.FindReleasesOptions{
- IncludeDrafts: true,
- IncludeTags: true,
- HasSha1: optional.Some(true), // only draft releases which are created with existing tags
- RepoID: ctx.Repo.Repository.ID,
- })
- if err != nil {
- ctx.ServerError("GetReleaseCountByRepoID", err)
- return
- }
- ctx.Data["NumReleases"], err = db.Count[repo_model.Release](ctx, repo_model.FindReleasesOptions{
- // only show draft releases for users who can write, read-only users shouldn't see draft releases.
- IncludeDrafts: ctx.Repo.CanWrite(unit_model.TypeReleases),
- RepoID: ctx.Repo.Repository.ID,
- })
- if err != nil {
- ctx.ServerError("GetReleaseCountByRepoID", err)
- return
- }
-
- ctx.Data["Title"] = repo.Owner.Name + "/" + repo.Name
- ctx.Data["Repository"] = repo
- ctx.Data["Owner"] = ctx.Repo.Repository.Owner
- ctx.Data["CanWriteCode"] = ctx.Repo.CanWrite(unit_model.TypeCode)
- ctx.Data["CanWriteIssues"] = ctx.Repo.CanWrite(unit_model.TypeIssues)
- ctx.Data["CanWritePulls"] = ctx.Repo.CanWrite(unit_model.TypePullRequests)
- ctx.Data["CanWriteActions"] = ctx.Repo.CanWrite(unit_model.TypeActions)
-
- canSignedUserFork, err := repo_module.CanUserForkRepo(ctx, ctx.Doer, ctx.Repo.Repository)
- if err != nil {
- ctx.ServerError("CanUserForkRepo", err)
- return
- }
- ctx.Data["CanSignedUserFork"] = canSignedUserFork
-
- userAndOrgForks, err := repo_model.GetForksByUserAndOrgs(ctx, ctx.Doer, ctx.Repo.Repository)
- if err != nil {
- ctx.ServerError("GetForksByUserAndOrgs", err)
- return
- }
- ctx.Data["UserAndOrgForks"] = userAndOrgForks
-
- // canSignedUserFork is true if the current user doesn't have a fork of this repo yet or
- // if he owns an org that doesn't have a fork of this repo yet
- // If multiple forks are available or if the user can fork to another account, but there is already a fork: open selection dialog
- ctx.Data["ShowForkModal"] = len(userAndOrgForks) > 1 || (canSignedUserFork && len(userAndOrgForks) > 0)
-
- ctx.Data["RepoCloneLink"] = repo.CloneLink(ctx, ctx.Doer)
-
- cloneButtonShowHTTPS := !setting.Repository.DisableHTTPGit
- cloneButtonShowSSH := !setting.SSH.Disabled && (ctx.IsSigned || setting.SSH.ExposeAnonymous)
- if !cloneButtonShowHTTPS && !cloneButtonShowSSH {
- // We have to show at least one link, so we just show the HTTPS
- cloneButtonShowHTTPS = true
- }
- ctx.Data["CloneButtonShowHTTPS"] = cloneButtonShowHTTPS
- ctx.Data["CloneButtonShowSSH"] = cloneButtonShowSSH
- ctx.Data["CloneButtonOriginLink"] = ctx.Data["RepoCloneLink"] // it may be rewritten to the WikiCloneLink by the router middleware
-
- ctx.Data["RepoSearchEnabled"] = setting.Indexer.RepoIndexerEnabled
- if setting.Indexer.RepoIndexerEnabled {
- ctx.Data["CodeIndexerUnavailable"] = !code_indexer.IsAvailable(ctx)
- }
-
- if ctx.IsSigned {
- ctx.Data["IsWatchingRepo"] = repo_model.IsWatching(ctx, ctx.Doer.ID, repo.ID)
- ctx.Data["IsStaringRepo"] = repo_model.IsStaring(ctx, ctx.Doer.ID, repo.ID)
- }
-
- if repo.IsFork {
- RetrieveBaseRepo(ctx, repo)
- if ctx.Written() {
- return
- }
- }
-
- if repo.IsGenerated() {
- RetrieveTemplateRepo(ctx, repo)
- if ctx.Written() {
- return
- }
- }
-
- isHomeOrSettings := ctx.Link == ctx.Repo.RepoLink ||
- ctx.Link == ctx.Repo.RepoLink+"/settings" ||
- strings.HasPrefix(ctx.Link, ctx.Repo.RepoLink+"/settings/") ||
- ctx.Link == ctx.Repo.RepoLink+"/-/migrate/status"
-
- // Disable everything when the repo is being created
- if ctx.Repo.Repository.IsBeingCreated() || ctx.Repo.Repository.IsBroken() {
- if !isHomeOrSettings {
- ctx.Redirect(ctx.Repo.RepoLink)
- }
- return
- }
-
- if ctx.Repo.GitRepo != nil {
- setting.PanicInDevOrTesting("RepoAssignment: GitRepo should be nil")
- _ = ctx.Repo.GitRepo.Close()
- ctx.Repo.GitRepo = nil
- }
-
- ctx.Repo.GitRepo, err = gitrepo.RepositoryFromRequestContextOrOpen(ctx, repo)
- if err != nil {
- if strings.Contains(err.Error(), "repository does not exist") || strings.Contains(err.Error(), "no such file or directory") {
- log.Error("Repository %-v has a broken repository on the file system: %s Error: %v", ctx.Repo.Repository, ctx.Repo.Repository.RepoPath(), err)
- ctx.Repo.Repository.MarkAsBrokenEmpty()
- // Only allow access to base of repo or settings
- if !isHomeOrSettings {
- ctx.Redirect(ctx.Repo.RepoLink)
- }
- return
- }
- ctx.ServerError("RepoAssignment Invalid repo "+repo.FullName(), err)
- return
- }
-
- // Stop at this point when the repo is empty.
- if ctx.Repo.Repository.IsEmpty {
- return
- }
-
- branchOpts := git_model.FindBranchOptions{
- RepoID: ctx.Repo.Repository.ID,
- IsDeletedBranch: optional.Some(false),
- ListOptions: db.ListOptionsAll,
- }
- branchesTotal, err := db.Count[git_model.Branch](ctx, branchOpts)
- if err != nil {
- ctx.ServerError("CountBranches", err)
- return
- }
-
- // non-empty repo should have at least 1 branch, so this repository's branches haven't been synced yet
- if branchesTotal == 0 { // fallback to do a sync immediately
- branchesTotal, err = repo_module.SyncRepoBranches(ctx, ctx.Repo.Repository.ID, 0)
- if err != nil {
- ctx.ServerError("SyncRepoBranches", err)
- return
- }
- }
-
- ctx.Data["BranchesCount"] = branchesTotal
-
- // People who have push access or have forked repository can propose a new pull request.
- canPush := ctx.Repo.CanWrite(unit_model.TypeCode) ||
- (ctx.IsSigned && repo_model.HasForkedRepo(ctx, ctx.Doer.ID, ctx.Repo.Repository.ID))
- canCompare := false
-
- // Pull request is allowed if this is a fork repository
- // and base repository accepts pull requests.
- if repo.BaseRepo != nil && repo.BaseRepo.AllowsPulls(ctx) {
- canCompare = true
- ctx.Data["BaseRepo"] = repo.BaseRepo
- ctx.Repo.PullRequest.BaseRepo = repo.BaseRepo
- ctx.Repo.PullRequest.Allowed = canPush
- } else if repo.AllowsPulls(ctx) {
- // Or, this is repository accepts pull requests between branches.
- canCompare = true
- ctx.Data["BaseRepo"] = repo
- ctx.Repo.PullRequest.BaseRepo = repo
- ctx.Repo.PullRequest.Allowed = canPush
- ctx.Repo.PullRequest.SameRepo = true
- }
- ctx.Data["CanCompareOrPull"] = canCompare
- ctx.Data["PullRequestCtx"] = ctx.Repo.PullRequest
-
- if ctx.Repo.Repository.Status == repo_model.RepositoryPendingTransfer {
- repoTransfer, err := repo_model.GetPendingRepositoryTransfer(ctx, ctx.Repo.Repository)
- if err != nil {
- ctx.ServerError("GetPendingRepositoryTransfer", err)
- return
- }
-
- if err := repoTransfer.LoadAttributes(ctx); err != nil {
- ctx.ServerError("LoadRecipient", err)
- return
- }
-
- ctx.Data["RepoTransfer"] = repoTransfer
- if ctx.Doer != nil {
- ctx.Data["CanUserAcceptOrRejectTransfer"] = repoTransfer.CanUserAcceptOrRejectTransfer(ctx, ctx.Doer)
- }
- }
-
- if ctx.FormString("go-get") == "1" {
- ctx.Data["GoGetImport"] = ComposeGoGetImport(ctx, repo.Owner.Name, repo.Name)
- fullURLPrefix := repo.HTMLURL() + "/src/branch/" + util.PathEscapeSegments(ctx.Repo.BranchName)
- ctx.Data["GoDocDirectory"] = fullURLPrefix + "{/dir}"
- ctx.Data["GoDocFile"] = fullURLPrefix + "{/dir}/{file}#L{line}"
- }
- }
-
- const headRefName = "HEAD"
-
- func getRefNameFromPath(repo *Repository, path string, isExist func(string) bool) string {
- refName := ""
- parts := strings.Split(path, "/")
- for i, part := range parts {
- refName = strings.TrimPrefix(refName+"/"+part, "/")
- if isExist(refName) {
- repo.TreePath = strings.Join(parts[i+1:], "/")
- return refName
- }
- }
- return ""
- }
-
- func getRefNameLegacy(ctx *Base, repo *Repository, reqPath, extraRef string) (refName string, refType git.RefType, fallbackDefaultBranch bool) {
- reqRefPath := path.Join(extraRef, reqPath)
- reqRefPathParts := strings.Split(reqRefPath, "/")
- if refName := getRefName(ctx, repo, reqRefPath, git.RefTypeBranch); refName != "" {
- return refName, git.RefTypeBranch, false
- }
- if refName := getRefName(ctx, repo, reqRefPath, git.RefTypeTag); refName != "" {
- return refName, git.RefTypeTag, false
- }
- if git.IsStringLikelyCommitID(git.ObjectFormatFromName(repo.Repository.ObjectFormatName), reqRefPathParts[0]) {
- // FIXME: this logic is different from other types. Ideally, it should also try to GetCommit to check if it exists
- repo.TreePath = strings.Join(reqRefPathParts[1:], "/")
- return reqRefPathParts[0], git.RefTypeCommit, false
- }
- // FIXME: the old code falls back to default branch if "ref" doesn't exist, there could be an edge case:
- // "README?ref=no-such" would read the README file from the default branch, but the user might expect a 404
- repo.TreePath = reqPath
- return repo.Repository.DefaultBranch, git.RefTypeBranch, true
- }
-
- func getRefName(ctx *Base, repo *Repository, path string, refType git.RefType) string {
- switch refType {
- case git.RefTypeBranch:
- ref := getRefNameFromPath(repo, path, repo.GitRepo.IsBranchExist)
- if len(ref) == 0 {
- // check if ref is HEAD
- parts := strings.Split(path, "/")
- if parts[0] == headRefName {
- repo.TreePath = strings.Join(parts[1:], "/")
- return repo.Repository.DefaultBranch
- }
-
- // maybe it's a renamed branch
- return getRefNameFromPath(repo, path, func(s string) bool {
- b, exist, err := git_model.FindRenamedBranch(ctx, repo.Repository.ID, s)
- if err != nil {
- log.Error("FindRenamedBranch: %v", err)
- return false
- }
-
- if !exist {
- return false
- }
-
- ctx.Data["IsRenamedBranch"] = true
- ctx.Data["RenamedBranchName"] = b.To
-
- return true
- })
- }
-
- return ref
- case git.RefTypeTag:
- return getRefNameFromPath(repo, path, repo.GitRepo.IsTagExist)
- case git.RefTypeCommit:
- parts := strings.Split(path, "/")
- if git.IsStringLikelyCommitID(repo.GetObjectFormat(), parts[0], 7) {
- // FIXME: this logic is different from other types. Ideally, it should also try to GetCommit to check if it exists
- repo.TreePath = strings.Join(parts[1:], "/")
- return parts[0]
- }
-
- if parts[0] == headRefName {
- // HEAD ref points to last default branch commit
- commit, err := repo.GitRepo.GetBranchCommit(repo.Repository.DefaultBranch)
- if err != nil {
- return ""
- }
- repo.TreePath = strings.Join(parts[1:], "/")
- return commit.ID.String()
- }
- default:
- panic(fmt.Sprintf("Unrecognized ref type: %v", refType))
- }
- return ""
- }
-
- func repoRefFullName(typ git.RefType, shortName string) git.RefName {
- switch typ {
- case git.RefTypeBranch:
- return git.RefNameFromBranch(shortName)
- case git.RefTypeTag:
- return git.RefNameFromTag(shortName)
- case git.RefTypeCommit:
- return git.RefNameFromCommit(shortName)
- default:
- setting.PanicInDevOrTesting("Unknown RepoRefType: %v", typ)
- return git.RefNameFromBranch("main") // just a dummy result, it shouldn't happen
- }
- }
-
- func RepoRefByDefaultBranch() func(*Context) {
- return func(ctx *Context) {
- ctx.Repo.RefFullName = git.RefNameFromBranch(ctx.Repo.Repository.DefaultBranch)
- ctx.Repo.BranchName = ctx.Repo.Repository.DefaultBranch
- ctx.Repo.Commit, _ = ctx.Repo.GitRepo.GetBranchCommit(ctx.Repo.BranchName)
- ctx.Repo.CommitsCount, _ = ctx.Repo.GetCommitsCount()
- ctx.Data["RefFullName"] = ctx.Repo.RefFullName
- ctx.Data["BranchName"] = ctx.Repo.BranchName
- ctx.Data["CommitsCount"] = ctx.Repo.CommitsCount
- }
- }
-
- // RepoRefByType handles repository reference name for a specific type
- // of repository reference
- func RepoRefByType(detectRefType git.RefType) func(*Context) {
- return func(ctx *Context) {
- var err error
- refType := detectRefType
- if ctx.Repo.Repository.IsBeingCreated() || ctx.Repo.Repository.IsBroken() {
- return // no git repo, so do nothing, users will see a "migrating" UI provided by "migrate/migrating.tmpl", or empty repo guide
- }
- // Empty repository does not have reference information.
- if ctx.Repo.Repository.IsEmpty {
- // assume the user is viewing the (non-existent) default branch
- ctx.Repo.BranchName = ctx.Repo.Repository.DefaultBranch
- ctx.Repo.RefFullName = git.RefNameFromBranch(ctx.Repo.BranchName)
- // these variables are used by the template to "add/upload" new files
- ctx.Data["BranchName"] = ctx.Repo.BranchName
- ctx.Data["TreePath"] = ""
- return
- }
-
- // Get default branch.
- var refShortName string
- reqPath := ctx.PathParam("*")
- if reqPath == "" {
- refShortName = ctx.Repo.Repository.DefaultBranch
- if !gitrepo.IsBranchExist(ctx, ctx.Repo.Repository, refShortName) {
- brs, _, err := ctx.Repo.GitRepo.GetBranchNames(0, 1)
- if err == nil && len(brs) != 0 {
- refShortName = brs[0]
- } else if len(brs) == 0 {
- log.Error("No branches in non-empty repository %s", ctx.Repo.GitRepo.Path)
- } else {
- log.Error("GetBranches error: %v", err)
- }
- }
- ctx.Repo.RefFullName = git.RefNameFromBranch(refShortName)
- ctx.Repo.BranchName = refShortName
- ctx.Repo.Commit, err = ctx.Repo.GitRepo.GetBranchCommit(refShortName)
- if err == nil {
- ctx.Repo.CommitID = ctx.Repo.Commit.ID.String()
- } else if strings.Contains(err.Error(), "fatal: not a git repository") || strings.Contains(err.Error(), "object does not exist") {
- // if the repository is broken, we can continue to the handler code, to show "Settings -> Delete Repository" for end users
- log.Error("GetBranchCommit: %v", err)
- } else {
- ctx.ServerError("GetBranchCommit", err)
- return
- }
- } else { // there is a path in request
- guessLegacyPath := refType == ""
- fallbackDefaultBranch := false
- if guessLegacyPath {
- refShortName, refType, fallbackDefaultBranch = getRefNameLegacy(ctx.Base, ctx.Repo, reqPath, "")
- } else {
- refShortName = getRefName(ctx.Base, ctx.Repo, reqPath, refType)
- }
- ctx.Repo.RefFullName = repoRefFullName(refType, refShortName)
- isRenamedBranch, has := ctx.Data["IsRenamedBranch"].(bool)
- if isRenamedBranch && has {
- renamedBranchName := ctx.Data["RenamedBranchName"].(string)
- ctx.Flash.Info(ctx.Tr("repo.branch.renamed", refShortName, renamedBranchName))
- link := setting.AppSubURL + strings.Replace(ctx.Req.URL.EscapedPath(), util.PathEscapeSegments(refShortName), util.PathEscapeSegments(renamedBranchName), 1)
- ctx.Redirect(link)
- return
- }
-
- if refType == git.RefTypeBranch && gitrepo.IsBranchExist(ctx, ctx.Repo.Repository, refShortName) {
- ctx.Repo.BranchName = refShortName
- ctx.Repo.RefFullName = git.RefNameFromBranch(refShortName)
-
- ctx.Repo.Commit, err = ctx.Repo.GitRepo.GetBranchCommit(refShortName)
- if err != nil {
- ctx.ServerError("GetBranchCommit", err)
- return
- }
- ctx.Repo.CommitID = ctx.Repo.Commit.ID.String()
- } else if refType == git.RefTypeTag && gitrepo.IsTagExist(ctx, ctx.Repo.Repository, refShortName) {
- ctx.Repo.RefFullName = git.RefNameFromTag(refShortName)
-
- ctx.Repo.Commit, err = ctx.Repo.GitRepo.GetTagCommit(refShortName)
- if err != nil {
- if git.IsErrNotExist(err) {
- ctx.NotFound(err)
- return
- }
- ctx.ServerError("GetTagCommit", err)
- return
- }
- ctx.Repo.CommitID = ctx.Repo.Commit.ID.String()
- } else if git.IsStringLikelyCommitID(ctx.Repo.GetObjectFormat(), refShortName, 7) {
- ctx.Repo.RefFullName = git.RefNameFromCommit(refShortName)
- ctx.Repo.CommitID = refShortName
-
- ctx.Repo.Commit, err = ctx.Repo.GitRepo.GetCommit(refShortName)
- if err != nil {
- ctx.NotFound(err)
- return
- }
- // If short commit ID add canonical link header
- if len(refShortName) < ctx.Repo.GetObjectFormat().FullLength() {
- canonicalURL := util.URLJoin(httplib.GuessCurrentAppURL(ctx), strings.Replace(ctx.Req.URL.RequestURI(), util.PathEscapeSegments(refShortName), url.PathEscape(ctx.Repo.Commit.ID.String()), 1))
- ctx.RespHeader().Set("Link", fmt.Sprintf(`<%s>; rel="canonical"`, canonicalURL))
- }
- } else {
- ctx.NotFound(fmt.Errorf("branch or tag not exist: %s", refShortName))
- return
- }
-
- if guessLegacyPath {
- // redirect from old URL scheme to new URL scheme
- // * /user2/repo1/commits/master => /user2/repo1/commits/branch/master
- // * /user2/repo1/src/master => /user2/repo1/src/branch/master
- // * /user2/repo1/src/README.md => /user2/repo1/src/branch/master/README.md (fallback to default branch)
- var redirect string
- refSubPath := "src"
- // remove the "/subpath/owner/repo/" prefix, the names are case-insensitive
- remainingLowerPath, cut := strings.CutPrefix(setting.AppSubURL+strings.ToLower(ctx.Req.URL.Path), strings.ToLower(ctx.Repo.RepoLink)+"/")
- if cut {
- refSubPath, _, _ = strings.Cut(remainingLowerPath, "/") // it could be "src" or "commits"
- }
- if fallbackDefaultBranch {
- redirect = fmt.Sprintf("%s/%s/%s/%s/%s", ctx.Repo.RepoLink, refSubPath, refType, util.PathEscapeSegments(refShortName), ctx.PathParamRaw("*"))
- } else {
- redirect = fmt.Sprintf("%s/%s/%s/%s", ctx.Repo.RepoLink, refSubPath, refType, ctx.PathParamRaw("*"))
- }
- if ctx.Req.URL.RawQuery != "" {
- redirect += "?" + ctx.Req.URL.RawQuery
- }
- ctx.Redirect(redirect)
- return
- }
- }
-
- ctx.Data["RefFullName"] = ctx.Repo.RefFullName
- ctx.Data["RefTypeNameSubURL"] = ctx.Repo.RefTypeNameSubURL()
- ctx.Data["TreePath"] = ctx.Repo.TreePath
-
- ctx.Data["BranchName"] = ctx.Repo.BranchName
-
- ctx.Data["CommitID"] = ctx.Repo.CommitID
-
- ctx.Data["CanCreateBranch"] = ctx.Repo.CanCreateBranch() // only used by the branch selector dropdown: AllowCreateNewRef
-
- ctx.Repo.CommitsCount, err = ctx.Repo.GetCommitsCount()
- if err != nil {
- ctx.ServerError("GetCommitsCount", err)
- return
- }
- if ctx.Repo.RefFullName.IsTag() {
- rel, err := repo_model.GetRelease(ctx, ctx.Repo.Repository.ID, ctx.Repo.RefFullName.TagName())
- if err == nil && rel.NumCommits <= 0 {
- rel.NumCommits = ctx.Repo.CommitsCount
- if err := repo_model.UpdateReleaseNumCommits(ctx, rel); err != nil {
- log.Error("UpdateReleaseNumCommits", err)
- }
- }
- }
- ctx.Data["CommitsCount"] = ctx.Repo.CommitsCount
- ctx.Repo.GitRepo.LastCommitCache = git.NewLastCommitCache(ctx.Repo.CommitsCount, ctx.Repo.Repository.FullName(), ctx.Repo.GitRepo, cache.GetCache())
- }
- }
-
- // GitHookService checks if repository Git hooks service has been enabled.
- func GitHookService() func(ctx *Context) {
- return func(ctx *Context) {
- if !ctx.Doer.CanEditGitHook() {
- ctx.NotFound(nil)
- return
- }
- }
- }
-
- // canWriteAsMaintainer check if the doer can write to a branch as a maintainer
- func canWriteAsMaintainer(ctx *Context) bool {
- branchName := getRefNameFromPath(ctx.Repo, ctx.PathParam("*"), func(branchName string) bool {
- return issues_model.CanMaintainerWriteToBranch(ctx, ctx.Repo.Permission, branchName, ctx.Doer)
- })
- return len(branchName) > 0
- }
|