2014-02-15 07:16:54 +08:00
// Copyright 2014 The Gogs Authors. All rights reserved.
2017-08-26 21:57:41 +08:00
// Copyright 2017 The Gitea Authors. All rights reserved.
2014-02-15 07:16:54 +08:00
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
2014-02-14 22:20:57 +08:00
package models
import (
2019-12-15 17:51:28 +08:00
"context"
2014-03-10 08:06:29 +08:00
"errors"
2014-03-11 08:48:58 +08:00
"fmt"
2014-07-23 01:52:37 +08:00
"html/template"
2020-07-27 04:31:28 +08:00
"net"
2019-01-31 05:04:19 +08:00
"net/url"
2014-02-14 22:20:57 +08:00
"os"
2014-03-30 10:18:36 +08:00
"path"
2014-02-14 22:20:57 +08:00
"path/filepath"
2014-06-03 11:17:21 +08:00
"sort"
2019-04-23 04:40:51 +08:00
"strconv"
2014-02-14 22:20:57 +08:00
"strings"
"time"
2021-09-22 13:38:34 +08:00
"unicode/utf8"
2014-02-14 22:20:57 +08:00
2021-11-17 20:34:35 +08:00
_ "image/jpeg" // Needed for jpeg support
2021-11-18 13:58:42 +08:00
admin_model "code.gitea.io/gitea/models/admin"
2021-09-19 19:49:59 +08:00
"code.gitea.io/gitea/models/db"
2021-11-19 21:39:57 +08:00
repo_model "code.gitea.io/gitea/models/repo"
2021-11-10 03:57:58 +08:00
"code.gitea.io/gitea/models/unit"
2021-11-10 13:13:16 +08:00
"code.gitea.io/gitea/models/webhook"
2021-04-09 06:25:57 +08:00
"code.gitea.io/gitea/modules/lfs"
2016-11-11 00:24:48 +08:00
"code.gitea.io/gitea/modules/log"
2017-09-17 01:17:57 +08:00
"code.gitea.io/gitea/modules/markup"
2016-12-23 02:12:23 +08:00
"code.gitea.io/gitea/modules/options"
2016-11-11 00:24:48 +08:00
"code.gitea.io/gitea/modules/setting"
2020-08-18 12:23:45 +08:00
"code.gitea.io/gitea/modules/storage"
2019-05-11 18:21:34 +08:00
api "code.gitea.io/gitea/modules/structs"
2019-08-15 22:46:21 +08:00
"code.gitea.io/gitea/modules/timeutil"
2019-11-11 05:33:47 +08:00
"code.gitea.io/gitea/modules/util"
2017-01-02 02:15:09 +08:00
2020-06-13 19:35:59 +08:00
"xorm.io/builder"
2014-02-14 22:20:57 +08:00
)
2014-03-11 13:32:36 +08:00
var (
2016-11-29 01:27:55 +08:00
// ErrMirrorNotExist mirror does not exist error
ErrMirrorNotExist = errors . New ( "Mirror does not exist" )
// ErrNameEmpty name is empty error
ErrNameEmpty = errors . New ( "Name is empty" )
2014-03-11 13:32:36 +08:00
)
2014-03-10 08:06:29 +08:00
var (
2016-11-29 01:27:55 +08:00
// Gitignores contains the gitiginore files
Gitignores [ ] string
// Licenses contains the license files
Licenses [ ] string
// Readmes contains the readme files
Readmes [ ] string
2019-12-07 10:13:19 +08:00
// LabelTemplates contains the label template files and the list of labels for each file
LabelTemplates map [ string ] string
2015-10-01 21:17:27 +08:00
2016-11-29 01:27:55 +08:00
// ItemsPerPage maximum items per page in forks, watchers and stars of a repo
2015-11-17 12:28:46 +08:00
ItemsPerPage = 40
2014-03-10 08:06:29 +08:00
)
2019-05-15 09:57:00 +08:00
// loadRepoConfig loads the repository config
func loadRepoConfig ( ) {
2015-08-28 16:44:04 +08:00
// Load .gitignore and license files and readme templates.
2016-08-30 10:02:49 +08:00
types := [ ] string { "gitignore" , "license" , "readme" , "label" }
typeFiles := make ( [ ] [ ] string , 4 )
2014-05-12 02:03:51 +08:00
for i , t := range types {
2016-12-23 02:12:23 +08:00
files , err := options . Dir ( t )
2014-07-26 12:24:27 +08:00
if err != nil {
2019-04-02 15:48:31 +08:00
log . Fatal ( "Failed to get %s files: %v" , t , err )
2014-07-26 12:24:27 +08:00
}
2016-12-23 02:12:23 +08:00
customPath := path . Join ( setting . CustomPath , "options" , t )
2020-11-28 10:42:08 +08:00
isDir , err := util . IsDir ( customPath )
if err != nil {
log . Fatal ( "Failed to get custom %s files: %v" , t , err )
}
if isDir {
2020-12-22 07:40:57 +08:00
customFiles , err := util . StatDir ( customPath )
2014-05-12 02:03:51 +08:00
if err != nil {
2019-04-02 15:48:31 +08:00
log . Fatal ( "Failed to get custom %s files: %v" , t , err )
2014-05-12 02:03:51 +08:00
}
for _ , f := range customFiles {
2020-12-25 17:59:32 +08:00
if ! util . IsStringInSlice ( f , files , true ) {
2014-05-12 02:03:51 +08:00
files = append ( files , f )
}
}
}
typeFiles [ i ] = files
}
2014-07-26 12:24:27 +08:00
Gitignores = typeFiles [ 0 ]
2014-05-12 02:03:51 +08:00
Licenses = typeFiles [ 1 ]
2015-08-28 16:44:04 +08:00
Readmes = typeFiles [ 2 ]
2019-12-07 10:13:19 +08:00
LabelTemplatesFiles := typeFiles [ 3 ]
2014-07-26 12:24:27 +08:00
sort . Strings ( Gitignores )
2014-06-03 11:17:21 +08:00
sort . Strings ( Licenses )
2015-08-28 16:44:04 +08:00
sort . Strings ( Readmes )
2019-12-07 10:13:19 +08:00
sort . Strings ( LabelTemplatesFiles )
// Load label templates
LabelTemplates = make ( map [ string ] string )
for _ , templateFile := range LabelTemplatesFiles {
labels , err := LoadLabelsFormatted ( templateFile )
if err != nil {
log . Error ( "Failed to load labels: %v" , err )
}
LabelTemplates [ templateFile ] = labels
}
2016-08-28 15:06:22 +08:00
// Filter out invalid names and promote preferred licenses.
sortedLicenses := make ( [ ] string , 0 , len ( Licenses ) )
for _ , name := range setting . Repository . PreferredLicenses {
2020-12-25 17:59:32 +08:00
if util . IsStringInSlice ( name , Licenses , true ) {
2016-08-28 15:06:22 +08:00
sortedLicenses = append ( sortedLicenses , name )
}
}
for _ , name := range Licenses {
2020-12-25 17:59:32 +08:00
if ! util . IsStringInSlice ( name , setting . Repository . PreferredLicenses , true ) {
2016-08-28 15:06:22 +08:00
sortedLicenses = append ( sortedLicenses , name )
}
}
Licenses = sortedLicenses
2014-03-21 13:48:10 +08:00
}
2014-03-17 23:56:50 +08:00
2016-11-29 01:27:55 +08:00
// NewRepoContext creates a new repository context
2014-03-21 13:48:10 +08:00
func NewRepoContext ( ) {
2019-05-15 09:57:00 +08:00
loadRepoConfig ( )
2021-11-10 03:57:58 +08:00
unit . LoadUnitConfig ( )
2014-08-23 23:58:56 +08:00
2021-11-19 01:42:27 +08:00
admin_model . RemoveAllWithNotice ( db . DefaultContext , "Clean up repository temporary data" , filepath . Join ( setting . AppDataPath , "tmp" ) )
2014-03-11 13:32:36 +08:00
}
2019-10-13 21:23:14 +08:00
// RepositoryStatus defines the status of repository
type RepositoryStatus int
// all kinds of RepositoryStatus
const (
2021-03-01 08:47:30 +08:00
RepositoryReady RepositoryStatus = iota // a normal repository
RepositoryBeingMigrated // repository is migrating
RepositoryPendingTransfer // repository pending in ownership transfer state
2019-10-13 21:23:14 +08:00
)
2020-09-20 00:44:55 +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
}
2014-03-21 04:04:56 +08:00
// Repository represents a git repository.
type Repository struct {
2020-01-12 17:36:21 +08:00
ID int64 ` xorm:"pk autoincr" `
OwnerID int64 ` xorm:"UNIQUE(s) index" `
OwnerName string
2020-01-21 04:01:19 +08:00
Owner * 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)" `
2019-10-14 14:10:42 +08:00
DefaultBranch string
2014-10-19 13:35:24 +08:00
2014-05-13 02:06:42 +08:00
NumWatches int
NumStars int
NumForks int
NumIssues int
NumClosedIssues int
NumOpenIssues int ` xorm:"-" `
2014-07-26 12:24:27 +08:00
NumPulls int
NumClosedPulls int
NumOpenPulls int ` xorm:"-" `
2014-05-13 02:06:42 +08:00
NumMilestones int ` xorm:"NOT NULL DEFAULT 0" `
NumClosedMilestones int ` xorm:"NOT NULL DEFAULT 0" `
NumOpenMilestones int ` xorm:"-" `
2020-08-17 11:07:38 +08:00
NumProjects int ` xorm:"NOT NULL DEFAULT 0" `
NumClosedProjects int ` xorm:"NOT NULL DEFAULT 0" `
NumOpenProjects int ` xorm:"-" `
2014-10-19 13:35:24 +08:00
2021-06-15 01:20:43 +08:00
IsPrivate bool ` xorm:"INDEX" `
IsEmpty bool ` xorm:"INDEX" `
IsArchived bool ` xorm:"INDEX" `
IsMirror bool ` xorm:"INDEX" `
* Mirror ` xorm:"-" `
PushMirrors [ ] * PushMirror ` xorm:"-" `
Status RepositoryStatus ` xorm:"NOT NULL DEFAULT 0" `
2014-10-19 13:35:24 +08:00
2020-05-24 16:14:26 +08:00
RenderingMetas map [ string ] string ` xorm:"-" `
DocumentRenderingMetas map [ string ] string ` xorm:"-" `
Units [ ] * RepoUnit ` xorm:"-" `
PrimaryLanguage * LanguageStat ` xorm:"-" `
2015-12-05 10:30:33 +08:00
2019-02-11 03:27:19 +08:00
IsFork bool ` xorm:"INDEX NOT NULL DEFAULT false" `
ForkID int64 ` xorm:"INDEX" `
BaseRepo * Repository ` xorm:"-" `
2019-11-11 23:15:29 +08:00
IsTemplate bool ` xorm:"INDEX NOT NULL DEFAULT false" `
TemplateID int64 ` xorm:"INDEX" `
TemplateRepo * Repository ` xorm:"-" `
2019-02-11 03:27:19 +08:00
Size int64 ` xorm:"NOT NULL DEFAULT 0" `
2020-02-11 17:34:17 +08:00
CodeIndexerStatus * RepoIndexerStatus ` xorm:"-" `
StatsIndexerStatus * RepoIndexerStatus ` xorm:"-" `
2019-02-11 03:27:19 +08:00
IsFsckEnabled bool ` xorm:"NOT NULL DEFAULT true" `
CloseIssuesViaCommitInAnyBranch bool ` xorm:"NOT NULL DEFAULT false" `
Topics [ ] string ` xorm:"TEXT JSON" `
2014-10-19 13:35:24 +08:00
2020-09-20 00:44:55 +08:00
TrustModel TrustModelType
2019-05-30 10:22:26 +08:00
// Avatar: ID(10-20)-md5(32) - must fit into 64 symbols
Avatar string ` xorm:"VARCHAR(64)" `
2019-08-15 22:46:21 +08:00
CreatedUnix timeutil . TimeStamp ` xorm:"INDEX created" `
UpdatedUnix timeutil . TimeStamp ` xorm:"INDEX updated" `
2014-03-21 04:04:56 +08:00
}
2021-09-19 19:49:59 +08:00
func init ( ) {
db . RegisterModel ( new ( Repository ) )
}
2020-01-25 18:57:43 +08:00
// SanitizedOriginalURL returns a sanitized OriginalURL
func ( repo * Repository ) SanitizedOriginalURL ( ) string {
if repo . OriginalURL == "" {
return ""
}
2021-06-15 01:20:43 +08:00
u , err := url . Parse ( repo . OriginalURL )
if err != nil {
return ""
}
u . User = nil
return u . String ( )
2020-01-25 18:57:43 +08:00
}
2019-04-23 04:40:51 +08:00
// ColorFormat returns a colored string to represent this repo
func ( repo * Repository ) ColorFormat ( s fmt . State ) {
log . ColorFprintf ( s , "%d:%s/%s" ,
log . NewColoredIDValue ( repo . ID ) ,
2020-07-02 22:09:09 +08:00
repo . OwnerName ,
2019-04-23 04:40:51 +08:00
repo . Name )
}
2021-02-18 23:39:04 +08:00
// IsBeingMigrated indicates that repository is being migrated
2019-10-13 21:23:14 +08:00
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 ( )
}
2017-10-02 00:52:35 +08:00
// 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 {
2020-09-25 12:09:23 +08:00
repo . DefaultBranch = setting . Repository . DefaultBranch
2015-08-20 20:18:49 +08:00
}
2017-10-02 00:52:35 +08:00
repo . NumOpenIssues = repo . NumIssues - repo . NumClosedIssues
repo . NumOpenPulls = repo . NumPulls - repo . NumClosedPulls
repo . NumOpenMilestones = repo . NumMilestones - repo . NumClosedMilestones
2020-08-17 11:07:38 +08:00
repo . NumOpenProjects = repo . NumProjects - repo . NumClosedProjects
2015-08-20 20:18:49 +08:00
}
2016-08-14 18:32:24 +08:00
// MustOwner always returns a valid *User object to avoid
// conceptually impossible error handling.
2017-01-05 08:50:34 +08:00
// It creates a fake object that contains error details
2016-08-14 18:32:24 +08:00
// when error occurs.
func ( repo * Repository ) MustOwner ( ) * User {
2021-09-23 23:45:36 +08:00
return repo . mustOwner ( db . GetEngine ( db . DefaultContext ) )
2016-08-14 18:32:24 +08:00
}
2016-11-29 01:27:55 +08:00
// FullName returns the repository full name
2016-08-14 18:32:24 +08:00
func ( repo * Repository ) FullName ( ) string {
2020-01-12 17:36:21 +08:00
return repo . OwnerName + "/" + repo . Name
2016-08-14 18:32:24 +08:00
}
2016-11-29 01:27:55 +08:00
// HTMLURL returns the repository HTML URL
2016-08-17 01:19:09 +08:00
func ( repo * Repository ) HTMLURL ( ) string {
2021-11-17 02:18:25 +08:00
return setting . AppURL + url . PathEscape ( repo . OwnerName ) + "/" + url . PathEscape ( repo . Name )
2016-08-14 18:32:24 +08:00
}
2020-05-20 20:47:24 +08:00
// 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 {
2021-11-17 02:18:25 +08:00
result = repo . HTMLURL ( ) + "/commit/" + url . PathEscape ( commitID )
2020-05-20 20:47:24 +08:00
}
return
}
2017-03-03 22:35:42 +08:00
// APIURL returns the repository API URL
func ( repo * Repository ) APIURL ( ) string {
2021-11-17 02:18:25 +08:00
return setting . AppURL + "api/v1/repos/" + url . PathEscape ( repo . OwnerName ) + "/" + url . PathEscape ( repo . Name )
2017-03-03 22:35:42 +08:00
}
2017-10-26 09:37:33 +08:00
// 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 )
}
2021-09-19 19:49:59 +08:00
func ( repo * Repository ) getUnits ( e db . Engine ) ( err error ) {
2017-02-04 23:53:46 +08:00
if repo . Units != nil {
return nil
}
repo . Units , err = getUnitsByRepoID ( e , repo . ID )
2020-01-17 15:34:37 +08:00
log . Trace ( "repo.Units: %-+v" , repo . Units )
2017-02-04 23:53:46 +08:00
return err
}
2017-05-18 22:54:24 +08:00
// CheckUnitUser check whether user could visit the unit of this repository
2021-11-10 03:57:58 +08:00
func ( repo * Repository ) CheckUnitUser ( user * User , unitType unit . Type ) bool {
2021-09-23 23:45:36 +08:00
return repo . checkUnitUser ( db . GetEngine ( db . DefaultContext ) , user , unitType )
2018-10-25 18:55:16 +08:00
}
2021-11-10 03:57:58 +08:00
func ( repo * Repository ) checkUnitUser ( e db . Engine , user * User , unitType unit . Type ) bool {
2020-09-16 07:49:34 +08:00
if user . IsAdmin {
2018-11-28 19:26:14 +08:00
return true
2017-06-15 10:50:12 +08:00
}
2018-11-28 19:26:14 +08:00
perm , err := getUserRepoPermission ( e , repo , user )
if err != nil {
2020-09-16 07:49:34 +08:00
log . Error ( "getUserRepoPermission(): %v" , err )
2018-11-28 19:26:14 +08:00
return false
2017-05-18 22:54:24 +08:00
}
2018-11-28 19:26:14 +08:00
return perm . CanRead ( unitType )
2017-02-04 23:53:46 +08:00
}
2017-08-02 16:46:54 +08:00
// UnitEnabled if this repository has the given unit enabled
2021-11-10 03:57:58 +08:00
func ( repo * Repository ) UnitEnabled ( tp unit . Type ) bool {
2021-09-23 23:45:36 +08:00
if err := repo . getUnits ( db . GetEngine ( db . DefaultContext ) ) ; err != nil {
2017-10-15 07:17:39 +08:00
log . Warn ( "Error loading repository (ID: %d) units: %s" , repo . ID , err . Error ( ) )
}
2017-02-04 23:53:46 +08:00
for _ , unit := range repo . Units {
if unit . Type == tp {
return true
}
}
return false
}
2019-05-30 23:09:05 +08:00
// ErrUnitTypeNotExist represents a "UnitTypeNotExist" kind of error.
type ErrUnitTypeNotExist struct {
2021-11-10 03:57:58 +08:00
UT unit . Type
2019-05-30 23:09:05 +08:00
}
// IsErrUnitTypeNotExist checks if an error is a ErrUnitNotExist.
func IsErrUnitTypeNotExist ( err error ) bool {
_ , ok := err . ( ErrUnitTypeNotExist )
return ok
}
func ( err ErrUnitTypeNotExist ) Error ( ) string {
return fmt . Sprintf ( "Unit type does not exist: %s" , err . UT . String ( ) )
}
2017-02-04 23:53:46 +08:00
// MustGetUnit always returns a RepoUnit object
2021-11-10 03:57:58 +08:00
func ( repo * Repository ) MustGetUnit ( tp unit . Type ) * RepoUnit {
2017-02-04 23:53:46 +08:00
ru , err := repo . GetUnit ( tp )
if err == nil {
return ru
}
2021-11-10 03:57:58 +08:00
if tp == unit . TypeExternalWiki {
2017-02-04 23:53:46 +08:00
return & RepoUnit {
Type : tp ,
Config : new ( ExternalWikiConfig ) ,
}
2021-11-10 03:57:58 +08:00
} else if tp == unit . TypeExternalTracker {
2017-02-04 23:53:46 +08:00
return & RepoUnit {
Type : tp ,
Config : new ( ExternalTrackerConfig ) ,
}
2021-11-10 03:57:58 +08:00
} else if tp == unit . TypePullRequests {
2018-01-06 02:56:50 +08:00
return & RepoUnit {
Type : tp ,
Config : new ( PullRequestsConfig ) ,
}
2021-11-10 03:57:58 +08:00
} else if tp == unit . TypeIssues {
2019-05-30 23:09:05 +08:00
return & RepoUnit {
Type : tp ,
Config : new ( IssuesConfig ) ,
}
2017-02-04 23:53:46 +08:00
}
return & RepoUnit {
Type : tp ,
Config : new ( UnitConfig ) ,
}
}
// GetUnit returns a RepoUnit object
2021-11-10 03:57:58 +08:00
func ( repo * Repository ) GetUnit ( tp unit . Type ) ( * RepoUnit , error ) {
2021-09-23 23:45:36 +08:00
return repo . getUnit ( db . GetEngine ( db . DefaultContext ) , tp )
2018-10-27 22:45:24 +08:00
}
2021-11-10 03:57:58 +08:00
func ( repo * Repository ) getUnit ( e db . Engine , tp unit . Type ) ( * RepoUnit , error ) {
2018-10-27 22:45:24 +08:00
if err := repo . getUnits ( e ) ; err != nil {
2017-02-04 23:53:46 +08:00
return nil , err
}
for _ , unit := range repo . Units {
if unit . Type == tp {
return unit , nil
}
}
2019-05-30 23:09:05 +08:00
return nil , ErrUnitTypeNotExist { tp }
2017-02-04 23:53:46 +08:00
}
2021-09-19 19:49:59 +08:00
func ( repo * Repository ) getOwner ( e db . Engine ) ( err error ) {
2015-10-24 15:36:47 +08:00
if repo . Owner != nil {
return nil
2014-10-10 07:01:22 +08:00
}
2015-10-24 15:36:47 +08:00
repo . Owner , err = getUserByID ( e , repo . OwnerID )
2014-05-08 20:18:03 +08:00
return err
}
2016-11-29 01:27:55 +08:00
// GetOwner returns the repository owner
2015-08-18 04:03:11 +08:00
func ( repo * Repository ) GetOwner ( ) error {
2021-09-23 23:45:36 +08:00
return repo . getOwner ( db . GetEngine ( db . DefaultContext ) )
2015-02-13 13:58:46 +08:00
}
2021-09-19 19:49:59 +08:00
func ( repo * Repository ) mustOwner ( e db . Engine ) * User {
2015-11-27 06:33:45 +08:00
if err := repo . getOwner ( e ) ; err != nil {
return & User {
Name : "error" ,
FullName : err . Error ( ) ,
}
}
return repo . Owner
}
2019-04-12 13:53:34 +08:00
// ComposeMetas composes a map of metas for properly rendering issue links and external issue trackers.
2015-12-05 10:30:33 +08:00
func ( repo * Repository ) ComposeMetas ( ) map [ string ] string {
2020-05-24 16:14:26 +08:00
if len ( repo . RenderingMetas ) == 0 {
2019-11-25 00:34:44 +08:00
metas := map [ string ] string {
2020-01-12 17:36:21 +08:00
"user" : repo . OwnerName ,
2019-08-14 16:04:55 +08:00
"repo" : repo . Name ,
"repoPath" : repo . RepoPath ( ) ,
2020-05-24 16:14:26 +08:00
"mode" : "comment" ,
2015-12-05 10:30:33 +08:00
}
2019-11-25 00:34:44 +08:00
2021-11-10 03:57:58 +08:00
unit , err := repo . GetUnit ( unit . TypeExternalTracker )
2019-11-25 00:34:44 +08:00
if err == nil {
metas [ "format" ] = unit . ExternalTrackerConfig ( ) . ExternalTrackerFormat
switch unit . ExternalTrackerConfig ( ) . ExternalTrackerStyle {
case markup . IssueNameStyleAlphanumeric :
metas [ "style" ] = markup . IssueNameStyleAlphanumeric
default :
metas [ "style" ] = markup . IssueNameStyleNumeric
}
2019-04-12 13:53:34 +08:00
}
2020-01-12 17:36:21 +08:00
repo . MustOwner ( )
2019-11-25 00:34:44 +08:00
if repo . Owner . IsOrganization ( ) {
teams := make ( [ ] string , 0 , 5 )
2021-09-23 23:45:36 +08:00
_ = db . GetEngine ( db . DefaultContext ) . Table ( "team_repo" ) .
2019-11-25 00:34:44 +08:00
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 , "," ) + ","
2020-01-12 17:36:21 +08:00
metas [ "org" ] = strings . ToLower ( repo . OwnerName )
2016-04-23 06:28:08 +08:00
}
2019-11-25 00:34:44 +08:00
repo . RenderingMetas = metas
2015-12-05 10:30:33 +08:00
}
2019-11-25 00:34:44 +08:00
return repo . RenderingMetas
2015-12-05 10:30:33 +08:00
}
2020-05-24 16:14:26 +08:00
// 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
}
2021-09-19 19:49:59 +08:00
func ( repo * Repository ) getAssignees ( e db . Engine ) ( _ [ ] * User , err error ) {
2016-08-16 09:40:32 +08:00
if err = repo . getOwner ( e ) ; err != nil {
2015-08-10 21:47:23 +08:00
return nil , err
}
accesses := make ( [ ] * Access , 0 , 10 )
2016-11-10 23:16:32 +08:00
if err = e .
Where ( "repo_id = ? AND mode >= ?" , repo . ID , AccessModeWrite ) .
Find ( & accesses ) ; err != nil {
2015-08-10 21:47:23 +08:00
return nil , err
}
2016-08-16 09:40:32 +08:00
// Leave a seat for owner itself to append later, but if owner is an organization
// and just waste 1 unit is cheaper than re-allocate memory once.
2016-08-16 09:48:20 +08:00
users := make ( [ ] * User , 0 , len ( accesses ) + 1 )
if len ( accesses ) > 0 {
userIDs := make ( [ ] int64 , len ( accesses ) )
for i := 0 ; i < len ( accesses ) ; i ++ {
userIDs [ i ] = accesses [ i ] . UserID
}
if err = e . In ( "id" , userIDs ) . Find ( & users ) ; err != nil {
return nil , err
}
2016-08-16 09:40:32 +08:00
}
2015-08-10 21:47:23 +08:00
if ! repo . Owner . IsOrganization ( ) {
users = append ( users , repo . Owner )
}
return users , nil
}
2016-08-16 09:40:32 +08:00
// GetAssignees returns all users that have write access and can be assigned to issues
// of the repository,
func ( repo * Repository ) GetAssignees ( ) ( _ [ ] * User , err error ) {
2021-09-23 23:45:36 +08:00
return repo . getAssignees ( db . GetEngine ( db . DefaultContext ) )
2016-08-16 09:40:32 +08:00
}
2021-09-19 19:49:59 +08:00
func ( repo * Repository ) getReviewers ( e db . Engine , doerID , posterID int64 ) ( [ ] * User , error ) {
2020-10-13 03:55:13 +08:00
// Get the owner of the repository - this often already pre-cached and if so saves complexity for the following queries
if err := repo . getOwner ( e ) ; err != nil {
2020-04-07 00:33:34 +08:00
return nil , err
}
2020-10-13 03:55:13 +08:00
var users [ ] * User
2020-04-07 00:33:34 +08:00
2021-06-27 03:53:14 +08:00
if repo . IsPrivate || repo . Owner . Visibility == api . VisibleTypePrivate {
2020-10-13 03:55:13 +08:00
// This a private repository:
// Anyone who can read the repository is a requestable reviewer
if err := e .
SQL ( "SELECT * FROM `user` WHERE id in (SELECT user_id FROM `access` WHERE repo_id = ? AND mode >= ? AND user_id NOT IN ( ?, ?)) ORDER BY name" ,
repo . ID , AccessModeRead ,
doerID , posterID ) .
Find ( & users ) ; err != nil {
return nil , err
}
2020-04-07 00:33:34 +08:00
2020-10-13 03:55:13 +08:00
return users , nil
}
2020-04-07 00:33:34 +08:00
2020-10-13 03:55:13 +08:00
// This is a "public" repository:
2021-03-01 02:24:00 +08:00
// Any user that has read access, is a watcher or organization member can be requested to review
2020-10-13 03:55:13 +08:00
if err := e .
SQL ( "SELECT * FROM `user` WHERE id IN ( " +
2021-03-01 02:24:00 +08:00
"SELECT user_id FROM `access` WHERE repo_id = ? AND mode >= ? " +
2020-10-13 03:55:13 +08:00
"UNION " +
2021-03-01 02:24:00 +08:00
"SELECT user_id FROM `watch` WHERE repo_id = ? AND mode IN (?, ?) " +
"UNION " +
"SELECT uid AS user_id FROM `org_user` WHERE org_id = ? " +
") AND id NOT IN (?, ?) ORDER BY name" ,
repo . ID , AccessModeRead ,
repo . ID , RepoWatchModeNormal , RepoWatchModeAuto ,
repo . OwnerID ,
doerID , posterID ) .
2020-04-07 00:33:34 +08:00
Find ( & users ) ; err != nil {
return nil , err
}
return users , nil
}
2020-10-13 03:55:13 +08:00
// GetReviewers get all users can be requested to review:
// * for private repositories this returns all users that have read access or higher to the repository.
2021-03-01 02:24:00 +08:00
// * for public repositories this returns all users that have read access or higher to the repository,
// all repo watchers and all organization members.
2021-02-18 23:39:04 +08:00
// TODO: may be we should have a busy choice for users to block review request to them.
2020-10-13 03:55:13 +08:00
func ( repo * Repository ) GetReviewers ( doerID , posterID int64 ) ( [ ] * User , error ) {
2021-09-23 23:45:36 +08:00
return repo . getReviewers ( db . GetEngine ( db . DefaultContext ) , doerID , posterID )
2020-10-13 03:55:13 +08:00
}
// GetReviewerTeams get all teams can be requested to review
func ( repo * Repository ) GetReviewerTeams ( ) ( [ ] * Team , error ) {
if err := repo . GetOwner ( ) ; err != nil {
2020-04-07 00:33:34 +08:00
return nil , err
}
2020-10-13 03:55:13 +08:00
if ! repo . Owner . IsOrganization ( ) {
return nil , nil
}
2020-04-07 00:33:34 +08:00
2020-10-13 03:55:13 +08:00
teams , err := GetTeamsWithAccessToRepo ( repo . OwnerID , repo . ID , AccessModeRead )
if err != nil {
return nil , err
2020-04-07 00:33:34 +08:00
}
2020-10-13 03:55:13 +08:00
return teams , err
2020-04-07 00:33:34 +08:00
}
2015-08-10 21:47:23 +08:00
// GetMilestoneByID returns the milestone belongs to repository by given ID.
func ( repo * Repository ) GetMilestoneByID ( milestoneID int64 ) ( * Milestone , error ) {
2016-08-25 07:05:56 +08:00
return GetMilestoneByRepoID ( repo . ID , milestoneID )
2015-08-10 21:47:23 +08:00
}
2015-08-25 22:58:34 +08:00
// IssueStats returns number of open and closed repository issues by given filter mode.
2015-09-03 04:18:09 +08:00
func ( repo * Repository ) IssueStats ( uid int64 , filterMode int , isPull bool ) ( int64 , int64 ) {
return GetRepoIssueStats ( repo . ID , uid , filterMode , isPull )
2015-08-25 22:58:34 +08:00
}
2016-11-29 01:27:55 +08:00
// GetMirror sets the repository mirror, returns an error upon failure
2014-08-11 11:11:18 +08:00
func ( repo * Repository ) GetMirror ( ) ( err error ) {
2016-08-31 07:18:33 +08:00
repo . Mirror , err = GetMirrorByRepoID ( repo . ID )
2014-08-11 11:11:18 +08:00
return err
}
2021-06-15 01:20:43 +08:00
// LoadPushMirrors populates the repository push mirrors.
func ( repo * Repository ) LoadPushMirrors ( ) ( err error ) {
repo . PushMirrors , err = GetPushMirrorsByRepoID ( repo . ID )
return err
}
2018-01-05 18:56:52 +08:00
// 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)
2015-08-08 22:43:14 +08:00
func ( repo * Repository ) GetBaseRepo ( ) ( err error ) {
2021-09-23 23:45:36 +08:00
return repo . getBaseRepo ( db . GetEngine ( db . DefaultContext ) )
2018-10-20 00:36:42 +08:00
}
2021-09-19 19:49:59 +08:00
func ( repo * Repository ) getBaseRepo ( e db . Engine ) ( err error ) {
2014-10-19 13:35:24 +08:00
if ! repo . IsFork {
return nil
}
2018-10-20 00:36:42 +08:00
repo . BaseRepo , err = getRepositoryByID ( e , repo . ForkID )
2014-10-19 13:35:24 +08:00
return err
}
2019-11-11 23:15:29 +08:00
// IsGenerated returns whether _this_ repository was generated from a template
func ( repo * Repository ) IsGenerated ( ) bool {
return repo . TemplateID != 0
}
// 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 ( repo * Repository ) GetTemplateRepo ( ) ( err error ) {
2021-09-23 23:45:36 +08:00
return repo . getTemplateRepo ( db . GetEngine ( db . DefaultContext ) )
2019-11-11 23:15:29 +08:00
}
2021-09-19 19:49:59 +08:00
func ( repo * Repository ) getTemplateRepo ( e db . Engine ) ( err error ) {
2019-11-11 23:15:29 +08:00
if ! repo . IsGenerated ( ) {
return nil
}
repo . TemplateRepo , err = getRepositoryByID ( e , repo . TemplateID )
return err
}
2016-11-29 01:27:55 +08:00
// RepoPath returns the repository path
2015-11-27 06:33:45 +08:00
func ( repo * Repository ) RepoPath ( ) string {
2020-01-12 17:36:21 +08:00
return RepoPath ( repo . OwnerName , repo . Name )
2015-09-03 20:09:08 +08:00
}
2017-12-03 13:29:41 +08:00
// GitConfigPath returns the path to a repository's git config/ directory
func GitConfigPath ( repoPath string ) string {
return filepath . Join ( repoPath , "config" )
}
2016-11-29 01:27:55 +08:00
// GitConfigPath returns the repository git config path
2015-12-09 09:06:12 +08:00
func ( repo * Repository ) GitConfigPath ( ) string {
2017-12-03 13:29:41 +08:00
return GitConfigPath ( repo . RepoPath ( ) )
2015-12-09 09:06:12 +08:00
}
2016-11-29 01:27:55 +08:00
// Link returns the repository link
2016-08-17 14:06:38 +08:00
func ( repo * Repository ) Link ( ) string {
2021-11-17 02:18:25 +08:00
return setting . AppSubURL + "/" + url . PathEscape ( repo . OwnerName ) + "/" + url . PathEscape ( repo . Name )
2016-03-05 03:50:34 +08:00
}
2016-11-29 01:27:55 +08:00
// ComposeCompareURL returns the repository comparison URL
2015-12-10 09:46:05 +08:00
func ( repo * Repository ) ComposeCompareURL ( oldCommitID , newCommitID string ) string {
2021-11-17 02:18:25 +08:00
return fmt . Sprintf ( "%s/%s/compare/%s...%s" , url . PathEscape ( repo . OwnerName ) , url . PathEscape ( repo . Name ) , util . PathEscapeSegments ( oldCommitID ) , util . PathEscapeSegments ( newCommitID ) )
2015-12-10 09:46:05 +08:00
}
2017-02-21 23:02:10 +08:00
// UpdateDefaultBranch updates the default branch
func ( repo * Repository ) UpdateDefaultBranch ( ) error {
2021-09-23 23:45:36 +08:00
_ , err := db . GetEngine ( db . DefaultContext ) . ID ( repo . ID ) . Cols ( "default_branch" ) . Update ( repo )
2017-02-21 23:02:10 +08:00
return err
}
2016-11-29 01:27:55 +08:00
// IsOwnedBy returns true when user owns this repository
2015-08-14 02:43:40 +08:00
func ( repo * Repository ) IsOwnedBy ( userID int64 ) bool {
return repo . OwnerID == userID
2014-10-14 03:23:30 +08:00
}
2021-09-19 19:49:59 +08:00
func ( repo * Repository ) updateSize ( e db . Engine ) error {
2020-01-12 17:36:21 +08:00
size , err := util . GetDirectorySize ( repo . RepoPath ( ) )
2017-04-11 21:30:15 +08:00
if err != nil {
2020-01-12 20:11:17 +08:00
return fmt . Errorf ( "updateSize: %v" , err )
2017-04-11 21:30:15 +08:00
}
2021-03-28 11:56:28 +08:00
lfsSize , err := e . Where ( "repository_id = ?" , repo . ID ) . SumInt ( new ( LFSMetaObject ) , "size" )
2020-05-31 12:51:19 +08:00
if err != nil {
return fmt . Errorf ( "updateSize: GetLFSMetaObjects: %v" , err )
}
2021-03-28 11:56:28 +08:00
repo . Size = size + lfsSize
2021-04-28 00:33:16 +08:00
_ , err = e . ID ( repo . ID ) . Cols ( "size" ) . NoAutoTime ( ) . Update ( repo )
2017-04-11 21:30:15 +08:00
return err
}
2019-11-11 05:33:47 +08:00
// UpdateSize updates the repository size, calculating it using util.GetDirectorySize
2021-09-23 23:45:36 +08:00
func ( repo * Repository ) UpdateSize ( ctx context . Context ) error {
return repo . updateSize ( db . GetEngine ( ctx ) )
2017-05-26 13:08:13 +08:00
}
2021-11-22 23:21:55 +08:00
// CanUserForkRepo returns true if specified user can fork repository.
func CanUserForkRepo ( user * User , repo * Repository ) ( bool , error ) {
2017-10-15 23:06:07 +08:00
if user == nil {
return false , nil
}
2021-11-22 23:21:55 +08:00
if repo . OwnerID != user . ID && ! HasForkedRepo ( user . ID , repo . ID ) {
2017-10-15 23:06:07 +08:00
return true , nil
}
2021-11-22 23:21:55 +08:00
ownedOrgs , err := GetOwnedOrgsByUserID ( user . ID )
if err != nil {
2017-10-15 23:06:07 +08:00
return false , err
}
2021-11-22 23:21:55 +08:00
for _ , org := range ownedOrgs {
if repo . OwnerID != org . ID && ! HasForkedRepo ( org . ID , repo . ID ) {
2017-10-15 23:06:07 +08:00
return true , nil
}
}
return false , nil
}
2019-10-26 14:54:11 +08:00
// CanUserDelete returns true if user could delete the repository
func ( repo * Repository ) CanUserDelete ( user * User ) ( bool , error ) {
if user . IsAdmin || user . ID == repo . OwnerID {
return true , nil
}
if err := repo . GetOwner ( ) ; err != nil {
return false , err
}
if repo . Owner . IsOrganization ( ) {
2021-11-19 19:41:40 +08:00
isOwner , err := OrgFromUser ( repo . Owner ) . IsOwnedBy ( user . ID )
2019-10-26 14:54:11 +08:00
if err != nil {
return false , err
} else if isOwner {
return true , nil
}
}
return false , nil
}
2016-02-20 03:33:06 +08:00
// CanEnablePulls returns true if repository meets the requirements of accepting pulls.
func ( repo * Repository ) CanEnablePulls ( ) bool {
2019-01-18 08:01:04 +08:00
return ! repo . IsMirror && ! repo . IsEmpty
2016-02-20 03:33:06 +08:00
}
2016-11-29 01:27:55 +08:00
// AllowsPulls returns true if repository meets the requirements of accepting pulls and has them enabled.
2016-02-20 03:33:06 +08:00
func ( repo * Repository ) AllowsPulls ( ) bool {
2021-11-10 03:57:58 +08:00
return repo . CanEnablePulls ( ) && repo . UnitEnabled ( unit . TypePullRequests )
2015-09-06 02:31:52 +08:00
}
2016-08-28 19:56:41 +08:00
// CanEnableEditor returns true if repository meets the requirements of web editor.
func ( repo * Repository ) CanEnableEditor ( ) bool {
return ! repo . IsMirror
}
2019-10-09 03:18:17 +08:00
// GetReaders returns all users that have explicit read access or higher to the repository.
func ( repo * Repository ) GetReaders ( ) ( _ [ ] * User , err error ) {
2021-09-23 23:45:36 +08:00
return repo . getUsersWithAccessMode ( db . GetEngine ( db . DefaultContext ) , AccessModeRead )
2019-10-09 03:18:17 +08:00
}
2017-09-14 16:16:22 +08:00
// GetWriters returns all users that have write access to the repository.
func ( repo * Repository ) GetWriters ( ) ( _ [ ] * User , err error ) {
2021-09-23 23:45:36 +08:00
return repo . getUsersWithAccessMode ( db . GetEngine ( db . DefaultContext ) , AccessModeWrite )
2017-09-14 16:16:22 +08:00
}
2019-10-09 03:18:17 +08:00
// IsReader returns true if user has explicit read access or higher to the repository.
func ( repo * Repository ) IsReader ( userID int64 ) ( bool , error ) {
if repo . OwnerID == userID {
return true , nil
}
2021-09-23 23:45:36 +08:00
return db . GetEngine ( db . DefaultContext ) . Where ( "repo_id = ? AND user_id = ? AND mode >= ?" , repo . ID , userID , AccessModeRead ) . Get ( & Access { } )
2019-10-09 03:18:17 +08:00
}
2017-09-14 16:16:22 +08:00
// getUsersWithAccessMode returns users that have at least given access mode to the repository.
2021-09-19 19:49:59 +08:00
func ( repo * Repository ) getUsersWithAccessMode ( e db . Engine , mode AccessMode ) ( _ [ ] * User , err error ) {
2017-09-14 16:16:22 +08:00
if err = repo . getOwner ( e ) ; err != nil {
return nil , err
}
accesses := make ( [ ] * Access , 0 , 10 )
if err = e . Where ( "repo_id = ? AND mode >= ?" , repo . ID , mode ) . Find ( & accesses ) ; err != nil {
return nil , err
}
// Leave a seat for owner itself to append later, but if owner is an organization
// and just waste 1 unit is cheaper than re-allocate memory once.
users := make ( [ ] * User , 0 , len ( accesses ) + 1 )
if len ( accesses ) > 0 {
userIDs := make ( [ ] int64 , len ( accesses ) )
for i := 0 ; i < len ( accesses ) ; i ++ {
userIDs [ i ] = accesses [ i ] . UserID
}
if err = e . In ( "id" , userIDs ) . Find ( & users ) ; err != nil {
return nil , err
}
}
if ! repo . Owner . IsOrganization ( ) {
users = append ( users , repo . Owner )
}
return users , nil
}
2016-11-29 01:27:55 +08:00
// DescriptionHTML does special handles to description and return HTML string.
func ( repo * Repository ) DescriptionHTML ( ) template . HTML {
2021-04-20 06:25:08 +08:00
desc , err := markup . RenderDescriptionHTML ( & markup . RenderContext {
URLPrefix : repo . HTMLURL ( ) ,
Metas : repo . ComposeMetas ( ) ,
} , repo . Description )
2019-03-12 10:23:34 +08:00
if err != nil {
2019-04-02 15:48:31 +08:00
log . Error ( "Failed to render description for %s (ID: %d): %v" , repo . Name , repo . ID , err )
2019-03-12 10:23:34 +08:00
return template . HTML ( markup . Sanitize ( repo . Description ) )
2014-07-23 02:08:04 +08:00
}
2019-03-12 10:23:34 +08:00
return template . HTML ( markup . Sanitize ( string ( desc ) ) )
2014-07-23 01:52:37 +08:00
}
2021-03-01 08:47:30 +08:00
// ReadBy sets repo to be visited by given user.
func ( repo * Repository ) ReadBy ( userID int64 ) error {
2021-09-23 23:45:36 +08:00
return setRepoNotificationStatusReadIfUnread ( db . GetEngine ( db . DefaultContext ) , userID , repo . ID )
2021-03-01 08:47:30 +08:00
}
2021-09-19 19:49:59 +08:00
func isRepositoryExist ( e db . Engine , u * User , repoName string ) ( bool , error ) {
2015-08-08 17:10:34 +08:00
has , err := e . Get ( & Repository {
2016-07-24 01:08:22 +08:00
OwnerID : u . ID ,
2015-02-24 13:27:22 +08:00
LowerName : strings . ToLower ( repoName ) ,
} )
2020-11-28 10:42:08 +08:00
if err != nil {
return false , err
}
isDir , err := util . IsDir ( RepoPath ( u . Name , repoName ) )
return has && isDir , err
2014-02-14 22:20:57 +08:00
}
2015-08-08 17:10:34 +08:00
// IsRepositoryExist returns true if the repository with given name under user has already existed.
func IsRepositoryExist ( u * User , repoName string ) ( bool , error ) {
2021-09-23 23:45:36 +08:00
return isRepositoryExist ( db . GetEngine ( db . DefaultContext ) , u , repoName )
2015-08-08 17:10:34 +08:00
}
2014-12-14 05:46:00 +08:00
// CloneLink represents different types of clone URLs of repository.
type CloneLink struct {
SSH string
HTTPS string
Git string
}
2016-08-08 05:29:16 +08:00
// ComposeHTTPSCloneURL returns HTTPS clone URL based on given owner and repository name.
func ComposeHTTPSCloneURL ( owner , repo string ) string {
2019-03-18 22:00:23 +08:00
return fmt . Sprintf ( "%s%s/%s.git" , setting . AppURL , url . PathEscape ( owner ) , url . PathEscape ( repo ) )
2016-08-08 05:29:16 +08:00
}
2020-01-12 17:36:21 +08:00
func ( repo * Repository ) cloneLink ( isWiki bool ) * CloneLink {
2015-12-01 09:45:55 +08:00
repoName := repo . Name
if isWiki {
repoName += ".wiki"
2014-12-14 05:46:00 +08:00
}
2015-04-18 18:21:07 +08:00
2017-10-14 23:51:00 +08:00
sshUser := setting . RunUser
if setting . SSH . StartBuiltinServer {
sshUser = setting . SSH . BuiltinServerUser
}
2015-12-01 09:45:55 +08:00
cl := new ( CloneLink )
2020-07-27 04:31:28 +08:00
// 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 + "]"
}
2016-02-28 09:48:39 +08:00
if setting . SSH . Port != 22 {
2021-11-17 02:18:25 +08:00
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 ) )
2017-08-26 21:57:41 +08:00
} else if setting . Repository . UseCompatSSHURI {
2021-11-17 02:18:25 +08:00
cl . SSH = fmt . Sprintf ( "ssh://%s@%s/%s/%s.git" , sshUser , sshDomain , url . PathEscape ( repo . OwnerName ) , url . PathEscape ( repoName ) )
2014-12-14 05:46:00 +08:00
} else {
2021-11-17 02:18:25 +08:00
cl . SSH = fmt . Sprintf ( "%s@%s:%s/%s.git" , sshUser , sshDomain , url . PathEscape ( repo . OwnerName ) , url . PathEscape ( repoName ) )
2014-12-14 05:46:00 +08:00
}
2020-01-12 17:36:21 +08:00
cl . HTTPS = ComposeHTTPSCloneURL ( repo . OwnerName , repoName )
2015-12-01 09:45:55 +08:00
return cl
}
// CloneLink returns clone URLs of repository.
func ( repo * Repository ) CloneLink ( ) ( cl * CloneLink ) {
2020-01-12 17:36:21 +08:00
return repo . cloneLink ( false )
2014-12-14 05:46:00 +08:00
}
2019-10-13 21:23:14 +08:00
// CheckCreateRepository check if could created a repository
2020-09-25 12:09:23 +08:00
func CheckCreateRepository ( doer , u * User , name string , overwriteOrAdopt bool ) error {
2019-10-13 21:23:14 +08:00
if ! doer . CanCreateRepo ( ) {
return ErrReachLimitOfRepo { u . MaxRepoCreation }
}
if err := IsUsableRepoName ( name ) ; err != nil {
return err
}
2021-09-23 23:45:36 +08:00
has , err := isRepositoryExist ( db . GetEngine ( db . DefaultContext ) , u , name )
2014-04-13 08:35:35 +08:00
if err != nil {
2019-10-13 21:23:14 +08:00
return fmt . Errorf ( "IsRepositoryExist: %v" , err )
} else if has {
return ErrRepoAlreadyExist { u . Name , name }
2014-04-13 08:35:35 +08:00
}
2020-09-25 12:09:23 +08:00
2020-11-28 10:42:08 +08:00
isExist , err := util . IsExist ( RepoPath ( u . Name , name ) )
if err != nil {
log . Error ( "Unable to check if %s exists. Error: %v" , RepoPath ( u . Name , name ) , err )
return err
}
if ! overwriteOrAdopt && isExist {
2020-09-25 12:09:23 +08:00
return ErrRepoFilesAlreadyExist { u . Name , name }
}
2019-10-13 21:23:14 +08:00
return nil
}
2014-04-13 08:35:35 +08:00
2016-11-29 01:27:55 +08:00
// CreateRepoOptions contains the create repository options
2015-08-28 18:33:09 +08:00
type CreateRepoOptions struct {
2020-01-10 23:35:17 +08:00
Name string
Description string
OriginalURL string
2020-01-21 04:01:19 +08:00
GitServiceType api . GitServiceType
2020-01-10 23:35:17 +08:00
Gitignores string
IssueLabels string
License string
Readme string
2020-03-27 03:14:51 +08:00
DefaultBranch string
2020-01-10 23:35:17 +08:00
IsPrivate bool
IsMirror bool
2020-09-25 13:18:37 +08:00
IsTemplate bool
2020-01-10 23:35:17 +08:00
AutoInit bool
Status RepositoryStatus
2020-09-20 00:44:55 +08:00
TrustModel TrustModelType
2021-01-03 07:47:47 +08:00
MirrorInterval string
2015-08-28 18:33:09 +08:00
}
2021-08-28 16:37:14 +08:00
// ForkRepoOptions contains the fork repository options
type ForkRepoOptions struct {
BaseRepo * Repository
Name string
Description string
}
2020-01-12 20:11:17 +08:00
// GetRepoInitFile returns repository init files
func GetRepoInitFile ( tp , name string ) ( [ ] byte , error ) {
2018-05-01 09:46:04 +08:00
cleanedName := strings . TrimLeft ( path . Clean ( "/" + name ) , "/" )
2016-12-23 02:12:23 +08:00
relPath := path . Join ( "options" , tp , cleanedName )
2015-08-28 18:33:09 +08:00
// Use custom file when available.
customPath := path . Join ( setting . CustomPath , relPath )
2020-11-28 10:42:08 +08:00
isFile , err := util . IsFile ( customPath )
if err != nil {
log . Error ( "Unable to check if %s is a file. Error: %v" , customPath , err )
}
if isFile {
2021-09-22 13:38:34 +08:00
return os . ReadFile ( customPath )
2015-08-28 18:33:09 +08:00
}
2016-12-23 02:12:23 +08:00
switch tp {
case "readme" :
return options . Readme ( cleanedName )
case "gitignore" :
return options . Gitignore ( cleanedName )
case "license" :
return options . License ( cleanedName )
2016-12-23 15:18:05 +08:00
case "label" :
return options . Labels ( cleanedName )
2016-12-23 02:12:23 +08:00
default :
return [ ] byte { } , fmt . Errorf ( "Invalid init file type" )
}
2015-08-28 18:33:09 +08:00
}
2016-07-23 18:58:18 +08:00
var (
reservedRepoNames = [ ] string { "." , ".." }
2021-07-01 23:13:20 +08:00
reservedRepoPatterns = [ ] string { "*.git" , "*.wiki" , "*.rss" , "*.atom" }
2016-07-23 18:58:18 +08:00
)
2016-11-29 01:27:55 +08:00
// IsUsableRepoName returns true when repository is usable
2016-07-23 18:58:18 +08:00
func IsUsableRepoName ( name string ) error {
2020-09-25 12:09:23 +08:00
if alphaDashDotPattern . MatchString ( name ) {
// Note: usually this error is normally caught up earlier in the UI
return ErrNameCharsNotAllowed { Name : name }
}
2016-07-23 18:58:18 +08:00
return isUsableName ( reservedRepoNames , reservedRepoPatterns , name )
}
2020-01-12 20:11:17 +08:00
// CreateRepository creates a repository for the user/organization.
2021-09-23 23:45:36 +08:00
func CreateRepository ( ctx context . Context , doer , u * User , repo * Repository , overwriteOrAdopt bool ) ( err error ) {
2016-07-23 18:58:18 +08:00
if err = IsUsableRepoName ( repo . Name ) ; err != nil {
2015-08-08 17:10:34 +08:00
return err
2014-06-19 13:08:03 +08:00
}
2021-09-23 23:45:36 +08:00
has , err := isRepositoryExist ( db . GetEngine ( ctx ) , u , repo . Name )
2015-03-27 05:11:47 +08:00
if err != nil {
2015-08-08 17:10:34 +08:00
return fmt . Errorf ( "IsRepositoryExist: %v" , err )
2015-03-27 05:11:47 +08:00
} else if has {
2015-08-08 17:10:34 +08:00
return ErrRepoAlreadyExist { u . Name , repo . Name }
2014-06-19 13:08:03 +08:00
}
2020-09-25 12:09:23 +08:00
repoPath := RepoPath ( u . Name , repo . Name )
2020-11-28 10:42:08 +08:00
isExist , err := util . IsExist ( repoPath )
if err != nil {
log . Error ( "Unable to check if %s exists. Error: %v" , repoPath , err )
return err
}
if ! overwriteOrAdopt && isExist {
2020-09-25 12:09:23 +08:00
log . Error ( "Files already exist in %s and we are not going to adopt or delete." , repoPath )
return ErrRepoFilesAlreadyExist {
Uname : u . Name ,
Name : repo . Name ,
}
}
2021-09-23 23:45:36 +08:00
if _ , err = db . GetEngine ( ctx ) . Insert ( repo ) ; err != nil {
2015-08-08 17:10:34 +08:00
return err
2015-08-30 01:13:24 +08:00
}
2021-09-23 23:45:36 +08:00
if err = deleteRepoRedirect ( db . GetEngine ( ctx ) , u . ID , repo . Name ) ; err != nil {
2017-02-05 22:35:03 +08:00
return err
}
2015-08-30 01:13:24 +08:00
2017-02-04 23:53:46 +08:00
// insert units for repo
2021-11-10 03:57:58 +08:00
units := make ( [ ] RepoUnit , 0 , len ( unit . DefaultRepoUnits ) )
for _ , tp := range unit . DefaultRepoUnits {
if tp == unit . TypeIssues {
2017-09-12 14:48:13 +08:00
units = append ( units , RepoUnit {
RepoID : repo . ID ,
Type : tp ,
2018-07-18 05:23:58 +08:00
Config : & IssuesConfig {
EnableTimetracker : setting . Service . DefaultEnableTimetracking ,
AllowOnlyContributorsToTrackTime : setting . Service . DefaultAllowOnlyContributorsToTrackTime ,
EnableDependencies : setting . Service . DefaultEnableDependencies ,
} ,
2017-09-12 14:48:13 +08:00
} )
2021-11-10 03:57:58 +08:00
} else if tp == unit . TypePullRequests {
2018-07-05 11:02:54 +08:00
units = append ( units , RepoUnit {
RepoID : repo . ID ,
Type : tp ,
2021-03-27 22:55:40 +08:00
Config : & PullRequestsConfig { AllowMerge : true , AllowRebase : true , AllowRebaseMerge : true , AllowSquash : true , DefaultMergeStyle : MergeStyleMerge } ,
2018-07-05 11:02:54 +08:00
} )
2017-09-12 14:48:13 +08:00
} else {
units = append ( units , RepoUnit {
RepoID : repo . ID ,
Type : tp ,
} )
}
2017-02-04 23:53:46 +08:00
}
2021-09-23 23:45:36 +08:00
if _ , err = db . GetEngine ( ctx ) . Insert ( & units ) ; err != nil {
2017-02-04 23:53:46 +08:00
return err
}
2015-08-30 01:13:24 +08:00
// Remember visibility preference.
u . LastRepoVisibility = repo . IsPrivate
2021-09-23 23:45:36 +08:00
if err = updateUserCols ( db . GetEngine ( ctx ) , u , "last_repo_visibility" ) ; err != nil {
2015-08-30 01:13:24 +08:00
return fmt . Errorf ( "updateUser: %v" , err )
2015-08-08 17:10:34 +08:00
}
2021-09-23 23:45:36 +08:00
if _ , err = db . GetEngine ( ctx ) . Incr ( "num_repos" ) . ID ( u . ID ) . Update ( new ( User ) ) ; err != nil {
2019-07-18 01:34:13 +08:00
return fmt . Errorf ( "increment user total_repos: %v" , err )
}
u . NumRepos ++
2019-11-06 17:37:14 +08:00
// Give access to all members in teams with access to all repositories.
2015-08-08 17:10:34 +08:00
if u . IsOrganization ( ) {
2021-11-19 19:41:40 +08:00
teams , err := OrgFromUser ( u ) . loadTeams ( db . GetEngine ( ctx ) )
if err != nil {
2021-08-12 20:43:08 +08:00
return fmt . Errorf ( "loadTeams: %v" , err )
2019-10-26 14:54:11 +08:00
}
2021-11-19 19:41:40 +08:00
for _ , t := range teams {
2019-11-06 17:37:14 +08:00
if t . IncludesAllRepositories {
2021-09-23 23:45:36 +08:00
if err := t . addRepository ( db . GetEngine ( ctx ) , repo ) ; err != nil {
2019-11-06 17:37:14 +08:00
return fmt . Errorf ( "addRepository: %v" , err )
}
}
2015-08-08 17:10:34 +08:00
}
2019-11-20 19:27:49 +08:00
2021-09-23 23:45:36 +08:00
if isAdmin , err := isUserRepoAdmin ( db . GetEngine ( ctx ) , repo , doer ) ; err != nil {
2019-11-20 19:27:49 +08:00
return fmt . Errorf ( "isUserRepoAdmin: %v" , err )
} else if ! isAdmin {
// Make creator repo admin if it wan't assigned automatically
2021-09-23 23:45:36 +08:00
if err = repo . addCollaborator ( db . GetEngine ( ctx ) , doer ) ; err != nil {
2019-11-20 19:27:49 +08:00
return fmt . Errorf ( "AddCollaborator: %v" , err )
}
2021-09-23 23:45:36 +08:00
if err = repo . changeCollaborationAccessMode ( db . GetEngine ( ctx ) , doer . ID , AccessModeAdmin ) ; err != nil {
2019-11-20 19:27:49 +08:00
return fmt . Errorf ( "ChangeCollaborationAccessMode: %v" , err )
}
}
2021-09-23 23:45:36 +08:00
} else if err = repo . recalculateAccesses ( db . GetEngine ( ctx ) ) ; err != nil {
2015-08-08 17:10:34 +08:00
// Organization automatically called this in addRepository method.
2019-06-13 03:41:28 +08:00
return fmt . Errorf ( "recalculateAccesses: %v" , err )
2015-08-08 17:10:34 +08:00
}
2019-01-27 17:25:21 +08:00
if setting . Service . AutoWatchNewRepos {
2021-09-23 23:45:36 +08:00
if err = watchRepo ( db . GetEngine ( ctx ) , doer . ID , repo . ID , true ) ; err != nil {
2019-01-27 17:25:21 +08:00
return fmt . Errorf ( "watchRepo: %v" , err )
}
}
2015-08-08 17:10:34 +08:00
2021-11-10 13:13:16 +08:00
if err = webhook . CopyDefaultWebhooksToRepo ( ctx , repo . ID ) ; err != nil {
2019-03-19 10:33:20 +08:00
return fmt . Errorf ( "copyDefaultWebhooksToRepo: %v" , err )
}
2015-08-08 17:10:34 +08:00
return nil
}
2021-10-14 03:47:02 +08:00
// CheckDaemonExportOK creates/removes git-daemon-export-ok for git-daemon...
func ( repo * Repository ) CheckDaemonExportOK ( ctx context . Context ) error {
e := db . GetEngine ( ctx )
if err := repo . getOwner ( e ) ; err != nil {
return err
}
// Create/Remove git-daemon-export-ok for git-daemon...
daemonExportFile := path . Join ( repo . RepoPath ( ) , ` git-daemon-export-ok ` )
isExist , err := util . IsExist ( daemonExportFile )
if err != nil {
log . Error ( "Unable to check if %s exists. Error: %v" , daemonExportFile , err )
return err
}
isPublic := ! repo . IsPrivate && repo . Owner . Visibility == api . VisibleTypePublic
if ! isPublic && isExist {
if err = util . Remove ( daemonExportFile ) ; err != nil {
log . Error ( "Failed to remove %s: %v" , daemonExportFile , err )
}
} else if isPublic && ! isExist {
if f , err := os . Create ( daemonExportFile ) ; err != nil {
log . Error ( "Failed to create %s: %v" , daemonExportFile , err )
} else {
f . Close ( )
}
}
return nil
}
2016-07-24 14:32:46 +08:00
func countRepositories ( userID int64 , private bool ) int64 {
2021-09-23 23:45:36 +08:00
sess := db . GetEngine ( db . DefaultContext ) . Where ( "id > 0" )
2015-09-04 17:54:22 +08:00
2016-07-24 14:32:46 +08:00
if userID > 0 {
sess . And ( "owner_id = ?" , userID )
}
if ! private {
sess . And ( "is_private=?" , false )
2015-09-04 17:54:22 +08:00
}
2016-02-11 06:30:24 +08:00
count , err := sess . Count ( new ( Repository ) )
if err != nil {
2019-04-02 15:48:31 +08:00
log . Error ( "countRepositories: %v" , err )
2016-02-11 06:30:24 +08:00
}
2015-09-04 17:54:22 +08:00
return count
}
2014-07-07 16:15:08 +08:00
// CountRepositories returns number of repositories.
2016-07-24 14:32:46 +08:00
// Argument private only takes effect when it is false,
// set it true to count all repositories.
func CountRepositories ( private bool ) int64 {
return countRepositories ( - 1 , private )
2015-09-04 17:54:22 +08:00
}
2016-07-24 14:32:46 +08:00
// CountUserRepositories returns number of repositories user owns.
// Argument private only takes effect when it is false,
// set it true to count all repositories.
func CountUserRepositories ( userID int64 , private bool ) int64 {
return countRepositories ( userID , private )
2014-07-07 16:15:08 +08:00
}
2015-09-26 07:07:21 +08:00
2014-04-13 08:35:35 +08:00
// RepoPath returns repository path by given user and repository name.
2014-03-21 04:04:56 +08:00
func RepoPath ( userName , repoName string ) string {
2014-03-30 10:13:02 +08:00
return filepath . Join ( UserPath ( userName ) , strings . ToLower ( repoName ) + ".git" )
2014-03-21 04:04:56 +08:00
}
2020-01-12 20:11:17 +08:00
// IncrementRepoForkNum increment repository fork number
2021-09-23 23:45:36 +08:00
func IncrementRepoForkNum ( ctx context . Context , repoID int64 ) error {
_ , err := db . GetEngine ( ctx ) . Exec ( "UPDATE `repository` SET num_forks=num_forks+1 WHERE id=?" , repoID )
2020-01-12 20:11:17 +08:00
return err
}
2021-09-15 01:07:08 +08:00
// DecrementRepoForkNum decrement repository fork number
2021-09-23 23:45:36 +08:00
func DecrementRepoForkNum ( ctx context . Context , repoID int64 ) error {
_ , err := db . GetEngine ( ctx ) . Exec ( "UPDATE `repository` SET num_forks=num_forks-1 WHERE id=?" , repoID )
2021-09-15 01:07:08 +08:00
return err
}
2014-04-04 03:50:55 +08:00
// ChangeRepositoryName changes all corresponding setting from old repository name to new one.
2019-11-15 16:06:11 +08:00
func ChangeRepositoryName ( doer * User , repo * Repository , newRepoName string ) ( err error ) {
2019-12-06 12:00:50 +08:00
oldRepoName := repo . Name
2014-12-12 14:29:36 +08:00
newRepoName = strings . ToLower ( newRepoName )
2016-07-23 18:58:18 +08:00
if err = IsUsableRepoName ( newRepoName ) ; err != nil {
2015-03-27 05:11:47 +08:00
return err
}
2019-11-15 16:06:11 +08:00
if err := repo . GetOwner ( ) ; err != nil {
return err
2014-08-24 21:09:05 +08:00
}
2019-11-15 16:06:11 +08:00
has , err := IsRepositoryExist ( repo . Owner , newRepoName )
2016-02-06 03:11:53 +08:00
if err != nil {
2019-11-15 16:06:11 +08:00
return fmt . Errorf ( "IsRepositoryExist: %v" , err )
} else if has {
return ErrRepoAlreadyExist { repo . Owner . Name , newRepoName }
2016-02-06 03:11:53 +08:00
}
2019-11-15 16:06:11 +08:00
newRepoPath := RepoPath ( repo . Owner . Name , newRepoName )
2021-07-15 23:46:07 +08:00
if err = util . Rename ( repo . RepoPath ( ) , newRepoPath ) ; err != nil {
2015-12-01 09:45:55 +08:00
return fmt . Errorf ( "rename repository directory: %v" , err )
}
2015-12-03 14:59:32 +08:00
2016-02-06 03:11:53 +08:00
wikiPath := repo . WikiPath ( )
2020-11-28 10:42:08 +08:00
isExist , err := util . IsExist ( wikiPath )
if err != nil {
log . Error ( "Unable to check if %s exists. Error: %v" , wikiPath , err )
return err
}
if isExist {
2021-07-15 23:46:07 +08:00
if err = util . Rename ( wikiPath , WikiPath ( repo . Owner . Name , newRepoName ) ) ; err != nil {
2015-12-03 15:08:25 +08:00
return fmt . Errorf ( "rename repository wiki: %v" , err )
}
2015-12-03 14:59:32 +08:00
}
2015-12-03 15:08:25 +08:00
2021-11-21 23:41:00 +08:00
ctx , committer , err := db . TxContext ( )
if err != nil {
return err
2019-03-02 21:07:19 +08:00
}
2021-11-21 23:41:00 +08:00
defer committer . Close ( )
2019-03-02 21:07:19 +08:00
2021-11-21 23:41:00 +08:00
if err := newRepoRedirect ( db . GetEngine ( ctx ) , repo . Owner . ID , repo . ID , oldRepoName , newRepoName ) ; err != nil {
2019-12-06 12:00:50 +08:00
return err
}
2021-11-21 23:41:00 +08:00
return committer . Commit ( )
2014-04-04 03:50:55 +08:00
}
2021-09-19 19:49:59 +08:00
func getRepositoriesByForkID ( e db . Engine , forkID int64 ) ( [ ] * Repository , error ) {
2015-09-01 23:43:53 +08:00
repos := make ( [ ] * Repository , 0 , 10 )
2016-11-10 23:16:32 +08:00
return repos , e .
Where ( "fork_id=?" , forkID ) .
Find ( & repos )
2015-09-01 23:43:53 +08:00
}
// GetRepositoriesByForkID returns all repositories with given fork ID.
func GetRepositoriesByForkID ( forkID int64 ) ( [ ] * Repository , error ) {
2021-09-23 23:45:36 +08:00
return getRepositoriesByForkID ( db . GetEngine ( db . DefaultContext ) , forkID )
2015-09-01 23:43:53 +08:00
}
2021-09-19 19:49:59 +08:00
func updateRepository ( e db . Engine , repo * Repository , visibilityChanged bool ) ( err error ) {
2014-04-04 03:50:55 +08:00
repo . LowerName = strings . ToLower ( repo . Name )
2020-07-08 05:35:52 +08:00
if utf8 . RuneCountInString ( repo . Description ) > 255 {
repo . Description = string ( [ ] rune ( repo . Description ) [ : 255 ] )
2014-03-23 04:00:46 +08:00
}
2020-07-08 05:35:52 +08:00
if utf8 . RuneCountInString ( repo . Website ) > 255 {
repo . Website = string ( [ ] rune ( repo . Website ) [ : 255 ] )
2014-03-23 04:00:46 +08:00
}
2015-03-16 16:52:11 +08:00
2017-10-05 12:43:04 +08:00
if _ , err = e . ID ( repo . ID ) . AllCols ( ) . Update ( repo ) ; err != nil {
2015-03-16 16:52:11 +08:00
return fmt . Errorf ( "update: %v" , err )
}
2020-11-07 12:44:08 +08:00
if err = repo . updateSize ( e ) ; err != nil {
log . Error ( "Failed to update size for repository: %v" , err )
}
2015-03-16 16:52:11 +08:00
if visibilityChanged {
if err = repo . getOwner ( e ) ; err != nil {
return fmt . Errorf ( "getOwner: %v" , err )
}
2015-10-09 10:38:42 +08:00
if repo . Owner . IsOrganization ( ) {
2017-01-05 08:50:34 +08:00
// Organization repository need to recalculate access table when visibility is changed.
2015-10-09 10:38:42 +08:00
if err = repo . recalculateTeamAccesses ( e , 0 ) ; err != nil {
return fmt . Errorf ( "recalculateTeamAccesses: %v" , err )
}
2015-03-16 16:52:11 +08:00
}
2015-09-01 23:43:53 +08:00
2017-02-11 18:57:57 +08:00
// If repo has become private, we need to set its actions to private.
if repo . IsPrivate {
_ , err = e . Where ( "repo_id = ?" , repo . ID ) . Cols ( "is_private" ) . Update ( & Action {
IsPrivate : true ,
} )
if err != nil {
return err
}
}
2016-08-11 11:08:09 +08:00
// Create/Remove git-daemon-export-ok for git-daemon...
2021-10-14 03:47:02 +08:00
if err := repo . CheckDaemonExportOK ( db . WithEngine ( db . DefaultContext , e ) ) ; err != nil {
2020-11-28 10:42:08 +08:00
return err
}
2016-08-11 11:08:09 +08:00
2015-09-01 23:43:53 +08:00
forkRepos , err := getRepositoriesByForkID ( e , repo . ID )
if err != nil {
return fmt . Errorf ( "getRepositoriesByForkID: %v" , err )
}
for i := range forkRepos {
2020-06-07 08:45:12 +08:00
forkRepos [ i ] . IsPrivate = repo . IsPrivate || repo . Owner . Visibility == api . VisibleTypePrivate
2015-09-01 23:43:53 +08:00
if err = updateRepository ( e , forkRepos [ i ] , true ) ; err != nil {
return fmt . Errorf ( "updateRepository[%d]: %v" , forkRepos [ i ] . ID , err )
}
}
2015-03-16 16:52:11 +08:00
}
return nil
2014-03-22 16:44:57 +08:00
}
2020-01-12 20:11:17 +08:00
// UpdateRepositoryCtx updates a repository with db context
2021-09-23 23:45:36 +08:00
func UpdateRepositoryCtx ( ctx context . Context , repo * Repository , visibilityChanged bool ) error {
return updateRepository ( db . GetEngine ( ctx ) , repo , visibilityChanged )
2020-01-12 20:11:17 +08:00
}
2016-11-29 01:27:55 +08:00
// UpdateRepository updates a repository
2015-03-16 16:52:11 +08:00
func UpdateRepository ( repo * Repository , visibilityChanged bool ) ( err error ) {
2021-11-21 23:41:00 +08:00
ctx , committer , err := db . TxContext ( )
if err != nil {
2015-03-16 16:52:11 +08:00
return err
}
2021-11-21 23:41:00 +08:00
defer committer . Close ( )
2015-03-16 16:52:11 +08:00
2021-11-21 23:41:00 +08:00
if err = updateRepository ( db . GetEngine ( ctx ) , repo , visibilityChanged ) ; err != nil {
2015-03-16 16:52:11 +08:00
return fmt . Errorf ( "updateRepository: %v" , err )
}
2021-11-21 23:41:00 +08:00
return committer . Commit ( )
2015-02-13 13:58:46 +08:00
}
2021-06-02 20:03:59 +08:00
// UpdateRepositoryOwnerNames updates repository owner_names (this should only be used when the ownerName has changed case)
func UpdateRepositoryOwnerNames ( ownerID int64 , ownerName string ) error {
if ownerID == 0 {
return nil
}
2021-11-21 23:41:00 +08:00
ctx , committer , err := db . TxContext ( )
if err != nil {
2021-06-02 20:03:59 +08:00
return err
}
2021-11-21 23:41:00 +08:00
defer committer . Close ( )
2021-06-02 20:03:59 +08:00
2021-11-21 23:41:00 +08:00
if _ , err := db . GetEngine ( ctx ) . Where ( "owner_id = ?" , ownerID ) . Cols ( "owner_name" ) . Update ( & Repository {
2021-06-02 20:03:59 +08:00
OwnerName : ownerName ,
} ) ; err != nil {
return err
}
2021-11-21 23:41:00 +08:00
return committer . Commit ( )
2021-06-02 20:03:59 +08:00
}
2019-10-01 21:40:17 +08:00
// UpdateRepositoryUpdatedTime updates a repository's updated time
func UpdateRepositoryUpdatedTime ( repoID int64 , updateTime time . Time ) error {
2021-09-23 23:45:36 +08:00
_ , err := db . GetEngine ( db . DefaultContext ) . Exec ( "UPDATE repository SET updated_unix = ? WHERE id = ?" , updateTime . Unix ( ) , repoID )
2019-10-01 21:40:17 +08:00
return err
}
2017-02-04 23:53:46 +08:00
// UpdateRepositoryUnits updates a repository's units
2021-11-10 03:57:58 +08:00
func UpdateRepositoryUnits ( repo * Repository , units [ ] RepoUnit , deleteUnitTypes [ ] unit . Type ) ( err error ) {
2021-11-21 23:41:00 +08:00
ctx , committer , err := db . TxContext ( )
if err != nil {
2017-02-04 23:53:46 +08:00
return err
}
2021-11-21 23:41:00 +08:00
defer committer . Close ( )
2017-02-04 23:53:46 +08:00
2020-01-17 15:34:37 +08:00
// Delete existing settings of units before adding again
for _ , u := range units {
deleteUnitTypes = append ( deleteUnitTypes , u . Type )
}
2021-11-21 23:41:00 +08:00
if _ , err = db . GetEngine ( ctx ) . Where ( "repo_id = ?" , repo . ID ) . In ( "type" , deleteUnitTypes ) . Delete ( new ( RepoUnit ) ) ; err != nil {
2017-02-04 23:53:46 +08:00
return err
}
2020-03-22 23:12:55 +08:00
if len ( units ) > 0 {
2021-11-21 23:41:00 +08:00
if err = db . Insert ( ctx , units ) ; err != nil {
2020-03-22 23:12:55 +08:00
return err
}
2017-02-04 23:53:46 +08:00
}
2021-11-21 23:41:00 +08:00
return committer . Commit ( )
2017-02-04 23:53:46 +08:00
}
2014-12-07 09:22:48 +08:00
// DeleteRepository deletes a repository for a user or organization.
2021-01-19 04:00:50 +08:00
// make sure if you call this func to close open sessions (sqlite will otherwise get a deadlock)
2017-09-03 16:20:24 +08:00
func DeleteRepository ( doer * User , uid , repoID int64 ) error {
2021-11-21 23:41:00 +08:00
ctx , committer , err := db . TxContext ( )
if err != nil {
2021-01-19 04:00:50 +08:00
return err
}
2021-11-21 23:41:00 +08:00
defer committer . Close ( )
sess := db . GetEngine ( ctx )
2021-01-19 04:00:50 +08:00
2014-08-27 16:39:36 +08:00
// In case is a organization.
2021-01-19 04:00:50 +08:00
org , err := getUserByID ( sess , uid )
2014-08-27 16:39:36 +08:00
if err != nil {
return err
}
2021-01-19 04:00:50 +08:00
repo := & Repository { OwnerID : uid }
has , err := sess . ID ( repoID ) . Get ( repo )
2017-03-01 12:05:45 +08:00
if err != nil {
return err
} else if ! has {
2017-12-02 15:34:39 +08:00
return ErrRepoNotExist { repoID , uid , "" , "" }
2017-03-01 12:05:45 +08:00
}
2019-02-04 07:56:53 +08:00
// Delete Deploy Keys
2021-08-12 20:43:08 +08:00
deployKeys , err := listDeployKeys ( sess , & ListDeployKeysOptions { RepoID : repoID } )
2019-02-04 07:56:53 +08:00
if err != nil {
return fmt . Errorf ( "listDeployKeys: %v" , err )
}
for _ , dKey := range deployKeys {
if err := deleteDeployKey ( sess , doer , dKey . ID ) ; err != nil {
return fmt . Errorf ( "deleteDeployKeys: %v" , err )
}
}
2017-10-05 12:43:04 +08:00
if cnt , err := sess . ID ( repoID ) . Delete ( & Repository { } ) ; err != nil {
2017-03-01 12:05:45 +08:00
return err
} else if cnt != 1 {
2017-12-02 15:34:39 +08:00
return ErrRepoNotExist { repoID , uid , "" , "" }
2017-03-01 12:05:45 +08:00
}
2014-08-27 16:39:36 +08:00
if org . IsOrganization ( ) {
2021-11-19 19:41:40 +08:00
teams , err := OrgFromUser ( org ) . loadTeams ( sess )
if err != nil {
return err
}
for _ , t := range teams {
2015-02-23 15:15:53 +08:00
if ! t . hasRepository ( sess , repoID ) {
2014-08-27 16:39:36 +08:00
continue
2015-03-01 10:44:09 +08:00
} else if err = t . removeRepository ( sess , repo , false ) ; err != nil {
2014-08-27 16:39:36 +08:00
return err
}
}
}
2021-11-19 21:39:57 +08:00
attachments := make ( [ ] * repo_model . Attachment , 0 , 20 )
2019-12-12 13:31:05 +08:00
if err = sess . Join ( "INNER" , "`release`" , "`release`.id = `attachment`.release_id" ) .
Where ( "`release`.repo_id = ?" , repoID ) .
Find ( & attachments ) ; err != nil {
return err
}
releaseAttachments := make ( [ ] string , 0 , len ( attachments ) )
for i := 0 ; i < len ( attachments ) ; i ++ {
2020-08-18 12:23:45 +08:00
releaseAttachments = append ( releaseAttachments , attachments [ i ] . RelativePath ( ) )
2019-12-12 13:31:05 +08:00
}
2021-03-20 03:01:24 +08:00
if _ , err := sess . Exec ( "UPDATE `user` SET num_stars=num_stars-1 WHERE id IN (SELECT `uid` FROM `star` WHERE repo_id = ?)" , repo . ID ) ; err != nil {
2020-07-08 03:16:34 +08:00
return err
}
2021-03-20 03:01:24 +08:00
if err := deleteBeans ( sess ,
2015-12-01 09:45:55 +08:00
& Access { RepoID : repo . ID } ,
& Action { RepoID : repo . ID } ,
& Collaboration { RepoID : repoID } ,
2021-05-01 03:10:39 +08:00
& Comment { RefRepoID : repoID } ,
& CommitStatus { RepoID : repoID } ,
& DeletedBranch { RepoID : repoID } ,
2021-11-10 13:13:16 +08:00
& webhook . HookTask { RepoID : repoID } ,
2021-05-01 03:10:39 +08:00
& LFSLock { RepoID : repoID } ,
& LanguageStat { RepoID : repoID } ,
& Milestone { RepoID : repoID } ,
& Mirror { RepoID : repoID } ,
2018-12-11 04:01:01 +08:00
& Notification { RepoID : repoID } ,
2021-05-01 03:10:39 +08:00
& ProtectedBranch { RepoID : repoID } ,
2021-06-25 22:28:55 +08:00
& ProtectedTag { RepoID : repoID } ,
2021-05-01 03:10:39 +08:00
& PullRequest { BaseRepoID : repoID } ,
2021-06-15 01:20:43 +08:00
& PushMirror { RepoID : repoID } ,
2021-05-01 03:10:39 +08:00
& Release { RepoID : repoID } ,
2019-08-04 14:53:17 +08:00
& RepoIndexerStatus { RepoID : repoID } ,
2021-05-01 03:10:39 +08:00
& RepoRedirect { RedirectRepoID : repoID } ,
& RepoUnit { RepoID : repoID } ,
& Star { RepoID : repoID } ,
2019-10-13 21:23:14 +08:00
& Task { RepoID : repoID } ,
2021-05-01 03:10:39 +08:00
& Watch { RepoID : repoID } ,
2021-11-10 13:13:16 +08:00
& webhook . Webhook { RepoID : repoID } ,
2015-12-01 09:45:55 +08:00
) ; err != nil {
return fmt . Errorf ( "deleteBeans: %v" , err )
2014-05-15 01:04:57 +08:00
}
2021-03-20 03:01:24 +08:00
// Delete Labels and related objects
if err := deleteLabelsByRepoID ( sess , repoID ) ; err != nil {
return err
}
2020-05-29 21:24:15 +08:00
// Delete Issues and related objects
var attachmentPaths [ ] string
if attachmentPaths , err = deleteIssuesByRepoID ( sess , repoID ) ; err != nil {
2018-11-30 20:59:12 +08:00
return err
2014-05-14 21:23:33 +08:00
}
2014-10-19 13:35:24 +08:00
2021-06-14 10:22:55 +08:00
// Delete issue index
2021-09-19 19:49:59 +08:00
if err := db . DeleteResouceIndex ( sess , "issue_index" , repoID ) ; err != nil {
2021-06-14 10:22:55 +08:00
return err
}
2014-10-14 03:23:30 +08:00
if repo . IsFork {
2021-03-20 03:01:24 +08:00
if _ , err := sess . Exec ( "UPDATE `repository` SET num_forks=num_forks-1 WHERE id=?" , repo . ForkID ) ; err != nil {
2015-09-01 23:43:53 +08:00
return fmt . Errorf ( "decrease fork count: %v" , err )
2014-10-19 13:35:24 +08:00
}
2014-10-14 03:23:30 +08:00
}
2014-04-13 08:35:35 +08:00
2021-03-20 03:01:24 +08:00
if _ , err := sess . Exec ( "UPDATE `user` SET num_repos=num_repos-1 WHERE id=?" , uid ) ; err != nil {
2014-04-13 08:35:35 +08:00
return err
}
2014-10-09 06:29:18 +08:00
2020-01-31 14:57:19 +08:00
if len ( repo . Topics ) > 0 {
2021-03-20 03:01:24 +08:00
if err := removeTopicsFromRepo ( sess , repo . ID ) ; err != nil {
2020-01-31 14:57:19 +08:00
return err
}
}
2020-08-17 11:07:38 +08:00
projects , _ , err := getProjects ( sess , ProjectSearchOptions {
RepoID : repoID ,
} )
if err != nil {
return fmt . Errorf ( "get projects: %v" , err )
}
for i := range projects {
if err := deleteProjectByID ( sess , projects [ i ] . ID ) ; err != nil {
return fmt . Errorf ( "delete project [%d]: %v" , projects [ i ] . ID , err )
}
}
2016-12-26 09:16:37 +08:00
// Remove LFS objects
var lfsObjects [ ] * LFSMetaObject
if err = sess . Where ( "repository_id=?" , repoID ) . Find ( & lfsObjects ) ; err != nil {
return err
}
2021-09-08 23:19:30 +08:00
var lfsPaths = make ( [ ] string , 0 , len ( lfsObjects ) )
2016-12-26 09:16:37 +08:00
for _ , v := range lfsObjects {
2021-04-09 06:25:57 +08:00
count , err := sess . Count ( & LFSMetaObject { Pointer : lfs . Pointer { Oid : v . Oid } } )
2016-12-26 09:16:37 +08:00
if err != nil {
return err
}
if count > 1 {
continue
}
2021-09-08 23:19:30 +08:00
lfsPaths = append ( lfsPaths , v . RelativePath ( ) )
2016-12-26 09:16:37 +08:00
}
if _ , err := sess . Delete ( & LFSMetaObject { RepositoryID : repoID } ) ; err != nil {
return err
}
2021-06-24 05:12:38 +08:00
// Remove archives
var archives [ ] * RepoArchiver
if err = sess . Where ( "repo_id=?" , repoID ) . Find ( & archives ) ; err != nil {
return err
}
2021-09-08 23:19:30 +08:00
var archivePaths = make ( [ ] string , 0 , len ( archives ) )
2021-06-24 05:12:38 +08:00
for _ , v := range archives {
v . Repo = repo
p , _ := v . RelativePath ( )
2021-09-08 23:19:30 +08:00
archivePaths = append ( archivePaths , p )
2021-06-24 05:12:38 +08:00
}
if _ , err := sess . Delete ( & RepoArchiver { RepoID : repoID } ) ; err != nil {
return err
}
2015-09-01 23:43:53 +08:00
if repo . NumForks > 0 {
2017-01-28 00:11:41 +08:00
if _ , err = sess . Exec ( "UPDATE `repository` SET fork_id=0,is_fork=? WHERE fork_id=?" , false , repo . ID ) ; err != nil {
2019-04-02 15:48:31 +08:00
log . Error ( "reset 'fork_id' and 'is_fork': %v" , err )
2016-07-09 13:42:05 +08:00
}
2015-09-01 23:43:53 +08:00
}
2021-09-08 23:19:30 +08:00
// Get all attachments with both issue_id and release_id are zero
2021-11-19 21:39:57 +08:00
var newAttachments [ ] * repo_model . Attachment
2021-09-08 23:19:30 +08:00
if err := sess . Where ( builder . Eq {
"repo_id" : repo . ID ,
"issue_id" : 0 ,
"release_id" : 0 ,
} ) . Find ( & newAttachments ) ; err != nil {
return err
}
var newAttachmentPaths = make ( [ ] string , 0 , len ( newAttachments ) )
for _ , attach := range newAttachments {
newAttachmentPaths = append ( newAttachmentPaths , attach . RelativePath ( ) )
}
2021-11-19 21:39:57 +08:00
if _ , err := sess . Where ( "repo_id=?" , repo . ID ) . Delete ( new ( repo_model . Attachment ) ) ; err != nil {
2021-09-08 23:19:30 +08:00
return err
}
2021-11-21 23:41:00 +08:00
if err = committer . Commit ( ) ; err != nil {
2021-01-19 04:00:50 +08:00
return err
2017-01-28 00:11:41 +08:00
}
2021-11-21 23:41:00 +08:00
committer . Close ( )
2019-12-12 13:31:05 +08:00
// We should always delete the files after the database transaction succeed. If
2021-07-08 19:38:13 +08:00
// we delete the file but the database rollback, the repository will be broken.
2019-12-12 13:31:05 +08:00
2021-09-08 23:19:30 +08:00
// Remove repository files.
repoPath := repo . RepoPath ( )
2021-11-19 01:42:27 +08:00
admin_model . RemoveAllWithNotice ( db . DefaultContext , "Delete repository files" , repoPath )
2021-09-08 23:19:30 +08:00
// Remove wiki files
if repo . HasWiki ( ) {
2021-11-19 01:42:27 +08:00
admin_model . RemoveAllWithNotice ( db . DefaultContext , "Delete repository wiki" , repo . WikiPath ( ) )
2021-09-08 23:19:30 +08:00
}
// Remove archives
for i := range archivePaths {
2021-11-19 01:42:27 +08:00
admin_model . RemoveStorageWithNotice ( db . DefaultContext , storage . RepoArchives , "Delete repo archive file" , archivePaths [ i ] )
2021-09-08 23:19:30 +08:00
}
// Remove lfs objects
for i := range lfsPaths {
2021-11-19 01:42:27 +08:00
admin_model . RemoveStorageWithNotice ( db . DefaultContext , storage . LFS , "Delete orphaned LFS file" , lfsPaths [ i ] )
2021-09-08 23:19:30 +08:00
}
2019-12-12 13:31:05 +08:00
// Remove issue attachment files.
for i := range attachmentPaths {
2021-11-19 01:42:27 +08:00
admin_model . RemoveStorageWithNotice ( db . DefaultContext , storage . Attachments , "Delete issue attachment" , attachmentPaths [ i ] )
2019-12-12 13:31:05 +08:00
}
// Remove release attachment files.
for i := range releaseAttachments {
2021-11-19 01:42:27 +08:00
admin_model . RemoveStorageWithNotice ( db . DefaultContext , storage . Attachments , "Delete release attachment" , releaseAttachments [ i ] )
2019-12-12 13:31:05 +08:00
}
2021-09-08 23:19:30 +08:00
// Remove attachment with no issue_id and release_id.
for i := range newAttachmentPaths {
2021-11-19 01:42:27 +08:00
admin_model . RemoveStorageWithNotice ( db . DefaultContext , storage . Attachments , "Delete issue attachment" , attachmentPaths [ i ] )
2021-09-08 23:19:30 +08:00
}
2019-05-30 10:22:26 +08:00
if len ( repo . Avatar ) > 0 {
2020-10-14 21:07:51 +08:00
if err := storage . RepoAvatars . Delete ( repo . CustomAvatarRelativePath ( ) ) ; err != nil {
return fmt . Errorf ( "Failed to remove %s: %v" , repo . Avatar , err )
2019-05-30 10:22:26 +08:00
}
}
2015-09-01 23:43:53 +08:00
return nil
2014-03-21 04:04:56 +08:00
}
2017-12-02 15:34:39 +08:00
// GetRepositoryByOwnerAndName returns the repository by given ownername and reponame.
func GetRepositoryByOwnerAndName ( ownerName , repoName string ) ( * Repository , error ) {
2021-09-23 23:45:36 +08:00
return getRepositoryByOwnerAndName ( db . GetEngine ( db . DefaultContext ) , ownerName , repoName )
2019-09-20 13:45:38 +08:00
}
2021-09-19 19:49:59 +08:00
func getRepositoryByOwnerAndName ( e db . Engine , ownerName , repoName string ) ( * Repository , error ) {
2017-12-02 15:34:39 +08:00
var repo Repository
2019-09-20 13:45:38 +08:00
has , err := e . Table ( "repository" ) . Select ( "repository.*" ) .
2018-07-20 10:10:17 +08:00
Join ( "INNER" , "`user`" , "`user`.id = repository.owner_id" ) .
2017-12-02 15:34:39 +08:00
Where ( "repository.lower_name = ?" , strings . ToLower ( repoName ) ) .
And ( "`user`.lower_name = ?" , strings . ToLower ( ownerName ) ) .
Get ( & repo )
2014-07-23 19:48:06 +08:00
if err != nil {
return nil , err
2017-12-02 15:34:39 +08:00
} else if ! has {
return nil , ErrRepoNotExist { 0 , 0 , ownerName , repoName }
2014-07-23 19:48:06 +08:00
}
2017-12-02 15:34:39 +08:00
return & repo , nil
2014-07-23 19:48:06 +08:00
}
2014-03-18 02:03:58 +08:00
// GetRepositoryByName returns the repository by given name under user if exists.
2016-08-17 14:06:38 +08:00
func GetRepositoryByName ( ownerID int64 , name string ) ( * Repository , error ) {
2014-03-13 10:27:11 +08:00
repo := & Repository {
2016-08-17 14:06:38 +08:00
OwnerID : ownerID ,
LowerName : strings . ToLower ( name ) ,
2014-03-13 10:27:11 +08:00
}
2021-09-23 23:45:36 +08:00
has , err := db . GetEngine ( db . DefaultContext ) . Get ( repo )
2014-03-13 10:27:11 +08:00
if err != nil {
return nil , err
} else if ! has {
2017-12-02 15:34:39 +08:00
return nil , ErrRepoNotExist { 0 , ownerID , "" , name }
2014-03-13 10:27:11 +08:00
}
return repo , err
}
2021-09-19 19:49:59 +08:00
func getRepositoryByID ( e db . Engine , id int64 ) ( * Repository , error ) {
2015-03-16 16:04:27 +08:00
repo := new ( Repository )
2017-10-05 12:43:04 +08:00
has , err := e . ID ( id ) . Get ( repo )
2014-03-13 10:27:11 +08:00
if err != nil {
return nil , err
} else if ! has {
2017-12-02 15:34:39 +08:00
return nil , ErrRepoNotExist { id , 0 , "" , "" }
2014-03-13 10:27:11 +08:00
}
2014-05-06 09:36:08 +08:00
return repo , nil
2014-03-13 10:27:11 +08:00
}
2015-08-08 22:43:14 +08:00
// GetRepositoryByID returns the repository by given id if exists.
func GetRepositoryByID ( id int64 ) ( * Repository , error ) {
2021-09-23 23:45:36 +08:00
return getRepositoryByID ( db . GetEngine ( db . DefaultContext ) , id )
2015-02-13 13:58:46 +08:00
}
2020-01-12 20:11:17 +08:00
// GetRepositoryByIDCtx returns the repository by given id if exists.
2021-09-23 23:45:36 +08:00
func GetRepositoryByIDCtx ( ctx context . Context , id int64 ) ( * Repository , error ) {
return getRepositoryByID ( db . GetEngine ( ctx ) , id )
2020-01-12 20:11:17 +08:00
}
2018-03-16 22:04:33 +08:00
// GetRepositoriesMapByIDs returns the repositories by given id slice.
func GetRepositoriesMapByIDs ( ids [ ] int64 ) ( map [ int64 ] * Repository , error ) {
2021-03-15 02:52:12 +08:00
repos := make ( map [ int64 ] * Repository , len ( ids ) )
2021-09-23 23:45:36 +08:00
return repos , db . GetEngine ( db . DefaultContext ) . In ( "id" , ids ) . Find ( & repos )
2018-03-16 22:04:33 +08:00
}
2016-07-24 14:32:46 +08:00
// GetUserRepositories returns a list of repositories of given user.
2020-06-13 19:35:59 +08:00
func GetUserRepositories ( opts * SearchRepoOptions ) ( [ ] * Repository , int64 , error ) {
2020-01-25 03:00:29 +08:00
if len ( opts . OrderBy ) == 0 {
opts . OrderBy = "updated_unix DESC"
2017-02-04 20:20:20 +08:00
}
2021-03-15 02:52:12 +08:00
cond := builder . NewCond ( )
2020-06-13 19:35:59 +08:00
cond = cond . And ( builder . Eq { "owner_id" : opts . Actor . ID } )
2020-01-25 03:00:29 +08:00
if ! opts . Private {
2020-06-13 19:35:59 +08:00
cond = cond . And ( builder . Eq { "is_private" : false } )
2014-04-14 06:12:07 +08:00
}
2020-09-25 12:09:23 +08:00
if opts . LowerNames != nil && len ( opts . LowerNames ) > 0 {
cond = cond . And ( builder . In ( "lower_name" , opts . LowerNames ) )
}
2021-11-21 23:41:00 +08:00
sess := db . GetEngine ( db . DefaultContext )
2020-06-13 19:35:59 +08:00
count , err := sess . Where ( cond ) . Count ( new ( Repository ) )
if err != nil {
return nil , 0 , fmt . Errorf ( "Count: %v" , err )
}
2016-07-24 14:32:46 +08:00
2021-11-21 23:41:00 +08:00
sess = sess . Where ( cond ) . OrderBy ( opts . OrderBy . String ( ) )
2020-01-25 03:00:29 +08:00
repos := make ( [ ] * Repository , 0 , opts . PageSize )
2021-09-24 19:32:56 +08:00
return repos , count , db . SetSessionPagination ( sess , opts ) . Find ( & repos )
2016-07-24 14:32:46 +08:00
}
2016-11-29 01:27:55 +08:00
// GetUserMirrorRepositories returns a list of mirror repositories of given user.
2016-07-24 14:32:46 +08:00
func GetUserMirrorRepositories ( userID int64 ) ( [ ] * Repository , error ) {
repos := make ( [ ] * Repository , 0 , 10 )
2021-09-23 23:45:36 +08:00
return repos , db . GetEngine ( db . DefaultContext ) .
2016-11-10 23:16:32 +08:00
Where ( "owner_id = ?" , userID ) .
And ( "is_mirror = ?" , true ) .
Find ( & repos )
2014-02-14 22:20:57 +08:00
}
2021-11-19 19:41:40 +08:00
func getRepositoryCount ( e db . Engine , ownerID int64 ) ( int64 , error ) {
return e . Count ( & Repository { OwnerID : ownerID } )
2015-09-06 20:54:08 +08:00
}
2021-09-19 19:49:59 +08:00
func getPublicRepositoryCount ( e db . Engine , u * User ) ( int64 , error ) {
2017-02-27 10:16:35 +08:00
return e . Where ( "is_private = ?" , false ) . Count ( & Repository { OwnerID : u . ID } )
2017-02-06 23:18:36 +08:00
}
2021-09-19 19:49:59 +08:00
func getPrivateRepositoryCount ( e db . Engine , u * User ) ( int64 , error ) {
2017-02-27 10:16:35 +08:00
return e . Where ( "is_private = ?" , true ) . Count ( & Repository { OwnerID : u . ID } )
2017-02-06 23:18:36 +08:00
}
2014-04-14 16:11:33 +08:00
// GetRepositoryCount returns the total number of repositories of user.
2021-11-19 19:41:40 +08:00
func GetRepositoryCount ( ctx context . Context , ownerID int64 ) ( int64 , error ) {
return getRepositoryCount ( db . GetEngine ( ctx ) , ownerID )
2014-03-11 14:17:05 +08:00
}
2017-02-06 23:18:36 +08:00
// GetPublicRepositoryCount returns the total number of public repositories of user.
func GetPublicRepositoryCount ( u * User ) ( int64 , error ) {
2021-09-23 23:45:36 +08:00
return getPublicRepositoryCount ( db . GetEngine ( db . DefaultContext ) , u )
2017-02-06 23:18:36 +08:00
}
// GetPrivateRepositoryCount returns the total number of private repositories of user.
func GetPrivateRepositoryCount ( u * User ) ( int64 , error ) {
2021-09-23 23:45:36 +08:00
return getPrivateRepositoryCount ( db . GetEngine ( db . DefaultContext ) , u )
2017-02-06 23:18:36 +08:00
}
2017-02-11 12:00:46 +08:00
// DeleteOldRepositoryArchives deletes old repository archives.
2020-05-17 07:31:38 +08:00
func DeleteOldRepositoryArchives ( ctx context . Context , olderThan time . Duration ) error {
2017-02-11 12:00:46 +08:00
log . Trace ( "Doing: ArchiveCleanup" )
2021-06-24 05:12:38 +08:00
for {
var archivers [ ] RepoArchiver
2021-09-23 23:45:36 +08:00
err := db . GetEngine ( db . DefaultContext ) . Where ( "created_unix < ?" , time . Now ( ) . Add ( - olderThan ) . Unix ( ) ) .
2021-06-24 05:12:38 +08:00
Asc ( "created_unix" ) .
Limit ( 100 ) .
Find ( & archivers )
2017-02-11 12:00:46 +08:00
if err != nil {
2021-06-24 05:12:38 +08:00
log . Trace ( "Error: ArchiveClean: %v" , err )
2017-02-11 12:00:46 +08:00
return err
}
2021-06-24 05:12:38 +08:00
for _ , archiver := range archivers {
if err := deleteOldRepoArchiver ( ctx , & archiver ) ; err != nil {
return err
2017-02-11 12:00:46 +08:00
}
}
2021-06-24 05:12:38 +08:00
if len ( archivers ) < 100 {
break
}
2017-02-11 12:00:46 +08:00
}
2021-06-24 05:12:38 +08:00
log . Trace ( "Finished: ArchiveCleanup" )
return nil
}
var delRepoArchiver = new ( RepoArchiver )
func deleteOldRepoArchiver ( ctx context . Context , archiver * RepoArchiver ) error {
p , err := archiver . RelativePath ( )
if err != nil {
return err
}
2021-09-23 23:45:36 +08:00
_ , err = db . GetEngine ( db . DefaultContext ) . ID ( archiver . ID ) . Delete ( delRepoArchiver )
2021-06-24 05:12:38 +08:00
if err != nil {
return err
}
if err := storage . RepoArchives . Delete ( p ) ; err != nil {
log . Error ( "delete repo archive file failed: %v" , err )
}
2017-02-11 12:00:46 +08:00
return nil
}
2015-09-01 23:43:53 +08:00
type repoChecker struct {
querySQL , correctSQL string
desc string
}
2015-08-18 04:03:11 +08:00
2019-12-15 17:51:28 +08:00
func repoStatsCheck ( ctx context . Context , checker * repoChecker ) {
2021-09-23 23:45:36 +08:00
results , err := db . GetEngine ( db . DefaultContext ) . Query ( checker . querySQL )
2015-08-18 04:03:11 +08:00
if err != nil {
2019-04-02 15:48:31 +08:00
log . Error ( "Select %s: %v" , checker . desc , err )
2015-08-18 04:03:11 +08:00
return
}
2015-09-01 23:43:53 +08:00
for _ , result := range results {
2020-12-25 17:59:32 +08:00
id , _ := strconv . ParseInt ( string ( result [ "id" ] ) , 10 , 64 )
2019-12-15 17:51:28 +08:00
select {
case <- ctx . Done ( ) :
2020-05-17 07:31:38 +08:00
log . Warn ( "CheckRepoStats: Cancelled before checking %s for Repo[%d]" , checker . desc , id )
2019-12-15 17:51:28 +08:00
return
default :
}
2015-09-01 23:43:53 +08:00
log . Trace ( "Updating %s: %d" , checker . desc , id )
2021-09-23 23:45:36 +08:00
_ , err = db . GetEngine ( db . DefaultContext ) . Exec ( checker . correctSQL , id , id )
2015-08-18 04:03:11 +08:00
if err != nil {
2019-04-02 15:48:31 +08:00
log . Error ( "Update %s[%d]: %v" , checker . desc , id , err )
2015-08-18 04:03:11 +08:00
}
}
2015-09-01 23:43:53 +08:00
}
2015-08-18 04:03:11 +08:00
2016-11-29 01:27:55 +08:00
// CheckRepoStats checks the repository stats
2020-05-17 07:31:38 +08:00
func CheckRepoStats ( ctx context . Context ) error {
2015-09-01 23:43:53 +08:00
log . Trace ( "Doing: CheckRepoStats" )
2015-08-30 01:13:24 +08:00
2015-09-01 23:43:53 +08:00
checkers := [ ] * repoChecker {
// Repository.NumWatches
{
2019-11-10 17:22:19 +08:00
"SELECT repo.id FROM `repository` repo WHERE repo.num_watches!=(SELECT COUNT(*) FROM `watch` WHERE repo_id=repo.id AND mode<>2)" ,
"UPDATE `repository` SET num_watches=(SELECT COUNT(*) FROM `watch` WHERE repo_id=? AND mode<>2) WHERE id=?" ,
2015-09-01 23:43:53 +08:00
"repository count 'num_watches'" ,
} ,
// Repository.NumStars
{
"SELECT repo.id FROM `repository` repo WHERE repo.num_stars!=(SELECT COUNT(*) FROM `star` WHERE repo_id=repo.id)" ,
"UPDATE `repository` SET num_stars=(SELECT COUNT(*) FROM `star` WHERE repo_id=?) WHERE id=?" ,
"repository count 'num_stars'" ,
} ,
// Label.NumIssues
{
"SELECT label.id FROM `label` WHERE label.num_issues!=(SELECT COUNT(*) FROM `issue_label` WHERE label_id=label.id)" ,
"UPDATE `label` SET num_issues=(SELECT COUNT(*) FROM `issue_label` WHERE label_id=?) WHERE id=?" ,
"label count 'num_issues'" ,
} ,
// User.NumRepos
{
"SELECT `user`.id FROM `user` WHERE `user`.num_repos!=(SELECT COUNT(*) FROM `repository` WHERE owner_id=`user`.id)" ,
"UPDATE `user` SET num_repos=(SELECT COUNT(*) FROM `repository` WHERE owner_id=?) WHERE id=?" ,
"user count 'num_repos'" ,
} ,
2015-10-30 08:40:57 +08:00
// Issue.NumComments
{
"SELECT `issue`.id FROM `issue` WHERE `issue`.num_comments!=(SELECT COUNT(*) FROM `comment` WHERE issue_id=`issue`.id AND type=0)" ,
"UPDATE `issue` SET num_comments=(SELECT COUNT(*) FROM `comment` WHERE issue_id=? AND type=0) WHERE id=?" ,
"issue count 'num_comments'" ,
} ,
2015-09-01 23:43:53 +08:00
}
2020-05-17 07:31:38 +08:00
for _ , checker := range checkers {
2019-12-15 17:51:28 +08:00
select {
case <- ctx . Done ( ) :
2020-05-17 07:31:38 +08:00
log . Warn ( "CheckRepoStats: Cancelled before %s" , checker . desc )
2021-11-10 13:13:16 +08:00
return db . ErrCancelledf ( "before checking %s" , checker . desc )
2019-12-15 17:51:28 +08:00
default :
2020-05-17 07:31:38 +08:00
repoStatsCheck ( ctx , checker )
2019-12-15 17:51:28 +08:00
}
2015-09-01 23:43:53 +08:00
}
2016-05-28 09:23:39 +08:00
// ***** START: Repository.NumClosedIssues *****
desc := "repository count 'num_closed_issues'"
2021-09-23 23:45:36 +08:00
results , err := db . GetEngine ( db . DefaultContext ) . Query ( "SELECT repo.id FROM `repository` repo WHERE repo.num_closed_issues!=(SELECT COUNT(*) FROM `issue` WHERE repo_id=repo.id AND is_closed=? AND is_pull=?)" , true , false )
2016-05-28 09:23:39 +08:00
if err != nil {
2019-04-02 15:48:31 +08:00
log . Error ( "Select %s: %v" , desc , err )
2016-05-28 09:23:39 +08:00
} else {
for _ , result := range results {
2020-12-25 17:59:32 +08:00
id , _ := strconv . ParseInt ( string ( result [ "id" ] ) , 10 , 64 )
2019-12-15 17:51:28 +08:00
select {
case <- ctx . Done ( ) :
2020-05-17 07:31:38 +08:00
log . Warn ( "CheckRepoStats: Cancelled during %s for repo ID %d" , desc , id )
2021-11-10 13:13:16 +08:00
return db . ErrCancelledf ( "during %s for repo ID %d" , desc , id )
2019-12-15 17:51:28 +08:00
default :
}
2016-05-28 09:23:39 +08:00
log . Trace ( "Updating %s: %d" , desc , id )
2021-09-23 23:45:36 +08:00
_ , err = db . GetEngine ( db . DefaultContext ) . Exec ( "UPDATE `repository` SET num_closed_issues=(SELECT COUNT(*) FROM `issue` WHERE repo_id=? AND is_closed=? AND is_pull=?) WHERE id=?" , id , true , false , id )
2016-05-28 09:23:39 +08:00
if err != nil {
2019-04-02 15:48:31 +08:00
log . Error ( "Update %s[%d]: %v" , desc , id , err )
2016-05-28 09:23:39 +08:00
}
}
}
// ***** END: Repository.NumClosedIssues *****
2019-07-19 05:51:33 +08:00
// ***** START: Repository.NumClosedPulls *****
desc = "repository count 'num_closed_pulls'"
2021-09-23 23:45:36 +08:00
results , err = db . GetEngine ( db . DefaultContext ) . Query ( "SELECT repo.id FROM `repository` repo WHERE repo.num_closed_pulls!=(SELECT COUNT(*) FROM `issue` WHERE repo_id=repo.id AND is_closed=? AND is_pull=?)" , true , true )
2019-07-19 05:51:33 +08:00
if err != nil {
log . Error ( "Select %s: %v" , desc , err )
} else {
for _ , result := range results {
2020-12-25 17:59:32 +08:00
id , _ := strconv . ParseInt ( string ( result [ "id" ] ) , 10 , 64 )
2019-12-15 17:51:28 +08:00
select {
case <- ctx . Done ( ) :
2020-05-17 07:31:38 +08:00
log . Warn ( "CheckRepoStats: Cancelled" )
2021-11-10 13:13:16 +08:00
return db . ErrCancelledf ( "during %s for repo ID %d" , desc , id )
2019-12-15 17:51:28 +08:00
default :
}
2019-07-19 05:51:33 +08:00
log . Trace ( "Updating %s: %d" , desc , id )
2021-09-23 23:45:36 +08:00
_ , err = db . GetEngine ( db . DefaultContext ) . Exec ( "UPDATE `repository` SET num_closed_pulls=(SELECT COUNT(*) FROM `issue` WHERE repo_id=? AND is_closed=? AND is_pull=?) WHERE id=?" , id , true , true , id )
2019-07-19 05:51:33 +08:00
if err != nil {
log . Error ( "Update %s[%d]: %v" , desc , id , err )
}
}
}
// ***** END: Repository.NumClosedPulls *****
2016-05-28 09:23:39 +08:00
// FIXME: use checker when stop supporting old fork repo format.
2015-09-01 23:43:53 +08:00
// ***** START: Repository.NumForks *****
2021-09-23 23:45:36 +08:00
results , err = db . GetEngine ( db . DefaultContext ) . Query ( "SELECT repo.id FROM `repository` repo WHERE repo.num_forks!=(SELECT COUNT(*) FROM `repository` WHERE fork_id=repo.id)" )
2015-08-30 01:13:24 +08:00
if err != nil {
2019-04-02 15:48:31 +08:00
log . Error ( "Select repository count 'num_forks': %v" , err )
2015-09-01 23:43:53 +08:00
} else {
for _ , result := range results {
2020-12-25 17:59:32 +08:00
id , _ := strconv . ParseInt ( string ( result [ "id" ] ) , 10 , 64 )
2019-12-15 17:51:28 +08:00
select {
case <- ctx . Done ( ) :
2020-05-17 07:31:38 +08:00
log . Warn ( "CheckRepoStats: Cancelled" )
2021-11-10 13:13:16 +08:00
return db . ErrCancelledf ( "during %s for repo ID %d" , desc , id )
2019-12-15 17:51:28 +08:00
default :
}
2015-09-01 23:43:53 +08:00
log . Trace ( "Updating repository count 'num_forks': %d" , id )
repo , err := GetRepositoryByID ( id )
if err != nil {
2019-04-02 15:48:31 +08:00
log . Error ( "GetRepositoryByID[%d]: %v" , id , err )
2015-09-01 23:43:53 +08:00
continue
}
2021-09-23 23:45:36 +08:00
rawResult , err := db . GetEngine ( db . DefaultContext ) . Query ( "SELECT COUNT(*) FROM `repository` WHERE fork_id=?" , repo . ID )
2015-09-01 23:43:53 +08:00
if err != nil {
2019-04-02 15:48:31 +08:00
log . Error ( "Select count of forks[%d]: %v" , repo . ID , err )
2015-09-01 23:43:53 +08:00
continue
}
repo . NumForks = int ( parseCountResult ( rawResult ) )
if err = UpdateRepository ( repo , false ) ; err != nil {
2019-04-02 15:48:31 +08:00
log . Error ( "UpdateRepository[%d]: %v" , id , err )
2015-09-01 23:43:53 +08:00
continue
}
2015-08-30 01:13:24 +08:00
}
}
2015-09-01 23:43:53 +08:00
// ***** END: Repository.NumForks *****
2020-05-17 07:31:38 +08:00
return nil
2015-03-21 20:55:00 +08:00
}
2019-01-24 02:58:38 +08:00
// SetArchiveRepoState sets if a repo is archived
func ( repo * Repository ) SetArchiveRepoState ( isArchived bool ) ( err error ) {
repo . IsArchived = isArchived
2021-09-23 23:45:36 +08:00
_ , err = db . GetEngine ( db . DefaultContext ) . Where ( "id = ?" , repo . ID ) . Cols ( "is_archived" ) . NoAutoTime ( ) . Update ( repo )
2019-01-24 02:58:38 +08:00
return
}
2014-10-19 13:35:24 +08:00
// ___________ __
// \_ _____/__________| | __
// | __)/ _ \_ __ \ |/ /
// | \( <_> ) | \/ <
// \___ / \____/|__| |__|_ \
// \/ \/
2021-11-22 23:21:55 +08:00
// GetForkedRepo checks if given user has already forked a repository with given ID.
func GetForkedRepo ( ownerID , repoID int64 ) * Repository {
2015-08-08 17:24:10 +08:00
repo := new ( Repository )
2021-09-23 23:45:36 +08:00
has , _ := db . GetEngine ( db . DefaultContext ) .
2016-11-10 23:16:32 +08:00
Where ( "owner_id=? AND fork_id=?" , ownerID , repoID ) .
Get ( repo )
2021-11-22 23:21:55 +08:00
if has {
return repo
}
return nil
}
// HasForkedRepo checks if given user has already forked a repository with given ID.
func HasForkedRepo ( ownerID , repoID int64 ) bool {
has , _ := db . GetEngine ( db . DefaultContext ) .
Table ( "repository" ) .
Where ( "owner_id=? AND fork_id=?" , ownerID , repoID ) .
Exist ( )
return has
2015-08-08 17:24:10 +08:00
}
2019-11-11 23:15:29 +08:00
// CopyLFS copies LFS data from one repo to another
2021-09-23 23:45:36 +08:00
func CopyLFS ( ctx context . Context , newRepo , oldRepo * Repository ) error {
2019-11-11 23:15:29 +08:00
var lfsObjects [ ] * LFSMetaObject
2021-09-23 23:45:36 +08:00
if err := db . GetEngine ( ctx ) . Where ( "repository_id=?" , oldRepo . ID ) . Find ( & lfsObjects ) ; err != nil {
2019-11-11 23:15:29 +08:00
return err
}
for _ , v := range lfsObjects {
v . ID = 0
v . RepositoryID = newRepo . ID
2021-09-23 23:45:36 +08:00
if _ , err := db . GetEngine ( ctx ) . Insert ( v ) ; err != nil {
2019-11-11 23:15:29 +08:00
return err
}
}
return nil
}
2016-11-29 01:27:55 +08:00
// GetForks returns all the forks of the repository
2021-09-24 19:32:56 +08:00
func ( repo * Repository ) GetForks ( listOptions db . ListOptions ) ( [ ] * Repository , error ) {
2020-01-25 03:00:29 +08:00
if listOptions . Page == 0 {
forks := make ( [ ] * Repository , 0 , repo . NumForks )
2021-09-23 23:45:36 +08:00
return forks , db . GetEngine ( db . DefaultContext ) . Find ( & forks , & Repository { ForkID : repo . ID } )
2020-01-25 03:00:29 +08:00
}
2021-09-24 19:32:56 +08:00
sess := db . GetPaginatedSession ( & listOptions )
2020-01-25 03:00:29 +08:00
forks := make ( [ ] * Repository , 0 , listOptions . PageSize )
return forks , sess . Find ( & forks , & Repository { ForkID : repo . ID } )
2015-10-01 21:17:27 +08:00
}
2016-08-11 20:48:08 +08:00
2017-02-14 22:14:29 +08:00
// GetUserFork return user forked repository from this repository, if not forked return nil
func ( repo * Repository ) GetUserFork ( userID int64 ) ( * Repository , error ) {
var forkedRepo Repository
2021-09-23 23:45:36 +08:00
has , err := db . GetEngine ( db . DefaultContext ) . Where ( "fork_id = ?" , repo . ID ) . And ( "owner_id = ?" , userID ) . Get ( & forkedRepo )
2017-02-14 22:14:29 +08:00
if err != nil {
return nil , err
}
if ! has {
return nil , nil
}
return & forkedRepo , nil
}
2019-05-30 10:22:26 +08:00
2019-07-08 10:14:12 +08:00
// 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
}
2019-10-30 05:32:21 +08:00
// GetTreePathLock returns LSF lock for the treePath
func ( repo * Repository ) GetTreePathLock ( treePath string ) ( * LFSLock , error ) {
if setting . LFS . StartServer {
2019-12-12 21:18:07 +08:00
locks , err := GetLFSLockByRepoID ( repo . ID , 0 , 0 )
2019-10-30 05:32:21 +08:00
if err != nil {
return nil , err
}
for _ , lock := range locks {
if lock . Path == treePath {
return lock , nil
}
}
}
return nil , nil
}
2019-11-09 06:21:00 +08:00
2021-09-19 19:49:59 +08:00
func updateRepositoryCols ( e db . Engine , repo * Repository , cols ... string ) error {
2019-11-25 13:17:51 +08:00
_ , err := e . ID ( repo . ID ) . Cols ( cols ... ) . Update ( repo )
return err
}
2019-11-09 06:21:00 +08:00
// UpdateRepositoryCols updates repository's columns
func UpdateRepositoryCols ( repo * Repository , cols ... string ) error {
2021-09-23 23:45:36 +08:00
return updateRepositoryCols ( db . GetEngine ( db . DefaultContext ) , repo , cols ... )
2019-11-09 06:21:00 +08:00
}
2020-07-08 03:16:34 +08:00
2020-09-20 00:44:55 +08:00
// 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
}
2021-11-21 23:41:00 +08:00
func updateUserStarNumbers ( users [ ] User ) error {
ctx , committer , err := db . TxContext ( )
if err != nil {
return err
}
defer committer . Close ( )
for _ , user := range users {
if _ , err = db . Exec ( ctx , "UPDATE `user` SET num_stars=(SELECT COUNT(*) FROM `star` WHERE uid=?) WHERE id=?" , user . ID , user . ID ) ; err != nil {
return err
}
}
return committer . Commit ( )
}
2020-07-08 03:16:34 +08:00
// DoctorUserStarNum recalculate Stars number for all user
func DoctorUserStarNum ( ) ( err error ) {
const batchSize = 100
for start := 0 ; ; start += batchSize {
users := make ( [ ] User , 0 , batchSize )
2021-11-21 23:41:00 +08:00
if err = db . GetEngine ( db . DefaultContext ) . Limit ( batchSize , start ) . Where ( "type = ?" , 0 ) . Cols ( "id" ) . Find ( & users ) ; err != nil {
2020-07-08 03:16:34 +08:00
return
}
if len ( users ) == 0 {
break
}
2021-11-21 23:41:00 +08:00
if err = updateUserStarNumbers ( users ) ; err != nil {
2020-07-08 03:16:34 +08:00
return
}
}
log . Debug ( "recalculate Stars number for all user finished" )
return
}
2020-10-14 21:07:51 +08:00
// IterateRepository iterate repositories
func IterateRepository ( f func ( repo * Repository ) error ) error {
var start int
2021-03-15 02:52:12 +08:00
batchSize := setting . Database . IterateBufferSize
2020-10-14 21:07:51 +08:00
for {
2021-03-15 02:52:12 +08:00
repos := make ( [ ] * Repository , 0 , batchSize )
2021-09-23 23:45:36 +08:00
if err := db . GetEngine ( db . DefaultContext ) . Limit ( batchSize , start ) . Find ( & repos ) ; err != nil {
2020-10-14 21:07:51 +08:00
return err
}
if len ( repos ) == 0 {
return nil
}
start += len ( repos )
for _ , repo := range repos {
if err := f ( repo ) ; err != nil {
return err
}
}
}
}
2021-11-19 21:39:57 +08:00
// LinkedRepository returns the linked repo if any
func LinkedRepository ( a * repo_model . Attachment ) ( * Repository , unit . Type , error ) {
if a . IssueID != 0 {
iss , err := GetIssueByID ( a . IssueID )
if err != nil {
return nil , unit . TypeIssues , err
}
repo , err := GetRepositoryByID ( iss . RepoID )
unitType := unit . TypeIssues
if iss . IsPull {
unitType = unit . TypePullRequests
}
return repo , unitType , err
} else if a . ReleaseID != 0 {
rel , err := GetReleaseByID ( a . ReleaseID )
if err != nil {
return nil , unit . TypeReleases , err
}
repo , err := GetRepositoryByID ( rel . RepoID )
return repo , unit . TypeReleases , err
}
return nil , - 1 , nil
}