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