123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900 |
- // Copyright 2021 The Gitea Authors. All rights reserved.
- // SPDX-License-Identifier: MIT
-
- package repo
-
- import (
- "context"
- "fmt"
- "html/template"
- "net"
- "net/url"
- "path/filepath"
- "strconv"
- "strings"
-
- "code.gitea.io/gitea/models/db"
- "code.gitea.io/gitea/models/unit"
- user_model "code.gitea.io/gitea/models/user"
- "code.gitea.io/gitea/modules/base"
- "code.gitea.io/gitea/modules/log"
- "code.gitea.io/gitea/modules/markup"
- "code.gitea.io/gitea/modules/setting"
- api "code.gitea.io/gitea/modules/structs"
- "code.gitea.io/gitea/modules/timeutil"
- "code.gitea.io/gitea/modules/util"
-
- "xorm.io/builder"
- )
-
- // ErrUserDoesNotHaveAccessToRepo represents an error where the user doesn't has access to a given repo.
- type ErrUserDoesNotHaveAccessToRepo struct {
- UserID int64
- RepoName string
- }
-
- // IsErrUserDoesNotHaveAccessToRepo checks if an error is a ErrRepoFileAlreadyExists.
- func IsErrUserDoesNotHaveAccessToRepo(err error) bool {
- _, ok := err.(ErrUserDoesNotHaveAccessToRepo)
- return ok
- }
-
- func (err ErrUserDoesNotHaveAccessToRepo) Error() string {
- return fmt.Sprintf("user doesn't have access to repo [user_id: %d, repo_name: %s]", err.UserID, err.RepoName)
- }
-
- func (err ErrUserDoesNotHaveAccessToRepo) Unwrap() error {
- return util.ErrPermissionDenied
- }
-
- type ErrRepoIsArchived struct {
- Repo *Repository
- }
-
- func (err ErrRepoIsArchived) Error() string {
- return fmt.Sprintf("%s is archived", err.Repo.LogString())
- }
-
- var (
- reservedRepoNames = []string{".", "..", "-"}
- reservedRepoPatterns = []string{"*.git", "*.wiki", "*.rss", "*.atom"}
- )
-
- // IsUsableRepoName returns true when repository is usable
- func IsUsableRepoName(name string) error {
- if db.AlphaDashDotPattern.MatchString(name) {
- // Note: usually this error is normally caught up earlier in the UI
- return db.ErrNameCharsNotAllowed{Name: name}
- }
- return db.IsUsableName(reservedRepoNames, reservedRepoPatterns, name)
- }
-
- // TrustModelType defines the types of trust model for this repository
- type TrustModelType int
-
- // kinds of TrustModel
- const (
- DefaultTrustModel TrustModelType = iota // default trust model
- CommitterTrustModel
- CollaboratorTrustModel
- CollaboratorCommitterTrustModel
- )
-
- // String converts a TrustModelType to a string
- func (t TrustModelType) String() string {
- switch t {
- case DefaultTrustModel:
- return "default"
- case CommitterTrustModel:
- return "committer"
- case CollaboratorTrustModel:
- return "collaborator"
- case CollaboratorCommitterTrustModel:
- return "collaboratorcommitter"
- }
- return "default"
- }
-
- // ToTrustModel converts a string to a TrustModelType
- func ToTrustModel(model string) TrustModelType {
- switch strings.ToLower(strings.TrimSpace(model)) {
- case "default":
- return DefaultTrustModel
- case "collaborator":
- return CollaboratorTrustModel
- case "committer":
- return CommitterTrustModel
- case "collaboratorcommitter":
- return CollaboratorCommitterTrustModel
- }
- return DefaultTrustModel
- }
-
- // RepositoryStatus defines the status of repository
- type RepositoryStatus int
-
- // all kinds of RepositoryStatus
- const (
- RepositoryReady RepositoryStatus = iota // a normal repository
- RepositoryBeingMigrated // repository is migrating
- RepositoryPendingTransfer // repository pending in ownership transfer state
- RepositoryBroken // repository is in a permanently broken state
- )
-
- // Repository represents a git repository.
- type Repository struct {
- ID int64 `xorm:"pk autoincr"`
- OwnerID int64 `xorm:"UNIQUE(s) index"`
- OwnerName string
- Owner *user_model.User `xorm:"-"`
- LowerName string `xorm:"UNIQUE(s) INDEX NOT NULL"`
- Name string `xorm:"INDEX NOT NULL"`
- Description string `xorm:"TEXT"`
- Website string `xorm:"VARCHAR(2048)"`
- OriginalServiceType api.GitServiceType `xorm:"index"`
- OriginalURL string `xorm:"VARCHAR(2048)"`
- DefaultBranch string
-
- NumWatches int
- NumStars int
- NumForks int
- NumIssues int
- NumClosedIssues int
- NumOpenIssues int `xorm:"-"`
- NumPulls int
- NumClosedPulls int
- NumOpenPulls int `xorm:"-"`
- NumMilestones int `xorm:"NOT NULL DEFAULT 0"`
- NumClosedMilestones int `xorm:"NOT NULL DEFAULT 0"`
- NumOpenMilestones int `xorm:"-"`
- NumProjects int `xorm:"NOT NULL DEFAULT 0"`
- NumClosedProjects int `xorm:"NOT NULL DEFAULT 0"`
- NumOpenProjects int `xorm:"-"`
- NumActionRuns int `xorm:"NOT NULL DEFAULT 0"`
- NumClosedActionRuns int `xorm:"NOT NULL DEFAULT 0"`
- NumOpenActionRuns int `xorm:"-"`
-
- IsPrivate bool `xorm:"INDEX"`
- IsEmpty bool `xorm:"INDEX"`
- IsArchived bool `xorm:"INDEX"`
- IsMirror bool `xorm:"INDEX"`
-
- Status RepositoryStatus `xorm:"NOT NULL DEFAULT 0"`
-
- RenderingMetas map[string]string `xorm:"-"`
- DocumentRenderingMetas map[string]string `xorm:"-"`
- Units []*RepoUnit `xorm:"-"`
- PrimaryLanguage *LanguageStat `xorm:"-"`
-
- IsFork bool `xorm:"INDEX NOT NULL DEFAULT false"`
- ForkID int64 `xorm:"INDEX"`
- BaseRepo *Repository `xorm:"-"`
- IsTemplate bool `xorm:"INDEX NOT NULL DEFAULT false"`
- TemplateID int64 `xorm:"INDEX"`
- Size int64 `xorm:"NOT NULL DEFAULT 0"`
- GitSize int64 `xorm:"NOT NULL DEFAULT 0"`
- LFSSize int64 `xorm:"NOT NULL DEFAULT 0"`
- CodeIndexerStatus *RepoIndexerStatus `xorm:"-"`
- StatsIndexerStatus *RepoIndexerStatus `xorm:"-"`
- IsFsckEnabled bool `xorm:"NOT NULL DEFAULT true"`
- CloseIssuesViaCommitInAnyBranch bool `xorm:"NOT NULL DEFAULT false"`
- Topics []string `xorm:"TEXT JSON"`
-
- TrustModel TrustModelType
-
- // Avatar: ID(10-20)-md5(32) - must fit into 64 symbols
- Avatar string `xorm:"VARCHAR(64)"`
-
- CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
- UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"`
- ArchivedUnix timeutil.TimeStamp `xorm:"DEFAULT 0"`
- }
-
- func init() {
- db.RegisterModel(new(Repository))
- }
-
- // SanitizedOriginalURL returns a sanitized OriginalURL
- func (repo *Repository) SanitizedOriginalURL() string {
- if repo.OriginalURL == "" {
- return ""
- }
- u, _ := util.SanitizeURL(repo.OriginalURL)
- return u
- }
-
- // text representations to be returned in SizeDetail.Name
- const (
- SizeDetailNameGit = "git"
- SizeDetailNameLFS = "lfs"
- )
-
- type SizeDetail struct {
- Name string
- Size int64
- }
-
- // SizeDetails forms a struct with various size details about repository
- func (repo *Repository) SizeDetails() []SizeDetail {
- sizeDetails := []SizeDetail{
- {
- Name: SizeDetailNameGit,
- Size: repo.GitSize,
- },
- {
- Name: SizeDetailNameLFS,
- Size: repo.LFSSize,
- },
- }
- return sizeDetails
- }
-
- // SizeDetailsString returns a concatenation of all repository size details as a string
- func (repo *Repository) SizeDetailsString() string {
- var str strings.Builder
- sizeDetails := repo.SizeDetails()
- for _, detail := range sizeDetails {
- str.WriteString(fmt.Sprintf("%s: %s, ", detail.Name, base.FileSize(detail.Size)))
- }
- return strings.TrimSuffix(str.String(), ", ")
- }
-
- func (repo *Repository) LogString() string {
- if repo == nil {
- return "<Repository nil>"
- }
- return fmt.Sprintf("<Repository %d:%s/%s>", repo.ID, repo.OwnerName, repo.Name)
- }
-
- // IsBeingMigrated indicates that repository is being migrated
- func (repo *Repository) IsBeingMigrated() bool {
- return repo.Status == RepositoryBeingMigrated
- }
-
- // IsBeingCreated indicates that repository is being migrated or forked
- func (repo *Repository) IsBeingCreated() bool {
- return repo.IsBeingMigrated()
- }
-
- // IsBroken indicates that repository is broken
- func (repo *Repository) IsBroken() bool {
- return repo.Status == RepositoryBroken
- }
-
- // MarkAsBrokenEmpty marks the repo as broken and empty
- func (repo *Repository) MarkAsBrokenEmpty() {
- repo.Status = RepositoryBroken
- repo.IsEmpty = true
- }
-
- // AfterLoad is invoked from XORM after setting the values of all fields of this object.
- func (repo *Repository) AfterLoad() {
- repo.NumOpenIssues = repo.NumIssues - repo.NumClosedIssues
- repo.NumOpenPulls = repo.NumPulls - repo.NumClosedPulls
- repo.NumOpenMilestones = repo.NumMilestones - repo.NumClosedMilestones
- repo.NumOpenProjects = repo.NumProjects - repo.NumClosedProjects
- repo.NumOpenActionRuns = repo.NumActionRuns - repo.NumClosedActionRuns
- }
-
- // LoadAttributes loads attributes of the repository.
- func (repo *Repository) LoadAttributes(ctx context.Context) error {
- // Load owner
- if err := repo.LoadOwner(ctx); err != nil {
- return fmt.Errorf("load owner: %w", err)
- }
-
- // Load primary language
- stats := make(LanguageStatList, 0, 1)
- if err := db.GetEngine(ctx).
- Where("`repo_id` = ? AND `is_primary` = ? AND `language` != ?", repo.ID, true, "other").
- Find(&stats); err != nil {
- return fmt.Errorf("find primary languages: %w", err)
- }
- stats.LoadAttributes()
- for _, st := range stats {
- if st.RepoID == repo.ID {
- repo.PrimaryLanguage = st
- break
- }
- }
- return nil
- }
-
- // FullName returns the repository full name
- func (repo *Repository) FullName() string {
- return repo.OwnerName + "/" + repo.Name
- }
-
- // HTMLURL returns the repository HTML URL
- func (repo *Repository) HTMLURL() string {
- return setting.AppURL + url.PathEscape(repo.OwnerName) + "/" + url.PathEscape(repo.Name)
- }
-
- // CommitLink make link to by commit full ID
- // note: won't check whether it's an right id
- func (repo *Repository) CommitLink(commitID string) (result string) {
- if commitID == "" || commitID == "0000000000000000000000000000000000000000" {
- result = ""
- } else {
- result = repo.Link() + "/commit/" + url.PathEscape(commitID)
- }
- return result
- }
-
- // APIURL returns the repository API URL
- func (repo *Repository) APIURL() string {
- return setting.AppURL + "api/v1/repos/" + url.PathEscape(repo.OwnerName) + "/" + url.PathEscape(repo.Name)
- }
-
- // GetCommitsCountCacheKey returns cache key used for commits count caching.
- func (repo *Repository) GetCommitsCountCacheKey(contextName string, isRef bool) string {
- var prefix string
- if isRef {
- prefix = "ref"
- } else {
- prefix = "commit"
- }
- return fmt.Sprintf("commits-count-%d-%s-%s", repo.ID, prefix, contextName)
- }
-
- // LoadUnits loads repo units into repo.Units
- func (repo *Repository) LoadUnits(ctx context.Context) (err error) {
- if repo.Units != nil {
- return nil
- }
-
- repo.Units, err = getUnitsByRepoID(ctx, repo.ID)
- if log.IsTrace() {
- unitTypeStrings := make([]string, len(repo.Units))
- for i, unit := range repo.Units {
- unitTypeStrings[i] = unit.Type.String()
- }
- log.Trace("repo.Units, ID=%d, Types: [%s]", repo.ID, strings.Join(unitTypeStrings, ", "))
- }
-
- return err
- }
-
- // UnitEnabled if this repository has the given unit enabled
- func (repo *Repository) UnitEnabled(ctx context.Context, tp unit.Type) bool {
- if err := repo.LoadUnits(ctx); err != nil {
- log.Warn("Error loading repository (ID: %d) units: %s", repo.ID, err.Error())
- }
- for _, unit := range repo.Units {
- if unit.Type == tp {
- return true
- }
- }
- return false
- }
-
- // MustGetUnit always returns a RepoUnit object
- func (repo *Repository) MustGetUnit(ctx context.Context, tp unit.Type) *RepoUnit {
- ru, err := repo.GetUnit(ctx, tp)
- if err == nil {
- return ru
- }
-
- if tp == unit.TypeExternalWiki {
- return &RepoUnit{
- Type: tp,
- Config: new(ExternalWikiConfig),
- }
- } else if tp == unit.TypeExternalTracker {
- return &RepoUnit{
- Type: tp,
- Config: new(ExternalTrackerConfig),
- }
- } else if tp == unit.TypePullRequests {
- return &RepoUnit{
- Type: tp,
- Config: new(PullRequestsConfig),
- }
- } else if tp == unit.TypeIssues {
- return &RepoUnit{
- Type: tp,
- Config: new(IssuesConfig),
- }
- } else if tp == unit.TypeActions {
- return &RepoUnit{
- Type: tp,
- Config: new(ActionsConfig),
- }
- }
-
- return &RepoUnit{
- Type: tp,
- Config: new(UnitConfig),
- }
- }
-
- // GetUnit returns a RepoUnit object
- func (repo *Repository) GetUnit(ctx context.Context, tp unit.Type) (*RepoUnit, error) {
- if err := repo.LoadUnits(ctx); err != nil {
- return nil, err
- }
- for _, unit := range repo.Units {
- if unit.Type == tp {
- return unit, nil
- }
- }
- return nil, ErrUnitTypeNotExist{tp}
- }
-
- // LoadOwner loads owner user
- func (repo *Repository) LoadOwner(ctx context.Context) (err error) {
- if repo.Owner != nil {
- return nil
- }
-
- repo.Owner, err = user_model.GetUserByID(ctx, repo.OwnerID)
- return err
- }
-
- // MustOwner always returns a valid *user_model.User object to avoid
- // conceptually impossible error handling.
- // It creates a fake object that contains error details
- // when error occurs.
- func (repo *Repository) MustOwner(ctx context.Context) *user_model.User {
- if err := repo.LoadOwner(ctx); err != nil {
- return &user_model.User{
- Name: "error",
- FullName: err.Error(),
- }
- }
-
- return repo.Owner
- }
-
- // ComposeMetas composes a map of metas for properly rendering issue links and external issue trackers.
- func (repo *Repository) ComposeMetas() map[string]string {
- if len(repo.RenderingMetas) == 0 {
- metas := map[string]string{
- "user": repo.OwnerName,
- "repo": repo.Name,
- "repoPath": repo.RepoPath(),
- "mode": "comment",
- }
-
- unit, err := repo.GetUnit(db.DefaultContext, unit.TypeExternalTracker)
- if err == nil {
- metas["format"] = unit.ExternalTrackerConfig().ExternalTrackerFormat
- switch unit.ExternalTrackerConfig().ExternalTrackerStyle {
- case markup.IssueNameStyleAlphanumeric:
- metas["style"] = markup.IssueNameStyleAlphanumeric
- case markup.IssueNameStyleRegexp:
- metas["style"] = markup.IssueNameStyleRegexp
- metas["regexp"] = unit.ExternalTrackerConfig().ExternalTrackerRegexpPattern
- default:
- metas["style"] = markup.IssueNameStyleNumeric
- }
- }
-
- repo.MustOwner(db.DefaultContext)
- if repo.Owner.IsOrganization() {
- teams := make([]string, 0, 5)
- _ = db.GetEngine(db.DefaultContext).Table("team_repo").
- Join("INNER", "team", "team.id = team_repo.team_id").
- Where("team_repo.repo_id = ?", repo.ID).
- Select("team.lower_name").
- OrderBy("team.lower_name").
- Find(&teams)
- metas["teams"] = "," + strings.Join(teams, ",") + ","
- metas["org"] = strings.ToLower(repo.OwnerName)
- }
-
- repo.RenderingMetas = metas
- }
- return repo.RenderingMetas
- }
-
- // ComposeDocumentMetas composes a map of metas for properly rendering documents
- func (repo *Repository) ComposeDocumentMetas() map[string]string {
- if len(repo.DocumentRenderingMetas) == 0 {
- metas := map[string]string{}
- for k, v := range repo.ComposeMetas() {
- metas[k] = v
- }
- metas["mode"] = "document"
- repo.DocumentRenderingMetas = metas
- }
- return repo.DocumentRenderingMetas
- }
-
- // GetBaseRepo populates repo.BaseRepo for a fork repository and
- // returns an error on failure (NOTE: no error is returned for
- // non-fork repositories, and BaseRepo will be left untouched)
- func (repo *Repository) GetBaseRepo(ctx context.Context) (err error) {
- if !repo.IsFork {
- return nil
- }
-
- repo.BaseRepo, err = GetRepositoryByID(ctx, repo.ForkID)
- return err
- }
-
- // IsGenerated returns whether _this_ repository was generated from a template
- func (repo *Repository) IsGenerated() bool {
- return repo.TemplateID != 0
- }
-
- // RepoPath returns repository path by given user and repository name.
- func RepoPath(userName, repoName string) string { //revive:disable-line:exported
- return filepath.Join(user_model.UserPath(userName), strings.ToLower(repoName)+".git")
- }
-
- // RepoPath returns the repository path
- func (repo *Repository) RepoPath() string {
- return RepoPath(repo.OwnerName, repo.Name)
- }
-
- // Link returns the repository relative url
- func (repo *Repository) Link() string {
- return setting.AppSubURL + "/" + url.PathEscape(repo.OwnerName) + "/" + url.PathEscape(repo.Name)
- }
-
- // ComposeCompareURL returns the repository comparison URL
- func (repo *Repository) ComposeCompareURL(oldCommitID, newCommitID string) string {
- return fmt.Sprintf("%s/%s/compare/%s...%s", url.PathEscape(repo.OwnerName), url.PathEscape(repo.Name), util.PathEscapeSegments(oldCommitID), util.PathEscapeSegments(newCommitID))
- }
-
- func (repo *Repository) ComposeBranchCompareURL(baseRepo *Repository, branchName string) string {
- if baseRepo == nil {
- baseRepo = repo
- }
- var cmpBranchEscaped string
- if repo.ID != baseRepo.ID {
- cmpBranchEscaped = fmt.Sprintf("%s/%s:", url.PathEscape(repo.OwnerName), url.PathEscape(repo.Name))
- }
- cmpBranchEscaped = fmt.Sprintf("%s%s", cmpBranchEscaped, util.PathEscapeSegments(branchName))
- return fmt.Sprintf("%s/compare/%s...%s", baseRepo.Link(), util.PathEscapeSegments(baseRepo.DefaultBranch), cmpBranchEscaped)
- }
-
- // IsOwnedBy returns true when user owns this repository
- func (repo *Repository) IsOwnedBy(userID int64) bool {
- return repo.OwnerID == userID
- }
-
- // CanCreateBranch returns true if repository meets the requirements for creating new branches.
- func (repo *Repository) CanCreateBranch() bool {
- return !repo.IsMirror
- }
-
- // CanEnablePulls returns true if repository meets the requirements of accepting pulls.
- func (repo *Repository) CanEnablePulls() bool {
- return !repo.IsMirror && !repo.IsEmpty
- }
-
- // AllowsPulls returns true if repository meets the requirements of accepting pulls and has them enabled.
- func (repo *Repository) AllowsPulls() bool {
- return repo.CanEnablePulls() && repo.UnitEnabled(db.DefaultContext, unit.TypePullRequests)
- }
-
- // CanEnableEditor returns true if repository meets the requirements of web editor.
- func (repo *Repository) CanEnableEditor() bool {
- return !repo.IsMirror
- }
-
- // DescriptionHTML does special handles to description and return HTML string.
- func (repo *Repository) DescriptionHTML(ctx context.Context) template.HTML {
- desc, err := markup.RenderDescriptionHTML(&markup.RenderContext{
- Ctx: ctx,
- // Don't use Metas to speedup requests
- }, repo.Description)
- if err != nil {
- log.Error("Failed to render description for %s (ID: %d): %v", repo.Name, repo.ID, err)
- return template.HTML(markup.Sanitize(repo.Description))
- }
- return template.HTML(markup.Sanitize(desc))
- }
-
- // CloneLink represents different types of clone URLs of repository.
- type CloneLink struct {
- SSH string
- HTTPS string
- }
-
- // ComposeHTTPSCloneURL returns HTTPS clone URL based on given owner and repository name.
- func ComposeHTTPSCloneURL(owner, repo string) string {
- return fmt.Sprintf("%s%s/%s.git", setting.AppURL, url.PathEscape(owner), url.PathEscape(repo))
- }
-
- func ComposeSSHCloneURL(ownerName, repoName string) string {
- sshUser := setting.SSH.User
- sshDomain := setting.SSH.Domain
-
- // non-standard port, it must use full URI
- if setting.SSH.Port != 22 {
- sshHost := net.JoinHostPort(sshDomain, strconv.Itoa(setting.SSH.Port))
- return fmt.Sprintf("ssh://%s@%s/%s/%s.git", sshUser, sshHost, url.PathEscape(ownerName), url.PathEscape(repoName))
- }
-
- // for standard port, it can use a shorter URI (without the port)
- sshHost := sshDomain
- if ip := net.ParseIP(sshHost); ip != nil && ip.To4() == nil {
- sshHost = "[" + sshHost + "]" // for IPv6 address, wrap it with brackets
- }
- if setting.Repository.UseCompatSSHURI {
- return fmt.Sprintf("ssh://%s@%s/%s/%s.git", sshUser, sshHost, url.PathEscape(ownerName), url.PathEscape(repoName))
- }
- return fmt.Sprintf("%s@%s:%s/%s.git", sshUser, sshHost, url.PathEscape(ownerName), url.PathEscape(repoName))
- }
-
- func (repo *Repository) cloneLink(isWiki bool) *CloneLink {
- repoName := repo.Name
- if isWiki {
- repoName += ".wiki"
- }
-
- cl := new(CloneLink)
- cl.SSH = ComposeSSHCloneURL(repo.OwnerName, repoName)
- cl.HTTPS = ComposeHTTPSCloneURL(repo.OwnerName, repoName)
- return cl
- }
-
- // CloneLink returns clone URLs of repository.
- func (repo *Repository) CloneLink() (cl *CloneLink) {
- return repo.cloneLink(false)
- }
-
- // GetOriginalURLHostname returns the hostname of a URL or the URL
- func (repo *Repository) GetOriginalURLHostname() string {
- u, err := url.Parse(repo.OriginalURL)
- if err != nil {
- return repo.OriginalURL
- }
-
- return u.Host
- }
-
- // GetTrustModel will get the TrustModel for the repo or the default trust model
- func (repo *Repository) GetTrustModel() TrustModelType {
- trustModel := repo.TrustModel
- if trustModel == DefaultTrustModel {
- trustModel = ToTrustModel(setting.Repository.Signing.DefaultTrustModel)
- if trustModel == DefaultTrustModel {
- return CollaboratorTrustModel
- }
- }
- return trustModel
- }
-
- // MustNotBeArchived returns ErrRepoIsArchived if the repo is archived
- func (repo *Repository) MustNotBeArchived() error {
- if repo.IsArchived {
- return ErrRepoIsArchived{Repo: repo}
- }
- return nil
- }
-
- // __________ .__ __
- // \______ \ ____ ______ ____ _____|__|/ |_ ___________ ___.__.
- // | _// __ \\____ \ / _ \/ ___/ \ __\/ _ \_ __ < | |
- // | | \ ___/| |_> > <_> )___ \| || | ( <_> ) | \/\___ |
- // |____|_ /\___ > __/ \____/____ >__||__| \____/|__| / ____|
- // \/ \/|__| \/ \/
-
- // ErrRepoNotExist represents a "RepoNotExist" kind of error.
- type ErrRepoNotExist struct {
- ID int64
- UID int64
- OwnerName string
- Name string
- }
-
- // IsErrRepoNotExist checks if an error is a ErrRepoNotExist.
- func IsErrRepoNotExist(err error) bool {
- _, ok := err.(ErrRepoNotExist)
- return ok
- }
-
- func (err ErrRepoNotExist) Error() string {
- return fmt.Sprintf("repository does not exist [id: %d, uid: %d, owner_name: %s, name: %s]",
- err.ID, err.UID, err.OwnerName, err.Name)
- }
-
- // Unwrap unwraps this error as a ErrNotExist error
- func (err ErrRepoNotExist) Unwrap() error {
- return util.ErrNotExist
- }
-
- // GetRepositoryByOwnerAndName returns the repository by given owner name and repo name
- func GetRepositoryByOwnerAndName(ctx context.Context, ownerName, repoName string) (*Repository, error) {
- var repo Repository
- has, err := db.GetEngine(ctx).Table("repository").Select("repository.*").
- Join("INNER", "`user`", "`user`.id = repository.owner_id").
- Where("repository.lower_name = ?", strings.ToLower(repoName)).
- And("`user`.lower_name = ?", strings.ToLower(ownerName)).
- Get(&repo)
- if err != nil {
- return nil, err
- } else if !has {
- return nil, ErrRepoNotExist{0, 0, ownerName, repoName}
- }
- return &repo, nil
- }
-
- // GetRepositoryByName returns the repository by given name under user if exists.
- func GetRepositoryByName(ownerID int64, name string) (*Repository, error) {
- repo := &Repository{
- OwnerID: ownerID,
- LowerName: strings.ToLower(name),
- }
- has, err := db.GetEngine(db.DefaultContext).Get(repo)
- if err != nil {
- return nil, err
- } else if !has {
- return nil, ErrRepoNotExist{0, ownerID, "", name}
- }
- return repo, err
- }
-
- // getRepositoryURLPathSegments returns segments (owner, reponame) extracted from a url
- func getRepositoryURLPathSegments(repoURL string) []string {
- if strings.HasPrefix(repoURL, setting.AppURL) {
- return strings.Split(strings.TrimPrefix(repoURL, setting.AppURL), "/")
- }
-
- sshURLVariants := [4]string{
- setting.SSH.Domain + ":",
- setting.SSH.User + "@" + setting.SSH.Domain + ":",
- "git+ssh://" + setting.SSH.Domain + "/",
- "git+ssh://" + setting.SSH.User + "@" + setting.SSH.Domain + "/",
- }
-
- for _, sshURL := range sshURLVariants {
- if strings.HasPrefix(repoURL, sshURL) {
- return strings.Split(strings.TrimPrefix(repoURL, sshURL), "/")
- }
- }
-
- return nil
- }
-
- // GetRepositoryByURL returns the repository by given url
- func GetRepositoryByURL(ctx context.Context, repoURL string) (*Repository, error) {
- // possible urls for git:
- // https://my.domain/sub-path/<owner>/<repo>.git
- // https://my.domain/sub-path/<owner>/<repo>
- // git+ssh://user@my.domain/<owner>/<repo>.git
- // git+ssh://user@my.domain/<owner>/<repo>
- // user@my.domain:<owner>/<repo>.git
- // user@my.domain:<owner>/<repo>
-
- pathSegments := getRepositoryURLPathSegments(repoURL)
-
- if len(pathSegments) != 2 {
- return nil, fmt.Errorf("unknown or malformed repository URL")
- }
-
- ownerName := pathSegments[0]
- repoName := strings.TrimSuffix(pathSegments[1], ".git")
- return GetRepositoryByOwnerAndName(ctx, ownerName, repoName)
- }
-
- // GetRepositoryByID returns the repository by given id if exists.
- func GetRepositoryByID(ctx context.Context, id int64) (*Repository, error) {
- repo := new(Repository)
- has, err := db.GetEngine(ctx).ID(id).Get(repo)
- if err != nil {
- return nil, err
- } else if !has {
- return nil, ErrRepoNotExist{id, 0, "", ""}
- }
- return repo, nil
- }
-
- // GetRepositoriesMapByIDs returns the repositories by given id slice.
- func GetRepositoriesMapByIDs(ids []int64) (map[int64]*Repository, error) {
- repos := make(map[int64]*Repository, len(ids))
- return repos, db.GetEngine(db.DefaultContext).In("id", ids).Find(&repos)
- }
-
- // IsRepositoryModelOrDirExist returns true if the repository with given name under user has already existed.
- func IsRepositoryModelOrDirExist(ctx context.Context, u *user_model.User, repoName string) (bool, error) {
- has, err := IsRepositoryModelExist(ctx, u, repoName)
- if err != nil {
- return false, err
- }
- isDir, err := util.IsDir(RepoPath(u.Name, repoName))
- return has || isDir, err
- }
-
- func IsRepositoryModelExist(ctx context.Context, u *user_model.User, repoName string) (bool, error) {
- return db.GetEngine(ctx).Get(&Repository{
- OwnerID: u.ID,
- LowerName: strings.ToLower(repoName),
- })
- }
-
- // GetTemplateRepo populates repo.TemplateRepo for a generated repository and
- // returns an error on failure (NOTE: no error is returned for
- // non-generated repositories, and TemplateRepo will be left untouched)
- func GetTemplateRepo(ctx context.Context, repo *Repository) (*Repository, error) {
- if !repo.IsGenerated() {
- return nil, nil
- }
-
- return GetRepositoryByID(ctx, repo.TemplateID)
- }
-
- // TemplateRepo returns the repository, which is template of this repository
- func (repo *Repository) TemplateRepo() *Repository {
- repo, err := GetTemplateRepo(db.DefaultContext, repo)
- if err != nil {
- log.Error("TemplateRepo: %v", err)
- return nil
- }
- return repo
- }
-
- type CountRepositoryOptions struct {
- OwnerID int64
- Private util.OptionalBool
- }
-
- // CountRepositories returns number of repositories.
- // Argument private only takes effect when it is false,
- // set it true to count all repositories.
- func CountRepositories(ctx context.Context, opts CountRepositoryOptions) (int64, error) {
- sess := db.GetEngine(ctx).Where("id > 0")
-
- if opts.OwnerID > 0 {
- sess.And("owner_id = ?", opts.OwnerID)
- }
- if !opts.Private.IsNone() {
- sess.And("is_private=?", opts.Private.IsTrue())
- }
-
- count, err := sess.Count(new(Repository))
- if err != nil {
- return 0, fmt.Errorf("countRepositories: %w", err)
- }
- return count, nil
- }
-
- // UpdateRepoIssueNumbers updates one of a repositories amount of (open|closed) (issues|PRs) with the current count
- func UpdateRepoIssueNumbers(ctx context.Context, repoID int64, isPull, isClosed bool) error {
- field := "num_"
- if isClosed {
- field += "closed_"
- }
- if isPull {
- field += "pulls"
- } else {
- field += "issues"
- }
-
- subQuery := builder.Select("count(*)").
- From("issue").Where(builder.Eq{
- "repo_id": repoID,
- "is_pull": isPull,
- }.And(builder.If(isClosed, builder.Eq{"is_closed": isClosed})))
-
- // builder.Update(cond) will generate SQL like UPDATE ... SET cond
- query := builder.Update(builder.Eq{field: subQuery}).
- From("repository").
- Where(builder.Eq{"id": repoID})
- _, err := db.Exec(ctx, query)
- return err
- }
-
- // CountNullArchivedRepository counts the number of repositories with is_archived is null
- func CountNullArchivedRepository(ctx context.Context) (int64, error) {
- return db.GetEngine(ctx).Where(builder.IsNull{"is_archived"}).Count(new(Repository))
- }
-
- // FixNullArchivedRepository sets is_archived to false where it is null
- func FixNullArchivedRepository(ctx context.Context) (int64, error) {
- return db.GetEngine(ctx).Where(builder.IsNull{"is_archived"}).Cols("is_archived").NoAutoTime().Update(&Repository{
- IsArchived: false,
- })
- }
-
- // UpdateRepositoryOwnerName updates the owner name of all repositories owned by the user
- func UpdateRepositoryOwnerName(ctx context.Context, oldUserName, newUserName string) error {
- if _, err := db.GetEngine(ctx).Exec("UPDATE `repository` SET owner_name=? WHERE owner_name=?", newUserName, oldUserName); err != nil {
- return fmt.Errorf("change repo owner name: %w", err)
- }
- return nil
- }
|