1// Copyright 2021 The Gitea Authors. All rights reserved.
2// Use of this source code is governed by a MIT-style
3// license that can be found in the LICENSE file.
4
5package repo
6
7import (
8	"context"
9	"fmt"
10	"html/template"
11	"net"
12	"net/url"
13	"path/filepath"
14	"strconv"
15	"strings"
16
17	"code.gitea.io/gitea/models/db"
18	"code.gitea.io/gitea/models/unit"
19	user_model "code.gitea.io/gitea/models/user"
20	"code.gitea.io/gitea/modules/log"
21	"code.gitea.io/gitea/modules/markup"
22	"code.gitea.io/gitea/modules/setting"
23	api "code.gitea.io/gitea/modules/structs"
24	"code.gitea.io/gitea/modules/timeutil"
25	"code.gitea.io/gitea/modules/util"
26)
27
28var (
29	reservedRepoNames    = []string{".", ".."}
30	reservedRepoPatterns = []string{"*.git", "*.wiki", "*.rss", "*.atom"}
31)
32
33// IsUsableRepoName returns true when repository is usable
34func IsUsableRepoName(name string) error {
35	if db.AlphaDashDotPattern.MatchString(name) {
36		// Note: usually this error is normally caught up earlier in the UI
37		return db.ErrNameCharsNotAllowed{Name: name}
38	}
39	return db.IsUsableName(reservedRepoNames, reservedRepoPatterns, name)
40}
41
42// TrustModelType defines the types of trust model for this repository
43type TrustModelType int
44
45// kinds of TrustModel
46const (
47	DefaultTrustModel TrustModelType = iota // default trust model
48	CommitterTrustModel
49	CollaboratorTrustModel
50	CollaboratorCommitterTrustModel
51)
52
53// String converts a TrustModelType to a string
54func (t TrustModelType) String() string {
55	switch t {
56	case DefaultTrustModel:
57		return "default"
58	case CommitterTrustModel:
59		return "committer"
60	case CollaboratorTrustModel:
61		return "collaborator"
62	case CollaboratorCommitterTrustModel:
63		return "collaboratorcommitter"
64	}
65	return "default"
66}
67
68// ToTrustModel converts a string to a TrustModelType
69func ToTrustModel(model string) TrustModelType {
70	switch strings.ToLower(strings.TrimSpace(model)) {
71	case "default":
72		return DefaultTrustModel
73	case "collaborator":
74		return CollaboratorTrustModel
75	case "committer":
76		return CommitterTrustModel
77	case "collaboratorcommitter":
78		return CollaboratorCommitterTrustModel
79	}
80	return DefaultTrustModel
81}
82
83// RepositoryStatus defines the status of repository
84type RepositoryStatus int
85
86// all kinds of RepositoryStatus
87const (
88	RepositoryReady           RepositoryStatus = iota // a normal repository
89	RepositoryBeingMigrated                           // repository is migrating
90	RepositoryPendingTransfer                         // repository pending in ownership transfer state
91	RepositoryBroken                                  // repository is in a permanently broken state
92)
93
94// Repository represents a git repository.
95type Repository struct {
96	ID                  int64 `xorm:"pk autoincr"`
97	OwnerID             int64 `xorm:"UNIQUE(s) index"`
98	OwnerName           string
99	Owner               *user_model.User   `xorm:"-"`
100	LowerName           string             `xorm:"UNIQUE(s) INDEX NOT NULL"`
101	Name                string             `xorm:"INDEX NOT NULL"`
102	Description         string             `xorm:"TEXT"`
103	Website             string             `xorm:"VARCHAR(2048)"`
104	OriginalServiceType api.GitServiceType `xorm:"index"`
105	OriginalURL         string             `xorm:"VARCHAR(2048)"`
106	DefaultBranch       string
107
108	NumWatches          int
109	NumStars            int
110	NumForks            int
111	NumIssues           int
112	NumClosedIssues     int
113	NumOpenIssues       int `xorm:"-"`
114	NumPulls            int
115	NumClosedPulls      int
116	NumOpenPulls        int `xorm:"-"`
117	NumMilestones       int `xorm:"NOT NULL DEFAULT 0"`
118	NumClosedMilestones int `xorm:"NOT NULL DEFAULT 0"`
119	NumOpenMilestones   int `xorm:"-"`
120	NumProjects         int `xorm:"NOT NULL DEFAULT 0"`
121	NumClosedProjects   int `xorm:"NOT NULL DEFAULT 0"`
122	NumOpenProjects     int `xorm:"-"`
123
124	IsPrivate  bool `xorm:"INDEX"`
125	IsEmpty    bool `xorm:"INDEX"`
126	IsArchived bool `xorm:"INDEX"`
127	IsMirror   bool `xorm:"INDEX"`
128	*Mirror    `xorm:"-"`
129	Status     RepositoryStatus `xorm:"NOT NULL DEFAULT 0"`
130
131	RenderingMetas         map[string]string `xorm:"-"`
132	DocumentRenderingMetas map[string]string `xorm:"-"`
133	Units                  []*RepoUnit       `xorm:"-"`
134	PrimaryLanguage        *LanguageStat     `xorm:"-"`
135
136	IsFork                          bool               `xorm:"INDEX NOT NULL DEFAULT false"`
137	ForkID                          int64              `xorm:"INDEX"`
138	BaseRepo                        *Repository        `xorm:"-"`
139	IsTemplate                      bool               `xorm:"INDEX NOT NULL DEFAULT false"`
140	TemplateID                      int64              `xorm:"INDEX"`
141	Size                            int64              `xorm:"NOT NULL DEFAULT 0"`
142	CodeIndexerStatus               *RepoIndexerStatus `xorm:"-"`
143	StatsIndexerStatus              *RepoIndexerStatus `xorm:"-"`
144	IsFsckEnabled                   bool               `xorm:"NOT NULL DEFAULT true"`
145	CloseIssuesViaCommitInAnyBranch bool               `xorm:"NOT NULL DEFAULT false"`
146	Topics                          []string           `xorm:"TEXT JSON"`
147
148	TrustModel TrustModelType
149
150	// Avatar: ID(10-20)-md5(32) - must fit into 64 symbols
151	Avatar string `xorm:"VARCHAR(64)"`
152
153	CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
154	UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"`
155}
156
157func init() {
158	db.RegisterModel(new(Repository))
159}
160
161// SanitizedOriginalURL returns a sanitized OriginalURL
162func (repo *Repository) SanitizedOriginalURL() string {
163	if repo.OriginalURL == "" {
164		return ""
165	}
166	u, err := url.Parse(repo.OriginalURL)
167	if err != nil {
168		return ""
169	}
170	u.User = nil
171	return u.String()
172}
173
174// ColorFormat returns a colored string to represent this repo
175func (repo *Repository) ColorFormat(s fmt.State) {
176	if repo == nil {
177		log.ColorFprintf(s, "%d:%s/%s",
178			log.NewColoredIDValue(0),
179			"<nil>",
180			"<nil>")
181		return
182	}
183	log.ColorFprintf(s, "%d:%s/%s",
184		log.NewColoredIDValue(repo.ID),
185		repo.OwnerName,
186		repo.Name)
187}
188
189// IsBeingMigrated indicates that repository is being migrated
190func (repo *Repository) IsBeingMigrated() bool {
191	return repo.Status == RepositoryBeingMigrated
192}
193
194// IsBeingCreated indicates that repository is being migrated or forked
195func (repo *Repository) IsBeingCreated() bool {
196	return repo.IsBeingMigrated()
197}
198
199// IsBroken indicates that repository is broken
200func (repo *Repository) IsBroken() bool {
201	return repo.Status == RepositoryBroken
202}
203
204// AfterLoad is invoked from XORM after setting the values of all fields of this object.
205func (repo *Repository) AfterLoad() {
206	// FIXME: use models migration to solve all at once.
207	if len(repo.DefaultBranch) == 0 {
208		repo.DefaultBranch = setting.Repository.DefaultBranch
209	}
210
211	repo.NumOpenIssues = repo.NumIssues - repo.NumClosedIssues
212	repo.NumOpenPulls = repo.NumPulls - repo.NumClosedPulls
213	repo.NumOpenMilestones = repo.NumMilestones - repo.NumClosedMilestones
214	repo.NumOpenProjects = repo.NumProjects - repo.NumClosedProjects
215}
216
217// MustOwner always returns a valid *user_model.User object to avoid
218// conceptually impossible error handling.
219// It creates a fake object that contains error details
220// when error occurs.
221func (repo *Repository) MustOwner() *user_model.User {
222	return repo.mustOwner(db.DefaultContext)
223}
224
225// FullName returns the repository full name
226func (repo *Repository) FullName() string {
227	return repo.OwnerName + "/" + repo.Name
228}
229
230// HTMLURL returns the repository HTML URL
231func (repo *Repository) HTMLURL() string {
232	return setting.AppURL + url.PathEscape(repo.OwnerName) + "/" + url.PathEscape(repo.Name)
233}
234
235// CommitLink make link to by commit full ID
236// note: won't check whether it's an right id
237func (repo *Repository) CommitLink(commitID string) (result string) {
238	if commitID == "" || commitID == "0000000000000000000000000000000000000000" {
239		result = ""
240	} else {
241		result = repo.HTMLURL() + "/commit/" + url.PathEscape(commitID)
242	}
243	return
244}
245
246// APIURL returns the repository API URL
247func (repo *Repository) APIURL() string {
248	return setting.AppURL + "api/v1/repos/" + url.PathEscape(repo.OwnerName) + "/" + url.PathEscape(repo.Name)
249}
250
251// GetCommitsCountCacheKey returns cache key used for commits count caching.
252func (repo *Repository) GetCommitsCountCacheKey(contextName string, isRef bool) string {
253	var prefix string
254	if isRef {
255		prefix = "ref"
256	} else {
257		prefix = "commit"
258	}
259	return fmt.Sprintf("commits-count-%d-%s-%s", repo.ID, prefix, contextName)
260}
261
262// LoadUnits loads repo units into repo.Units
263func (repo *Repository) LoadUnits(ctx context.Context) (err error) {
264	if repo.Units != nil {
265		return nil
266	}
267
268	repo.Units, err = getUnitsByRepoID(db.GetEngine(ctx), repo.ID)
269	log.Trace("repo.Units: %-+v", repo.Units)
270	return err
271}
272
273// UnitEnabled if this repository has the given unit enabled
274func (repo *Repository) UnitEnabled(tp unit.Type) bool {
275	if err := repo.LoadUnits(db.DefaultContext); err != nil {
276		log.Warn("Error loading repository (ID: %d) units: %s", repo.ID, err.Error())
277	}
278	for _, unit := range repo.Units {
279		if unit.Type == tp {
280			return true
281		}
282	}
283	return false
284}
285
286// MustGetUnit always returns a RepoUnit object
287func (repo *Repository) MustGetUnit(tp unit.Type) *RepoUnit {
288	ru, err := repo.GetUnit(tp)
289	if err == nil {
290		return ru
291	}
292
293	if tp == unit.TypeExternalWiki {
294		return &RepoUnit{
295			Type:   tp,
296			Config: new(ExternalWikiConfig),
297		}
298	} else if tp == unit.TypeExternalTracker {
299		return &RepoUnit{
300			Type:   tp,
301			Config: new(ExternalTrackerConfig),
302		}
303	} else if tp == unit.TypePullRequests {
304		return &RepoUnit{
305			Type:   tp,
306			Config: new(PullRequestsConfig),
307		}
308	} else if tp == unit.TypeIssues {
309		return &RepoUnit{
310			Type:   tp,
311			Config: new(IssuesConfig),
312		}
313	}
314	return &RepoUnit{
315		Type:   tp,
316		Config: new(UnitConfig),
317	}
318}
319
320// GetUnit returns a RepoUnit object
321func (repo *Repository) GetUnit(tp unit.Type) (*RepoUnit, error) {
322	return repo.GetUnitCtx(db.DefaultContext, tp)
323}
324
325// GetUnitCtx returns a RepoUnit object
326func (repo *Repository) GetUnitCtx(ctx context.Context, tp unit.Type) (*RepoUnit, error) {
327	if err := repo.LoadUnits(ctx); err != nil {
328		return nil, err
329	}
330	for _, unit := range repo.Units {
331		if unit.Type == tp {
332			return unit, nil
333		}
334	}
335	return nil, ErrUnitTypeNotExist{tp}
336}
337
338// GetOwner returns the repository owner
339func (repo *Repository) GetOwner(ctx context.Context) (err error) {
340	if repo.Owner != nil {
341		return nil
342	}
343
344	repo.Owner, err = user_model.GetUserByIDEngine(db.GetEngine(ctx), repo.OwnerID)
345	return err
346}
347
348func (repo *Repository) mustOwner(ctx context.Context) *user_model.User {
349	if err := repo.GetOwner(ctx); err != nil {
350		return &user_model.User{
351			Name:     "error",
352			FullName: err.Error(),
353		}
354	}
355
356	return repo.Owner
357}
358
359// ComposeMetas composes a map of metas for properly rendering issue links and external issue trackers.
360func (repo *Repository) ComposeMetas() map[string]string {
361	if len(repo.RenderingMetas) == 0 {
362		metas := map[string]string{
363			"user":     repo.OwnerName,
364			"repo":     repo.Name,
365			"repoPath": repo.RepoPath(),
366			"mode":     "comment",
367		}
368
369		unit, err := repo.GetUnit(unit.TypeExternalTracker)
370		if err == nil {
371			metas["format"] = unit.ExternalTrackerConfig().ExternalTrackerFormat
372			switch unit.ExternalTrackerConfig().ExternalTrackerStyle {
373			case markup.IssueNameStyleAlphanumeric:
374				metas["style"] = markup.IssueNameStyleAlphanumeric
375			default:
376				metas["style"] = markup.IssueNameStyleNumeric
377			}
378		}
379
380		repo.MustOwner()
381		if repo.Owner.IsOrganization() {
382			teams := make([]string, 0, 5)
383			_ = db.GetEngine(db.DefaultContext).Table("team_repo").
384				Join("INNER", "team", "team.id = team_repo.team_id").
385				Where("team_repo.repo_id = ?", repo.ID).
386				Select("team.lower_name").
387				OrderBy("team.lower_name").
388				Find(&teams)
389			metas["teams"] = "," + strings.Join(teams, ",") + ","
390			metas["org"] = strings.ToLower(repo.OwnerName)
391		}
392
393		repo.RenderingMetas = metas
394	}
395	return repo.RenderingMetas
396}
397
398// ComposeDocumentMetas composes a map of metas for properly rendering documents
399func (repo *Repository) ComposeDocumentMetas() map[string]string {
400	if len(repo.DocumentRenderingMetas) == 0 {
401		metas := map[string]string{}
402		for k, v := range repo.ComposeMetas() {
403			metas[k] = v
404		}
405		metas["mode"] = "document"
406		repo.DocumentRenderingMetas = metas
407	}
408	return repo.DocumentRenderingMetas
409}
410
411// GetBaseRepo populates repo.BaseRepo for a fork repository and
412// returns an error on failure (NOTE: no error is returned for
413// non-fork repositories, and BaseRepo will be left untouched)
414func (repo *Repository) GetBaseRepo() (err error) {
415	return repo.getBaseRepo(db.GetEngine(db.DefaultContext))
416}
417
418func (repo *Repository) getBaseRepo(e db.Engine) (err error) {
419	if !repo.IsFork {
420		return nil
421	}
422
423	repo.BaseRepo, err = getRepositoryByID(e, repo.ForkID)
424	return err
425}
426
427// IsGenerated returns whether _this_ repository was generated from a template
428func (repo *Repository) IsGenerated() bool {
429	return repo.TemplateID != 0
430}
431
432// RepoPath returns repository path by given user and repository name.
433func RepoPath(userName, repoName string) string { //revive:disable-line:exported
434	return filepath.Join(user_model.UserPath(userName), strings.ToLower(repoName)+".git")
435}
436
437// RepoPath returns the repository path
438func (repo *Repository) RepoPath() string {
439	return RepoPath(repo.OwnerName, repo.Name)
440}
441
442// GitConfigPath returns the path to a repository's git config/ directory
443func GitConfigPath(repoPath string) string {
444	return filepath.Join(repoPath, "config")
445}
446
447// GitConfigPath returns the repository git config path
448func (repo *Repository) GitConfigPath() string {
449	return GitConfigPath(repo.RepoPath())
450}
451
452// Link returns the repository link
453func (repo *Repository) Link() string {
454	return setting.AppSubURL + "/" + url.PathEscape(repo.OwnerName) + "/" + url.PathEscape(repo.Name)
455}
456
457// ComposeCompareURL returns the repository comparison URL
458func (repo *Repository) ComposeCompareURL(oldCommitID, newCommitID string) string {
459	return fmt.Sprintf("%s/%s/compare/%s...%s", url.PathEscape(repo.OwnerName), url.PathEscape(repo.Name), util.PathEscapeSegments(oldCommitID), util.PathEscapeSegments(newCommitID))
460}
461
462// IsOwnedBy returns true when user owns this repository
463func (repo *Repository) IsOwnedBy(userID int64) bool {
464	return repo.OwnerID == userID
465}
466
467// CanCreateBranch returns true if repository meets the requirements for creating new branches.
468func (repo *Repository) CanCreateBranch() bool {
469	return !repo.IsMirror
470}
471
472// CanEnablePulls returns true if repository meets the requirements of accepting pulls.
473func (repo *Repository) CanEnablePulls() bool {
474	return !repo.IsMirror && !repo.IsEmpty
475}
476
477// AllowsPulls returns true if repository meets the requirements of accepting pulls and has them enabled.
478func (repo *Repository) AllowsPulls() bool {
479	return repo.CanEnablePulls() && repo.UnitEnabled(unit.TypePullRequests)
480}
481
482// CanEnableEditor returns true if repository meets the requirements of web editor.
483func (repo *Repository) CanEnableEditor() bool {
484	return !repo.IsMirror
485}
486
487// DescriptionHTML does special handles to description and return HTML string.
488func (repo *Repository) DescriptionHTML() template.HTML {
489	desc, err := markup.RenderDescriptionHTML(&markup.RenderContext{
490		URLPrefix: repo.HTMLURL(),
491		Metas:     repo.ComposeMetas(),
492	}, repo.Description)
493	if err != nil {
494		log.Error("Failed to render description for %s (ID: %d): %v", repo.Name, repo.ID, err)
495		return template.HTML(markup.Sanitize(repo.Description))
496	}
497	return template.HTML(markup.Sanitize(string(desc)))
498}
499
500// CloneLink represents different types of clone URLs of repository.
501type CloneLink struct {
502	SSH   string
503	HTTPS string
504	Git   string
505}
506
507// ComposeHTTPSCloneURL returns HTTPS clone URL based on given owner and repository name.
508func ComposeHTTPSCloneURL(owner, repo string) string {
509	return fmt.Sprintf("%s%s/%s.git", setting.AppURL, url.PathEscape(owner), url.PathEscape(repo))
510}
511
512func (repo *Repository) cloneLink(isWiki bool) *CloneLink {
513	repoName := repo.Name
514	if isWiki {
515		repoName += ".wiki"
516	}
517
518	sshUser := setting.RunUser
519	if setting.SSH.StartBuiltinServer {
520		sshUser = setting.SSH.BuiltinServerUser
521	}
522
523	cl := new(CloneLink)
524
525	// if we have a ipv6 literal we need to put brackets around it
526	// for the git cloning to work.
527	sshDomain := setting.SSH.Domain
528	ip := net.ParseIP(setting.SSH.Domain)
529	if ip != nil && ip.To4() == nil {
530		sshDomain = "[" + setting.SSH.Domain + "]"
531	}
532
533	if setting.SSH.Port != 22 {
534		cl.SSH = fmt.Sprintf("ssh://%s@%s/%s/%s.git", sshUser, net.JoinHostPort(setting.SSH.Domain, strconv.Itoa(setting.SSH.Port)), url.PathEscape(repo.OwnerName), url.PathEscape(repoName))
535	} else if setting.Repository.UseCompatSSHURI {
536		cl.SSH = fmt.Sprintf("ssh://%s@%s/%s/%s.git", sshUser, sshDomain, url.PathEscape(repo.OwnerName), url.PathEscape(repoName))
537	} else {
538		cl.SSH = fmt.Sprintf("%s@%s:%s/%s.git", sshUser, sshDomain, url.PathEscape(repo.OwnerName), url.PathEscape(repoName))
539	}
540	cl.HTTPS = ComposeHTTPSCloneURL(repo.OwnerName, repoName)
541	return cl
542}
543
544// CloneLink returns clone URLs of repository.
545func (repo *Repository) CloneLink() (cl *CloneLink) {
546	return repo.cloneLink(false)
547}
548
549// GetOriginalURLHostname returns the hostname of a URL or the URL
550func (repo *Repository) GetOriginalURLHostname() string {
551	u, err := url.Parse(repo.OriginalURL)
552	if err != nil {
553		return repo.OriginalURL
554	}
555
556	return u.Host
557}
558
559// GetTrustModel will get the TrustModel for the repo or the default trust model
560func (repo *Repository) GetTrustModel() TrustModelType {
561	trustModel := repo.TrustModel
562	if trustModel == DefaultTrustModel {
563		trustModel = ToTrustModel(setting.Repository.Signing.DefaultTrustModel)
564		if trustModel == DefaultTrustModel {
565			return CollaboratorTrustModel
566		}
567	}
568	return trustModel
569}
570
571// GetRepositoryByOwnerAndName returns the repository by given ownername and reponame.
572func GetRepositoryByOwnerAndName(ownerName, repoName string) (*Repository, error) {
573	return GetRepositoryByOwnerAndNameCtx(db.DefaultContext, ownerName, repoName)
574}
575
576// __________                           .__  __
577// \______   \ ____ ______   ____  _____|__|/  |_  ___________ ___.__.
578//  |       _// __ \\____ \ /  _ \/  ___/  \   __\/  _ \_  __ <   |  |
579//  |    |   \  ___/|  |_> >  <_> )___ \|  ||  | (  <_> )  | \/\___  |
580//  |____|_  /\___  >   __/ \____/____  >__||__|  \____/|__|   / ____|
581//         \/     \/|__|              \/                       \/
582
583// ErrRepoNotExist represents a "RepoNotExist" kind of error.
584type ErrRepoNotExist struct {
585	ID        int64
586	UID       int64
587	OwnerName string
588	Name      string
589}
590
591// IsErrRepoNotExist checks if an error is a ErrRepoNotExist.
592func IsErrRepoNotExist(err error) bool {
593	_, ok := err.(ErrRepoNotExist)
594	return ok
595}
596
597func (err ErrRepoNotExist) Error() string {
598	return fmt.Sprintf("repository does not exist [id: %d, uid: %d, owner_name: %s, name: %s]",
599		err.ID, err.UID, err.OwnerName, err.Name)
600}
601
602// GetRepositoryByOwnerAndNameCtx returns the repository by given owner name and repo name
603func GetRepositoryByOwnerAndNameCtx(ctx context.Context, ownerName, repoName string) (*Repository, error) {
604	var repo Repository
605	has, err := db.GetEngine(ctx).Table("repository").Select("repository.*").
606		Join("INNER", "`user`", "`user`.id = repository.owner_id").
607		Where("repository.lower_name = ?", strings.ToLower(repoName)).
608		And("`user`.lower_name = ?", strings.ToLower(ownerName)).
609		Get(&repo)
610	if err != nil {
611		return nil, err
612	} else if !has {
613		return nil, ErrRepoNotExist{0, 0, ownerName, repoName}
614	}
615	return &repo, nil
616}
617
618// GetRepositoryByName returns the repository by given name under user if exists.
619func GetRepositoryByName(ownerID int64, name string) (*Repository, error) {
620	repo := &Repository{
621		OwnerID:   ownerID,
622		LowerName: strings.ToLower(name),
623	}
624	has, err := db.GetEngine(db.DefaultContext).Get(repo)
625	if err != nil {
626		return nil, err
627	} else if !has {
628		return nil, ErrRepoNotExist{0, ownerID, "", name}
629	}
630	return repo, err
631}
632
633func getRepositoryByID(e db.Engine, id int64) (*Repository, error) {
634	repo := new(Repository)
635	has, err := e.ID(id).Get(repo)
636	if err != nil {
637		return nil, err
638	} else if !has {
639		return nil, ErrRepoNotExist{id, 0, "", ""}
640	}
641	return repo, nil
642}
643
644// GetRepositoryByID returns the repository by given id if exists.
645func GetRepositoryByID(id int64) (*Repository, error) {
646	return getRepositoryByID(db.GetEngine(db.DefaultContext), id)
647}
648
649// GetRepositoryByIDCtx returns the repository by given id if exists.
650func GetRepositoryByIDCtx(ctx context.Context, id int64) (*Repository, error) {
651	return getRepositoryByID(db.GetEngine(ctx), id)
652}
653
654// GetRepositoriesMapByIDs returns the repositories by given id slice.
655func GetRepositoriesMapByIDs(ids []int64) (map[int64]*Repository, error) {
656	repos := make(map[int64]*Repository, len(ids))
657	return repos, db.GetEngine(db.DefaultContext).In("id", ids).Find(&repos)
658}
659
660// IsRepositoryExistCtx returns true if the repository with given name under user has already existed.
661func IsRepositoryExistCtx(ctx context.Context, u *user_model.User, repoName string) (bool, error) {
662	has, err := db.GetEngine(ctx).Get(&Repository{
663		OwnerID:   u.ID,
664		LowerName: strings.ToLower(repoName),
665	})
666	if err != nil {
667		return false, err
668	}
669	isDir, err := util.IsDir(RepoPath(u.Name, repoName))
670	return has && isDir, err
671}
672
673// IsRepositoryExist returns true if the repository with given name under user has already existed.
674func IsRepositoryExist(u *user_model.User, repoName string) (bool, error) {
675	return IsRepositoryExistCtx(db.DefaultContext, u, repoName)
676}
677
678// GetTemplateRepo populates repo.TemplateRepo for a generated repository and
679// returns an error on failure (NOTE: no error is returned for
680// non-generated repositories, and TemplateRepo will be left untouched)
681func GetTemplateRepo(repo *Repository) (*Repository, error) {
682	return getTemplateRepo(db.GetEngine(db.DefaultContext), repo)
683}
684
685func getTemplateRepo(e db.Engine, repo *Repository) (*Repository, error) {
686	if !repo.IsGenerated() {
687		return nil, nil
688	}
689
690	return getRepositoryByID(e, repo.TemplateID)
691}
692
693// TemplateRepo returns the repository, which is template of this repository
694func (repo *Repository) TemplateRepo() *Repository {
695	repo, err := GetTemplateRepo(repo)
696	if err != nil {
697		log.Error("TemplateRepo: %v", err)
698		return nil
699	}
700	return repo
701}
702
703func countRepositories(userID int64, private bool) int64 {
704	sess := db.GetEngine(db.DefaultContext).Where("id > 0")
705
706	if userID > 0 {
707		sess.And("owner_id = ?", userID)
708	}
709	if !private {
710		sess.And("is_private=?", false)
711	}
712
713	count, err := sess.Count(new(Repository))
714	if err != nil {
715		log.Error("countRepositories: %v", err)
716	}
717	return count
718}
719
720// CountRepositories returns number of repositories.
721// Argument private only takes effect when it is false,
722// set it true to count all repositories.
723func CountRepositories(private bool) int64 {
724	return countRepositories(-1, private)
725}
726
727// CountUserRepositories returns number of repositories user owns.
728// Argument private only takes effect when it is false,
729// set it true to count all repositories.
730func CountUserRepositories(userID int64, private bool) int64 {
731	return countRepositories(userID, private)
732}
733
734func getRepositoryCount(e db.Engine, ownerID int64) (int64, error) {
735	return e.Count(&Repository{OwnerID: ownerID})
736}
737
738func getPublicRepositoryCount(e db.Engine, u *user_model.User) (int64, error) {
739	return e.Where("is_private = ?", false).Count(&Repository{OwnerID: u.ID})
740}
741
742func getPrivateRepositoryCount(e db.Engine, u *user_model.User) (int64, error) {
743	return e.Where("is_private = ?", true).Count(&Repository{OwnerID: u.ID})
744}
745
746// GetRepositoryCount returns the total number of repositories of user.
747func GetRepositoryCount(ctx context.Context, ownerID int64) (int64, error) {
748	return getRepositoryCount(db.GetEngine(ctx), ownerID)
749}
750
751// GetPublicRepositoryCount returns the total number of public repositories of user.
752func GetPublicRepositoryCount(u *user_model.User) (int64, error) {
753	return getPublicRepositoryCount(db.GetEngine(db.DefaultContext), u)
754}
755
756// GetPrivateRepositoryCount returns the total number of private repositories of user.
757func GetPrivateRepositoryCount(u *user_model.User) (int64, error) {
758	return getPrivateRepositoryCount(db.GetEngine(db.DefaultContext), u)
759}
760