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  }