2021-12-10 09:27:50 +08:00
// Copyright 2021 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
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/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"
)
2022-06-13 17:37:59 +08:00
// ErrUserDoesNotHaveAccessToRepo represets 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 )
}
2021-12-12 23:48:20 +08:00
var (
2022-03-30 16:42:47 +08:00
reservedRepoNames = [ ] string { "." , ".." , "-" }
2021-12-12 23:48:20 +08:00
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 )
}
2021-12-10 09:27:50 +08:00
// 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:"-" `
IsPrivate bool ` xorm:"INDEX" `
IsEmpty bool ` xorm:"INDEX" `
IsArchived bool ` xorm:"INDEX" `
IsMirror bool ` xorm:"INDEX" `
* Mirror ` xorm:"-" `
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" `
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" `
}
func init ( ) {
db . RegisterModel ( new ( Repository ) )
}
// SanitizedOriginalURL returns a sanitized OriginalURL
func ( repo * Repository ) SanitizedOriginalURL ( ) string {
if repo . OriginalURL == "" {
return ""
}
u , err := url . Parse ( repo . OriginalURL )
if err != nil {
return ""
}
u . User = nil
return u . String ( )
}
// ColorFormat returns a colored string to represent this repo
func ( repo * Repository ) ColorFormat ( s fmt . State ) {
2021-12-17 03:01:14 +08:00
if repo == nil {
log . ColorFprintf ( s , "%d:%s/%s" ,
log . NewColoredIDValue ( 0 ) ,
"<nil>" ,
"<nil>" )
return
}
2021-12-10 09:27:50 +08:00
log . ColorFprintf ( s , "%d:%s/%s" ,
log . NewColoredIDValue ( 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
}
// AfterLoad is invoked from XORM after setting the values of all fields of this object.
func ( repo * Repository ) AfterLoad ( ) {
// FIXME: use models migration to solve all at once.
if len ( repo . DefaultBranch ) == 0 {
repo . DefaultBranch = setting . Repository . DefaultBranch
}
repo . NumOpenIssues = repo . NumIssues - repo . NumClosedIssues
repo . NumOpenPulls = repo . NumPulls - repo . NumClosedPulls
repo . NumOpenMilestones = repo . NumMilestones - repo . NumClosedMilestones
repo . NumOpenProjects = repo . NumProjects - repo . NumClosedProjects
}
// 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 ( ) * user_model . User {
return repo . mustOwner ( db . DefaultContext )
}
2022-01-25 14:33:40 +08:00
// LoadAttributes loads attributes of the repository.
func ( repo * Repository ) LoadAttributes ( ctx context . Context ) error {
// Load owner
if err := repo . GetOwner ( 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
}
2021-12-10 09:27:50 +08:00
// 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 . HTMLURL ( ) + "/commit/" + url . PathEscape ( commitID )
}
2022-06-20 18:02:49 +08:00
return result
2021-12-10 09:27:50 +08:00
}
// 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
}
2022-05-20 22:08:52 +08:00
repo . Units , err = getUnitsByRepoID ( ctx , repo . ID )
2022-02-27 23:49:22 +08:00
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 , ", " ) )
}
2021-12-10 09:27:50 +08:00
return err
}
// UnitEnabled if this repository has the given unit enabled
2022-05-04 03:46:28 +08:00
func ( repo * Repository ) UnitEnabled ( tp unit . Type ) ( result bool ) {
2022-06-20 20:38:58 +08:00
return repo . UnitEnabledCtx ( db . DefaultContext , tp )
2022-05-04 03:46:28 +08:00
}
// UnitEnabled if this repository has the given unit enabled
func ( repo * Repository ) UnitEnabledCtx ( ctx context . Context , tp unit . Type ) bool {
if err := repo . LoadUnits ( ctx ) ; err != nil {
2021-12-10 09:27:50 +08:00
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 ( tp unit . Type ) * RepoUnit {
ru , err := repo . GetUnit ( 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 ) ,
}
}
return & RepoUnit {
Type : tp ,
Config : new ( UnitConfig ) ,
}
}
// GetUnit returns a RepoUnit object
func ( repo * Repository ) GetUnit ( tp unit . Type ) ( * RepoUnit , error ) {
2021-12-14 06:59:39 +08:00
return repo . GetUnitCtx ( db . DefaultContext , tp )
2021-12-10 09:27:50 +08:00
}
2021-12-14 06:59:39 +08:00
// GetUnitCtx returns a RepoUnit object
func ( repo * Repository ) GetUnitCtx ( ctx context . Context , tp unit . Type ) ( * RepoUnit , error ) {
2021-12-10 09:27:50 +08:00
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 }
}
// GetOwner returns the repository owner
func ( repo * Repository ) GetOwner ( ctx context . Context ) ( err error ) {
if repo . Owner != nil {
return nil
}
2022-05-20 22:08:52 +08:00
repo . Owner , err = user_model . GetUserByIDCtx ( ctx , repo . OwnerID )
2021-12-10 09:27:50 +08:00
return err
}
func ( repo * Repository ) mustOwner ( ctx context . Context ) * user_model . User {
if err := repo . GetOwner ( 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 ( unit . TypeExternalTracker )
if err == nil {
metas [ "format" ] = unit . ExternalTrackerConfig ( ) . ExternalTrackerFormat
switch unit . ExternalTrackerConfig ( ) . ExternalTrackerStyle {
case markup . IssueNameStyleAlphanumeric :
metas [ "style" ] = markup . IssueNameStyleAlphanumeric
2022-06-10 13:39:53 +08:00
case markup . IssueNameStyleRegexp :
metas [ "style" ] = markup . IssueNameStyleRegexp
metas [ "regexp" ] = unit . ExternalTrackerConfig ( ) . ExternalTrackerRegexpPattern
2021-12-10 09:27:50 +08:00
default :
metas [ "style" ] = markup . IssueNameStyleNumeric
}
}
repo . MustOwner ( )
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 ( ) ( err error ) {
2022-05-20 22:08:52 +08:00
return repo . getBaseRepo ( db . DefaultContext )
2021-12-10 09:27:50 +08:00
}
2022-05-20 22:08:52 +08:00
func ( repo * Repository ) getBaseRepo ( ctx context . Context ) ( err error ) {
2021-12-10 09:27:50 +08:00
if ! repo . IsFork {
return nil
}
2022-05-20 22:08:52 +08:00
repo . BaseRepo , err = GetRepositoryByIDCtx ( ctx , repo . ForkID )
2021-12-10 09:27:50 +08:00
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 link
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 ) )
}
// 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 ( 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.
2022-01-20 07:26:57 +08:00
func ( repo * Repository ) DescriptionHTML ( ctx context . Context ) template . HTML {
2021-12-10 09:27:50 +08:00
desc , err := markup . RenderDescriptionHTML ( & markup . RenderContext {
2022-01-20 07:26:57 +08:00
Ctx : ctx ,
2021-12-10 09:27:50 +08:00
URLPrefix : repo . HTMLURL ( ) ,
2022-02-28 01:51:34 +08:00
// Don't use Metas to speedup requests
2021-12-10 09:27:50 +08:00
} , 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 ) )
}
2022-06-20 18:02:49 +08:00
return template . HTML ( markup . Sanitize ( desc ) )
2021-12-10 09:27:50 +08:00
}
// 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 ( repo * Repository ) cloneLink ( isWiki bool ) * CloneLink {
repoName := repo . Name
if isWiki {
repoName += ".wiki"
}
2022-02-08 05:56:45 +08:00
sshUser := setting . SSH . User
2021-12-10 09:27:50 +08:00
cl := new ( CloneLink )
// if we have a ipv6 literal we need to put brackets around it
// for the git cloning to work.
sshDomain := setting . SSH . Domain
ip := net . ParseIP ( setting . SSH . Domain )
if ip != nil && ip . To4 ( ) == nil {
sshDomain = "[" + setting . SSH . Domain + "]"
}
if setting . SSH . Port != 22 {
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 ) )
} else if setting . Repository . UseCompatSSHURI {
cl . SSH = fmt . Sprintf ( "ssh://%s@%s/%s/%s.git" , sshUser , sshDomain , url . PathEscape ( repo . OwnerName ) , url . PathEscape ( repoName ) )
} else {
cl . SSH = fmt . Sprintf ( "%s@%s:%s/%s.git" , sshUser , sshDomain , url . PathEscape ( repo . OwnerName ) , url . PathEscape ( 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
}
// GetRepositoryByOwnerAndName returns the repository by given ownername and reponame.
func GetRepositoryByOwnerAndName ( ownerName , repoName string ) ( * Repository , error ) {
return GetRepositoryByOwnerAndNameCtx ( db . DefaultContext , ownerName , repoName )
}
// __________ .__ __
// \______ \ ____ ______ ____ _____|__|/ |_ ___________ ___.__.
// | _// __ \\____ \ / _ \/ ___/ \ __\/ _ \_ __ < | |
// | | \ ___/| |_> > <_> )___ \| || | ( <_> ) | \/\___ |
// |____|_ /\___ > __/ \____/____ >__||__| \____/|__| / ____|
// \/ \/|__| \/ \/
// 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 )
}
// GetRepositoryByOwnerAndNameCtx returns the repository by given owner name and repo name
func GetRepositoryByOwnerAndNameCtx ( 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
}
2022-05-20 22:08:52 +08:00
// GetRepositoryByIDCtx returns the repository by given id if exists.
func GetRepositoryByIDCtx ( ctx context . Context , id int64 ) ( * Repository , error ) {
2021-12-10 09:27:50 +08:00
repo := new ( Repository )
2022-05-20 22:08:52 +08:00
has , err := db . GetEngine ( ctx ) . ID ( id ) . Get ( repo )
2021-12-10 09:27:50 +08:00
if err != nil {
return nil , err
} else if ! has {
return nil , ErrRepoNotExist { id , 0 , "" , "" }
}
return repo , nil
}
// GetRepositoryByID returns the repository by given id if exists.
func GetRepositoryByID ( id int64 ) ( * Repository , error ) {
2022-05-20 22:08:52 +08:00
return GetRepositoryByIDCtx ( db . DefaultContext , id )
2021-12-10 09:27:50 +08:00
}
// 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 )
}
2022-05-20 22:08:52 +08:00
// IsRepositoryExist returns true if the repository with given name under user has already existed.
func IsRepositoryExist ( ctx context . Context , u * user_model . User , repoName string ) ( bool , error ) {
2021-12-10 09:27:50 +08:00
has , err := db . GetEngine ( ctx ) . Get ( & Repository {
OwnerID : u . ID ,
LowerName : strings . ToLower ( repoName ) ,
} )
if err != nil {
return false , err
}
isDir , err := util . IsDir ( RepoPath ( u . Name , repoName ) )
return has && isDir , err
}
// 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)
2022-05-20 22:08:52 +08:00
func GetTemplateRepo ( ctx context . Context , repo * Repository ) ( * Repository , error ) {
2021-12-10 09:27:50 +08:00
if ! repo . IsGenerated ( ) {
return nil , nil
}
2022-05-20 22:08:52 +08:00
return GetRepositoryByIDCtx ( ctx , repo . TemplateID )
2021-12-10 09:27:50 +08:00
}
2021-12-16 15:12:50 +08:00
// TemplateRepo returns the repository, which is template of this repository
func ( repo * Repository ) TemplateRepo ( ) * Repository {
2022-05-20 22:08:52 +08:00
repo , err := GetTemplateRepo ( db . DefaultContext , repo )
2021-12-16 15:12:50 +08:00
if err != nil {
log . Error ( "TemplateRepo: %v" , err )
return nil
}
return repo
}
2022-05-20 22:08:52 +08:00
type CountRepositoryOptions struct {
OwnerID int64
Private util . OptionalBool
}
2021-12-10 09:27:50 +08:00
2022-05-20 22:08:52 +08:00
// 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 )
2021-12-10 09:27:50 +08:00
}
2022-05-20 22:08:52 +08:00
if ! opts . Private . IsNone ( ) {
sess . And ( "is_private=?" , opts . Private . IsTrue ( ) )
2021-12-10 09:27:50 +08:00
}
count , err := sess . Count ( new ( Repository ) )
if err != nil {
2022-05-20 22:08:52 +08:00
return 0 , fmt . Errorf ( "countRepositories: %v" , err )
2021-12-10 09:27:50 +08:00
}
2022-05-20 22:08:52 +08:00
return count , nil
2021-12-10 09:27:50 +08:00
}
2022-06-13 17:37:59 +08:00
// StatsCorrectNumClosed update repository's issue related numbers
func StatsCorrectNumClosed ( ctx context . Context , id int64 , isPull bool , field string ) error {
_ , err := db . Exec ( ctx , "UPDATE `repository` SET " + field + "=(SELECT COUNT(*) FROM `issue` WHERE repo_id=? AND is_closed=? AND is_pull=?) WHERE id=?" , id , true , isPull , id )
return err
}
// UpdateRepoIssueNumbers update repository issue numbers
func UpdateRepoIssueNumbers ( ctx context . Context , repoID int64 , isPull , isClosed bool ) error {
e := db . GetEngine ( ctx )
if isPull {
if _ , err := e . ID ( repoID ) . Decr ( "num_pulls" ) . Update ( new ( Repository ) ) ; err != nil {
return err
}
if isClosed {
if _ , err := e . ID ( repoID ) . Decr ( "num_closed_pulls" ) . Update ( new ( Repository ) ) ; err != nil {
return err
}
}
} else {
if _ , err := e . ID ( repoID ) . Decr ( "num_issues" ) . Update ( new ( Repository ) ) ; err != nil {
return err
}
if isClosed {
if _ , err := e . ID ( repoID ) . Decr ( "num_closed_issues" ) . Update ( new ( Repository ) ) ; err != nil {
return err
}
}
}
return nil
}