code.gitea.io/gitea@v1.19.3/modules/context/repo.go (about)

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