2014-02-14 17:16:54 -06:00
// Copyright 2014 The Gogs Authors. All rights reserved.
2017-08-26 08:57:41 -05:00
// Copyright 2017 The Gitea Authors. All rights reserved.
2014-02-14 17:16:54 -06:00
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
2014-02-14 08:20:57 -06:00
package models
import (
2015-08-28 05:33:09 -05:00
"bytes"
2019-12-15 03:51:28 -06:00
"context"
2019-05-29 21:22:26 -05:00
"crypto/md5"
2014-03-09 19:06:29 -05:00
"errors"
2014-03-10 19:48:58 -05:00
"fmt"
2014-07-22 12:52:37 -05:00
"html/template"
2019-05-29 21:22:26 -05:00
// Needed for jpeg support
_ "image/jpeg"
"image/png"
2014-07-23 06:48:06 -05:00
"io/ioutil"
2019-01-30 15:04:19 -06:00
"net/url"
2014-02-14 08:20:57 -06:00
"os"
2014-03-29 21:18:36 -05:00
"path"
2014-02-14 08:20:57 -06:00
"path/filepath"
2014-06-02 22:17:21 -05:00
"sort"
2019-04-22 15:40:51 -05:00
"strconv"
2014-02-14 08:20:57 -06:00
"strings"
"time"
2019-05-29 21:22:26 -05:00
"code.gitea.io/gitea/modules/avatar"
2019-03-27 04:33:00 -05:00
"code.gitea.io/gitea/modules/git"
2016-11-10 10:24:48 -06:00
"code.gitea.io/gitea/modules/log"
2017-09-16 12:17:57 -05:00
"code.gitea.io/gitea/modules/markup"
2016-12-22 12:12:23 -06:00
"code.gitea.io/gitea/modules/options"
2016-11-10 10:24:48 -06:00
"code.gitea.io/gitea/modules/setting"
2019-10-14 01:10:42 -05:00
"code.gitea.io/gitea/modules/structs"
2019-05-11 05:21:34 -05:00
api "code.gitea.io/gitea/modules/structs"
2016-11-10 10:24:48 -06:00
"code.gitea.io/gitea/modules/sync"
2019-08-15 09:46:21 -05:00
"code.gitea.io/gitea/modules/timeutil"
2019-11-10 15:33:47 -06:00
"code.gitea.io/gitea/modules/util"
2017-01-01 12:15:09 -06:00
2019-10-16 08:42:42 -05:00
"github.com/mcuadros/go-version"
2019-08-23 11:40:30 -05:00
"github.com/unknwon/com"
2019-06-23 10:22:43 -05:00
"xorm.io/builder"
2014-02-14 08:20:57 -06:00
)
2019-12-05 22:00:50 -06:00
// RepoWorkingPool represents a working pool to order the parallel changes to the same repository
var RepoWorkingPool = sync . NewExclusivePool ( )
2016-08-14 19:44:20 -05:00
2014-03-11 00:32:36 -05:00
var (
2016-11-28 11:27:55 -06: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 00:32:36 -05:00
)
2014-03-09 19:06:29 -05:00
var (
2016-11-28 11:27:55 -06:00
// Gitignores contains the gitiginore files
Gitignores [ ] string
// Licenses contains the license files
Licenses [ ] string
// Readmes contains the readme files
Readmes [ ] string
2019-12-06 20:13:19 -06:00
// LabelTemplates contains the label template files and the list of labels for each file
LabelTemplates map [ string ] string
2015-10-01 08:17:27 -05:00
2016-11-28 11:27:55 -06:00
// ItemsPerPage maximum items per page in forks, watchers and stars of a repo
2015-11-16 22:28:46 -06:00
ItemsPerPage = 40
2014-03-09 19:06:29 -05:00
)
2019-05-14 20:57:00 -05:00
// loadRepoConfig loads the repository config
func loadRepoConfig ( ) {
2015-08-28 03:44:04 -05:00
// Load .gitignore and license files and readme templates.
2016-08-29 21:02:49 -05:00
types := [ ] string { "gitignore" , "license" , "readme" , "label" }
typeFiles := make ( [ ] [ ] string , 4 )
2014-05-11 13:03:51 -05:00
for i , t := range types {
2016-12-22 12:12:23 -06:00
files , err := options . Dir ( t )
2014-07-25 23:24:27 -05:00
if err != nil {
2019-04-02 02:48:31 -05:00
log . Fatal ( "Failed to get %s files: %v" , t , err )
2014-07-25 23:24:27 -05:00
}
2016-12-22 12:12:23 -06:00
customPath := path . Join ( setting . CustomPath , "options" , t )
2014-05-25 19:11:25 -05:00
if com . IsDir ( customPath ) {
customFiles , err := com . StatDir ( customPath )
2014-05-11 13:03:51 -05:00
if err != nil {
2019-04-02 02:48:31 -05:00
log . Fatal ( "Failed to get custom %s files: %v" , t , err )
2014-05-11 13:03:51 -05:00
}
for _ , f := range customFiles {
if ! com . IsSliceContainsStr ( files , f ) {
files = append ( files , f )
}
}
}
typeFiles [ i ] = files
}
2014-07-25 23:24:27 -05:00
Gitignores = typeFiles [ 0 ]
2014-05-11 13:03:51 -05:00
Licenses = typeFiles [ 1 ]
2015-08-28 03:44:04 -05:00
Readmes = typeFiles [ 2 ]
2019-12-06 20:13:19 -06:00
LabelTemplatesFiles := typeFiles [ 3 ]
2014-07-25 23:24:27 -05:00
sort . Strings ( Gitignores )
2014-06-02 22:17:21 -05:00
sort . Strings ( Licenses )
2015-08-28 03:44:04 -05:00
sort . Strings ( Readmes )
2019-12-06 20:13:19 -06: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 02:06:22 -05:00
// Filter out invalid names and promote preferred licenses.
sortedLicenses := make ( [ ] string , 0 , len ( Licenses ) )
for _ , name := range setting . Repository . PreferredLicenses {
if com . IsSliceContainsStr ( Licenses , name ) {
sortedLicenses = append ( sortedLicenses , name )
}
}
for _ , name := range Licenses {
if ! com . IsSliceContainsStr ( setting . Repository . PreferredLicenses , name ) {
sortedLicenses = append ( sortedLicenses , name )
}
}
Licenses = sortedLicenses
2014-03-21 00:48:10 -05:00
}
2014-03-17 10:56:50 -05:00
2016-11-28 11:27:55 -06:00
// NewRepoContext creates a new repository context
2014-03-21 00:48:10 -05:00
func NewRepoContext ( ) {
2019-05-14 20:57:00 -05:00
loadRepoConfig ( )
2014-08-23 10:58:56 -05:00
2016-03-23 02:16:53 -05:00
RemoveAllWithNotice ( "Clean up repository temporary data" , filepath . Join ( setting . AppDataPath , "tmp" ) )
2014-03-11 00:32:36 -05:00
}
2019-10-13 08:23:14 -05:00
// RepositoryStatus defines the status of repository
type RepositoryStatus int
// all kinds of RepositoryStatus
const (
RepositoryReady RepositoryStatus = iota // a normal repository
RepositoryBeingMigrated // repository is migrating
)
2014-03-20 15:04:56 -05:00
// Repository represents a git repository.
type Repository struct {
2019-10-14 01:10:42 -05:00
ID int64 ` xorm:"pk autoincr" `
OwnerID int64 ` xorm:"UNIQUE(s) index" `
OwnerName string ` xorm:"-" `
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 structs . GitServiceType ` xorm:"index" `
OriginalURL string ` xorm:"VARCHAR(2048)" `
DefaultBranch string
2014-10-19 00:35:24 -05:00
2014-05-12 13:06:42 -05:00
NumWatches int
NumStars int
NumForks int
NumIssues int
NumClosedIssues int
NumOpenIssues int ` xorm:"-" `
2014-07-25 23:24:27 -05:00
NumPulls int
NumClosedPulls int
NumOpenPulls int ` xorm:"-" `
2014-05-12 13:06:42 -05:00
NumMilestones int ` xorm:"NOT NULL DEFAULT 0" `
NumClosedMilestones int ` xorm:"NOT NULL DEFAULT 0" `
NumOpenMilestones int ` xorm:"-" `
2014-10-19 00:35:24 -05:00
2019-01-23 12:58:38 -06:00
IsPrivate bool ` xorm:"INDEX" `
IsEmpty bool ` xorm:"INDEX" `
IsArchived bool ` xorm:"INDEX" `
2019-10-13 08:23:14 -05:00
IsMirror bool ` xorm:"INDEX" `
* Mirror ` xorm:"-" `
Status RepositoryStatus ` xorm:"NOT NULL DEFAULT 0" `
2014-10-19 00:35:24 -05:00
2019-11-24 10:34:44 -06:00
RenderingMetas map [ string ] string ` xorm:"-" `
Units [ ] * RepoUnit ` xorm:"-" `
2015-12-04 20:30:33 -06:00
2019-02-10 13:27:19 -06:00
IsFork bool ` xorm:"INDEX NOT NULL DEFAULT false" `
ForkID int64 ` xorm:"INDEX" `
BaseRepo * Repository ` xorm:"-" `
2019-11-11 09:15:29 -06:00
IsTemplate bool ` xorm:"INDEX NOT NULL DEFAULT false" `
TemplateID int64 ` xorm:"INDEX" `
TemplateRepo * Repository ` xorm:"-" `
2019-02-10 13:27:19 -06:00
Size int64 ` xorm:"NOT NULL DEFAULT 0" `
IndexerStatus * RepoIndexerStatus ` xorm:"-" `
IsFsckEnabled bool ` xorm:"NOT NULL DEFAULT true" `
CloseIssuesViaCommitInAnyBranch bool ` xorm:"NOT NULL DEFAULT false" `
Topics [ ] string ` xorm:"TEXT JSON" `
2014-10-19 00:35:24 -05:00
2019-05-29 21:22:26 -05:00
// Avatar: ID(10-20)-md5(32) - must fit into 64 symbols
Avatar string ` xorm:"VARCHAR(64)" `
2019-08-15 09:46:21 -05:00
CreatedUnix timeutil . TimeStamp ` xorm:"INDEX created" `
UpdatedUnix timeutil . TimeStamp ` xorm:"INDEX updated" `
2014-03-20 15:04:56 -05:00
}
2020-01-26 02:16:53 -06:00
// SanitizedOriginalURL returns a sanitized OriginalURL
func ( repo * Repository ) SanitizedOriginalURL ( ) string {
if repo . OriginalURL == "" {
return ""
}
return util . SanitizeURLCredentials ( repo . OriginalURL , false )
}
2019-04-22 15:40:51 -05:00
// ColorFormat returns a colored string to represent this repo
func ( repo * Repository ) ColorFormat ( s fmt . State ) {
var ownerName interface { }
if repo . OwnerName != "" {
ownerName = repo . OwnerName
} else if repo . Owner != nil {
ownerName = repo . Owner . Name
} else {
ownerName = log . NewColoredIDValue ( strconv . FormatInt ( repo . OwnerID , 10 ) )
}
log . ColorFprintf ( s , "%d:%s/%s" ,
log . NewColoredIDValue ( repo . ID ) ,
ownerName ,
repo . Name )
}
2019-10-13 08:23:14 -05:00
// IsBeingMigrated indicates that repository is being migtated
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-01 11:52:35 -05: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 {
repo . DefaultBranch = "master"
2015-08-20 07:18:49 -05:00
}
2017-10-01 11:52:35 -05:00
repo . NumOpenIssues = repo . NumIssues - repo . NumClosedIssues
repo . NumOpenPulls = repo . NumPulls - repo . NumClosedPulls
repo . NumOpenMilestones = repo . NumMilestones - repo . NumClosedMilestones
2015-08-20 07:18:49 -05:00
}
2016-08-14 05:32:24 -05:00
// MustOwner always returns a valid *User object to avoid
// conceptually impossible error handling.
2017-01-04 18:50:34 -06:00
// It creates a fake object that contains error details
2016-08-14 05:32:24 -05:00
// when error occurs.
func ( repo * Repository ) MustOwner ( ) * User {
return repo . mustOwner ( x )
}
2018-05-02 01:10:19 -05:00
// MustOwnerName always returns valid owner name to avoid
// conceptually impossible error handling.
// It returns "error" and logs error details when error
// occurs.
func ( repo * Repository ) MustOwnerName ( ) string {
return repo . mustOwnerName ( x )
}
2016-11-28 11:27:55 -06:00
// FullName returns the repository full name
2016-08-14 05:32:24 -05:00
func ( repo * Repository ) FullName ( ) string {
2018-05-02 01:10:19 -05:00
return repo . MustOwnerName ( ) + "/" + repo . Name
2016-08-14 05:32:24 -05:00
}
2016-11-28 11:27:55 -06:00
// HTMLURL returns the repository HTML URL
2016-08-16 12:19:09 -05:00
func ( repo * Repository ) HTMLURL ( ) string {
2016-11-27 04:14:25 -06:00
return setting . AppURL + repo . FullName ( )
2016-08-14 05:32:24 -05:00
}
2017-03-03 08:35:42 -06:00
// APIURL returns the repository API URL
func ( repo * Repository ) APIURL ( ) string {
return setting . AppURL + path . Join ( "api/v1/repos" , repo . FullName ( ) )
}
2016-11-28 11:27:55 -06:00
// APIFormat converts a Repository to api.Repository
2016-12-05 17:48:51 -06:00
func ( repo * Repository ) APIFormat ( mode AccessMode ) * api . Repository {
2018-10-19 11:36:42 -05:00
return repo . innerAPIFormat ( x , mode , false )
2017-05-11 22:09:26 -05:00
}
2017-10-25 20:37:33 -05: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 )
}
2018-10-19 11:36:42 -05:00
func ( repo * Repository ) innerAPIFormat ( e Engine , mode AccessMode , isParent bool ) * api . Repository {
2017-05-11 22:09:26 -05:00
var parent * api . Repository
2018-10-19 11:36:42 -05:00
cloneLink := repo . cloneLink ( e , false )
2016-12-05 17:48:51 -06:00
permission := & api . Permission {
Admin : mode >= AccessModeAdmin ,
Push : mode >= AccessModeWrite ,
Pull : mode >= AccessModeRead ,
}
2017-05-11 22:09:26 -05:00
if ! isParent {
2018-10-19 11:36:42 -05:00
err := repo . getBaseRepo ( e )
2017-05-11 22:09:26 -05:00
if err != nil {
2019-04-02 02:48:31 -05:00
log . Error ( "APIFormat: %v" , err )
2017-05-11 22:09:26 -05:00
}
if repo . BaseRepo != nil {
2018-10-19 11:36:42 -05:00
parent = repo . BaseRepo . innerAPIFormat ( e , mode , true )
2017-05-11 22:09:26 -05:00
}
}
2019-05-30 10:09:05 -05:00
hasIssues := false
2019-10-02 04:30:41 -05:00
var externalTracker * api . ExternalTracker
var internalTracker * api . InternalTracker
if unit , err := repo . getUnit ( e , UnitTypeIssues ) ; err == nil {
config := unit . IssuesConfig ( )
2019-05-30 10:09:05 -05:00
hasIssues = true
2019-10-02 04:30:41 -05:00
internalTracker = & api . InternalTracker {
EnableTimeTracker : config . EnableTimetracker ,
AllowOnlyContributorsToTrackTime : config . AllowOnlyContributorsToTrackTime ,
EnableIssueDependencies : config . EnableDependencies ,
}
} else if unit , err := repo . getUnit ( e , UnitTypeExternalTracker ) ; err == nil {
config := unit . ExternalTrackerConfig ( )
hasIssues = true
externalTracker = & api . ExternalTracker {
ExternalTrackerURL : config . ExternalTrackerURL ,
ExternalTrackerFormat : config . ExternalTrackerFormat ,
ExternalTrackerStyle : config . ExternalTrackerStyle ,
}
2019-05-30 10:09:05 -05:00
}
hasWiki := false
2019-10-02 04:30:41 -05:00
var externalWiki * api . ExternalWiki
2019-05-30 10:09:05 -05:00
if _ , err := repo . getUnit ( e , UnitTypeWiki ) ; err == nil {
hasWiki = true
2019-10-02 04:30:41 -05:00
} else if unit , err := repo . getUnit ( e , UnitTypeExternalWiki ) ; err == nil {
hasWiki = true
config := unit . ExternalWikiConfig ( )
externalWiki = & api . ExternalWiki {
ExternalWikiURL : config . ExternalWikiURL ,
}
2019-05-30 10:09:05 -05:00
}
hasPullRequests := false
ignoreWhitespaceConflicts := false
allowMerge := false
allowRebase := false
allowRebaseMerge := false
allowSquash := false
if unit , err := repo . getUnit ( e , UnitTypePullRequests ) ; err == nil {
config := unit . PullRequestsConfig ( )
hasPullRequests = true
ignoreWhitespaceConflicts = config . IgnoreWhitespaceConflicts
allowMerge = config . AllowMerge
allowRebase = config . AllowRebase
allowRebaseMerge = config . AllowRebaseMerge
allowSquash = config . AllowSquash
}
2020-04-06 17:13:12 -05:00
numReleases , _ := GetReleaseCountByRepoID ( repo . ID , FindReleasesOptions { IncludeDrafts : false , IncludeTags : true } )
2016-08-14 05:32:24 -05:00
return & api . Repository {
2019-05-30 10:09:05 -05:00
ID : repo . ID ,
Owner : repo . Owner . APIFormat ( ) ,
Name : repo . Name ,
FullName : repo . FullName ( ) ,
Description : repo . Description ,
Private : repo . IsPrivate ,
2019-11-11 09:15:29 -06:00
Template : repo . IsTemplate ,
2019-05-30 10:09:05 -05:00
Empty : repo . IsEmpty ,
Archived : repo . IsArchived ,
Size : int ( repo . Size / 1024 ) ,
Fork : repo . IsFork ,
Parent : parent ,
Mirror : repo . IsMirror ,
HTMLURL : repo . HTMLURL ( ) ,
SSHURL : cloneLink . SSH ,
CloneURL : cloneLink . HTTPS ,
Website : repo . Website ,
Stars : repo . NumStars ,
Forks : repo . NumForks ,
Watchers : repo . NumWatches ,
OpenIssues : repo . NumOpenIssues ,
2019-11-29 09:14:24 -06:00
OpenPulls : repo . NumOpenPulls ,
2020-04-06 17:13:12 -05:00
Releases : int ( numReleases ) ,
2019-05-30 10:09:05 -05:00
DefaultBranch : repo . DefaultBranch ,
Created : repo . CreatedUnix . AsTime ( ) ,
Updated : repo . UpdatedUnix . AsTime ( ) ,
Permissions : permission ,
HasIssues : hasIssues ,
2019-10-02 04:30:41 -05:00
ExternalTracker : externalTracker ,
InternalTracker : internalTracker ,
2019-05-30 10:09:05 -05:00
HasWiki : hasWiki ,
2019-10-02 04:30:41 -05:00
ExternalWiki : externalWiki ,
2019-05-30 10:09:05 -05:00
HasPullRequests : hasPullRequests ,
IgnoreWhitespaceConflicts : ignoreWhitespaceConflicts ,
AllowMerge : allowMerge ,
AllowRebase : allowRebase ,
AllowRebaseMerge : allowRebaseMerge ,
AllowSquash : allowSquash ,
2019-06-11 19:12:13 -05:00
AvatarURL : repo . avatarLink ( e ) ,
2016-08-14 05:32:24 -05:00
}
}
2017-02-04 09:53:46 -06:00
func ( repo * Repository ) getUnits ( e Engine ) ( err error ) {
if repo . Units != nil {
return nil
}
repo . Units , err = getUnitsByRepoID ( e , repo . ID )
return err
}
2017-05-18 09:54:24 -05:00
// CheckUnitUser check whether user could visit the unit of this repository
2017-05-18 19:59:26 -05:00
func ( repo * Repository ) CheckUnitUser ( userID int64 , isAdmin bool , unitType UnitType ) bool {
2018-10-25 05:55:16 -05:00
return repo . checkUnitUser ( x , userID , isAdmin , unitType )
}
func ( repo * Repository ) checkUnitUser ( e Engine , userID int64 , isAdmin bool , unitType UnitType ) bool {
2018-11-28 05:26:14 -06:00
if isAdmin {
return true
2017-06-14 21:50:12 -05:00
}
2018-11-28 05:26:14 -06:00
user , err := getUserByID ( e , userID )
2017-05-18 09:54:24 -05:00
if err != nil {
2018-11-28 05:26:14 -06:00
return false
2017-05-18 09:54:24 -05:00
}
2018-11-28 05:26:14 -06:00
perm , err := getUserRepoPermission ( e , repo , user )
if err != nil {
return false
2017-05-18 09:54:24 -05:00
}
2018-11-28 05:26:14 -06:00
return perm . CanRead ( unitType )
2017-02-04 09:53:46 -06:00
}
2017-08-02 03:46:54 -05:00
// UnitEnabled if this repository has the given unit enabled
func ( repo * Repository ) UnitEnabled ( tp UnitType ) bool {
2017-10-14 18:17:39 -05:00
if err := repo . getUnits ( x ) ; err != nil {
log . Warn ( "Error loading repository (ID: %d) units: %s" , repo . ID , err . Error ( ) )
}
2017-02-04 09:53:46 -06:00
for _ , unit := range repo . Units {
if unit . Type == tp {
return true
}
}
return false
}
2019-05-30 10:09:05 -05:00
// ErrUnitTypeNotExist represents a "UnitTypeNotExist" kind of error.
type ErrUnitTypeNotExist struct {
UT UnitType
}
// 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 09:53:46 -06:00
// MustGetUnit always returns a RepoUnit object
func ( repo * Repository ) MustGetUnit ( tp UnitType ) * RepoUnit {
ru , err := repo . GetUnit ( tp )
if err == nil {
return ru
}
if tp == UnitTypeExternalWiki {
return & RepoUnit {
Type : tp ,
Config : new ( ExternalWikiConfig ) ,
}
} else if tp == UnitTypeExternalTracker {
return & RepoUnit {
Type : tp ,
Config : new ( ExternalTrackerConfig ) ,
}
2018-01-05 12:56:50 -06:00
} else if tp == UnitTypePullRequests {
return & RepoUnit {
Type : tp ,
Config : new ( PullRequestsConfig ) ,
}
2019-05-30 10:09:05 -05:00
} else if tp == UnitTypeIssues {
return & RepoUnit {
Type : tp ,
Config : new ( IssuesConfig ) ,
}
2017-02-04 09:53:46 -06:00
}
return & RepoUnit {
Type : tp ,
Config : new ( UnitConfig ) ,
}
}
// GetUnit returns a RepoUnit object
func ( repo * Repository ) GetUnit ( tp UnitType ) ( * RepoUnit , error ) {
2018-10-27 09:45:24 -05:00
return repo . getUnit ( x , tp )
}
func ( repo * Repository ) getUnit ( e Engine , tp UnitType ) ( * RepoUnit , error ) {
if err := repo . getUnits ( e ) ; err != nil {
2017-02-04 09:53:46 -06:00
return nil , err
}
for _ , unit := range repo . Units {
if unit . Type == tp {
return unit , nil
}
}
2019-05-30 10:09:05 -05:00
return nil , ErrUnitTypeNotExist { tp }
2017-02-04 09:53:46 -06:00
}
2015-02-12 23:58:46 -06:00
func ( repo * Repository ) getOwner ( e Engine ) ( err error ) {
2015-10-24 02:36:47 -05:00
if repo . Owner != nil {
return nil
2014-10-09 18:01:22 -05:00
}
2015-10-24 02:36:47 -05:00
repo . Owner , err = getUserByID ( e , repo . OwnerID )
2014-05-08 07:18:03 -05:00
return err
}
2016-11-28 11:27:55 -06:00
// GetOwner returns the repository owner
2015-08-17 15:03:11 -05:00
func ( repo * Repository ) GetOwner ( ) error {
2015-02-12 23:58:46 -06:00
return repo . getOwner ( x )
}
2015-11-26 16:33:45 -06:00
func ( repo * Repository ) mustOwner ( e Engine ) * User {
if err := repo . getOwner ( e ) ; err != nil {
return & User {
Name : "error" ,
FullName : err . Error ( ) ,
}
}
return repo . Owner
}
2018-05-02 01:10:19 -05:00
func ( repo * Repository ) getOwnerName ( e Engine ) error {
if len ( repo . OwnerName ) > 0 {
return nil
}
if repo . Owner != nil {
repo . OwnerName = repo . Owner . Name
return nil
}
u := new ( User )
has , err := e . ID ( repo . OwnerID ) . Cols ( "name" ) . Get ( u )
if err != nil {
return err
} else if ! has {
return ErrUserNotExist { repo . OwnerID , "" , 0 }
}
repo . OwnerName = u . Name
return nil
}
// GetOwnerName returns the repository owner name
func ( repo * Repository ) GetOwnerName ( ) error {
return repo . getOwnerName ( x )
}
func ( repo * Repository ) mustOwnerName ( e Engine ) string {
if err := repo . getOwnerName ( e ) ; err != nil {
2019-04-02 02:48:31 -05:00
log . Error ( "Error loading repository owner name: %v" , err )
2018-05-02 01:10:19 -05:00
return "error"
}
return repo . OwnerName
}
2019-04-12 00:53:34 -05:00
// ComposeMetas composes a map of metas for properly rendering issue links and external issue trackers.
2015-12-04 20:30:33 -06:00
func ( repo * Repository ) ComposeMetas ( ) map [ string ] string {
2019-11-24 10:34:44 -06:00
if repo . RenderingMetas == nil {
metas := map [ string ] string {
2019-08-14 03:04:55 -05:00
"user" : repo . MustOwner ( ) . Name ,
"repo" : repo . Name ,
"repoPath" : repo . RepoPath ( ) ,
2015-12-04 20:30:33 -06:00
}
2019-11-24 10:34:44 -06:00
2019-04-12 00:53:34 -05:00
unit , err := repo . GetUnit ( UnitTypeExternalTracker )
2019-11-24 10:34:44 -06: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 00:53:34 -05:00
}
2019-11-24 10:34:44 -06:00
if repo . Owner . IsOrganization ( ) {
teams := make ( [ ] string , 0 , 5 )
_ = x . Table ( "team_repo" ) .
Join ( "INNER" , "team" , "team.id = team_repo.team_id" ) .
Where ( "team_repo.repo_id = ?" , repo . ID ) .
Select ( "team.lower_name" ) .
OrderBy ( "team.lower_name" ) .
Find ( & teams )
metas [ "teams" ] = "," + strings . Join ( teams , "," ) + ","
metas [ "org" ] = repo . Owner . LowerName
2016-04-22 17:28:08 -05:00
}
2019-11-24 10:34:44 -06:00
repo . RenderingMetas = metas
2015-12-04 20:30:33 -06:00
}
2019-11-24 10:34:44 -06:00
return repo . RenderingMetas
2015-12-04 20:30:33 -06:00
}
2016-03-03 22:24:22 -06:00
// DeleteWiki removes the actual and local copy of repository wiki.
2017-02-24 09:19:13 -06:00
func ( repo * Repository ) DeleteWiki ( ) error {
return repo . deleteWiki ( x )
}
func ( repo * Repository ) deleteWiki ( e Engine ) error {
2019-05-11 10:29:17 -05:00
wikiPaths := [ ] string { repo . WikiPath ( ) }
2016-03-03 14:38:25 -06:00
for _ , wikiPath := range wikiPaths {
2017-02-24 09:19:13 -06:00
removeAllWithNotice ( e , "Delete repository wiki" , wikiPath )
2016-03-03 14:38:25 -06:00
}
2017-02-04 09:53:46 -06:00
2017-02-24 09:19:13 -06:00
_ , err := e . Where ( "repo_id = ?" , repo . ID ) . And ( "type = ?" , UnitTypeWiki ) . Delete ( new ( RepoUnit ) )
return err
2016-03-03 14:38:25 -06:00
}
2016-08-15 20:40:32 -05:00
func ( repo * Repository ) getAssignees ( e Engine ) ( _ [ ] * User , err error ) {
if err = repo . getOwner ( e ) ; err != nil {
2015-08-10 08:47:23 -05:00
return nil , err
}
accesses := make ( [ ] * Access , 0 , 10 )
2016-11-10 09:16:32 -06:00
if err = e .
Where ( "repo_id = ? AND mode >= ?" , repo . ID , AccessModeWrite ) .
Find ( & accesses ) ; err != nil {
2015-08-10 08:47:23 -05:00
return nil , err
}
2016-08-15 20:40:32 -05: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-15 20:48:20 -05: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-15 20:40:32 -05:00
}
2015-08-10 08:47:23 -05:00
if ! repo . Owner . IsOrganization ( ) {
users = append ( users , repo . Owner )
}
return users , nil
}
2016-08-15 20:40:32 -05: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 ) {
return repo . getAssignees ( x )
}
2015-08-10 08:47:23 -05:00
// GetMilestoneByID returns the milestone belongs to repository by given ID.
func ( repo * Repository ) GetMilestoneByID ( milestoneID int64 ) ( * Milestone , error ) {
2016-08-24 18:05:56 -05:00
return GetMilestoneByRepoID ( repo . ID , milestoneID )
2015-08-10 08:47:23 -05:00
}
2015-08-25 09:58:34 -05:00
// IssueStats returns number of open and closed repository issues by given filter mode.
2015-09-02 15:18:09 -05:00
func ( repo * Repository ) IssueStats ( uid int64 , filterMode int , isPull bool ) ( int64 , int64 ) {
return GetRepoIssueStats ( repo . ID , uid , filterMode , isPull )
2015-08-25 09:58:34 -05:00
}
2016-11-28 11:27:55 -06:00
// GetMirror sets the repository mirror, returns an error upon failure
2014-08-10 22:11:18 -05:00
func ( repo * Repository ) GetMirror ( ) ( err error ) {
2016-08-30 18:18:33 -05:00
repo . Mirror , err = GetMirrorByRepoID ( repo . ID )
2014-08-10 22:11:18 -05:00
return err
}
2018-01-05 04:56:52 -06: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 09:43:14 -05:00
func ( repo * Repository ) GetBaseRepo ( ) ( err error ) {
2018-10-19 11:36:42 -05:00
return repo . getBaseRepo ( x )
}
func ( repo * Repository ) getBaseRepo ( e Engine ) ( err error ) {
2014-10-19 00:35:24 -05:00
if ! repo . IsFork {
return nil
}
2018-10-19 11:36:42 -05:00
repo . BaseRepo , err = getRepositoryByID ( e , repo . ForkID )
2014-10-19 00:35:24 -05:00
return err
}
2019-11-11 09:15:29 -06: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 ) {
return repo . getTemplateRepo ( x )
}
func ( repo * Repository ) getTemplateRepo ( e Engine ) ( err error ) {
if ! repo . IsGenerated ( ) {
return nil
}
repo . TemplateRepo , err = getRepositoryByID ( e , repo . TemplateID )
return err
}
2015-11-26 16:33:45 -06:00
func ( repo * Repository ) repoPath ( e Engine ) string {
2018-05-02 01:10:19 -05:00
return RepoPath ( repo . mustOwnerName ( e ) , repo . Name )
2014-10-19 00:35:24 -05:00
}
2016-11-28 11:27:55 -06:00
// RepoPath returns the repository path
2015-11-26 16:33:45 -06:00
func ( repo * Repository ) RepoPath ( ) string {
2015-09-03 07:09:08 -05:00
return repo . repoPath ( x )
}
2017-12-02 23:29:41 -06:00
// GitConfigPath returns the path to a repository's git config/ directory
func GitConfigPath ( repoPath string ) string {
return filepath . Join ( repoPath , "config" )
}
2016-11-28 11:27:55 -06:00
// GitConfigPath returns the repository git config path
2015-12-08 19:06:12 -06:00
func ( repo * Repository ) GitConfigPath ( ) string {
2017-12-02 23:29:41 -06:00
return GitConfigPath ( repo . RepoPath ( ) )
2015-12-08 19:06:12 -06:00
}
2016-11-28 11:27:55 -06:00
// RelLink returns the repository relative link
2016-08-17 01:06:38 -05:00
func ( repo * Repository ) RelLink ( ) string {
return "/" + repo . FullName ( )
2014-10-04 12:19:14 -05:00
}
2016-11-28 11:27:55 -06:00
// Link returns the repository link
2016-08-17 01:06:38 -05:00
func ( repo * Repository ) Link ( ) string {
2016-11-27 04:14:25 -06:00
return setting . AppSubURL + "/" + repo . FullName ( )
2016-03-04 13:50:34 -06:00
}
2016-11-28 11:27:55 -06:00
// ComposeCompareURL returns the repository comparison URL
2015-12-09 19:46:05 -06:00
func ( repo * Repository ) ComposeCompareURL ( oldCommitID , newCommitID string ) string {
return fmt . Sprintf ( "%s/%s/compare/%s...%s" , repo . MustOwner ( ) . Name , repo . Name , oldCommitID , newCommitID )
}
2017-02-21 09:02:10 -06:00
// UpdateDefaultBranch updates the default branch
func ( repo * Repository ) UpdateDefaultBranch ( ) error {
_ , err := x . ID ( repo . ID ) . Cols ( "default_branch" ) . Update ( repo )
return err
}
2016-11-28 11:27:55 -06:00
// IsOwnedBy returns true when user owns this repository
2015-08-13 13:43:40 -05:00
func ( repo * Repository ) IsOwnedBy ( userID int64 ) bool {
return repo . OwnerID == userID
2014-10-13 14:23:30 -05:00
}
2017-05-26 00:08:13 -05:00
func ( repo * Repository ) updateSize ( e Engine ) error {
2019-11-10 15:33:47 -06:00
size , err := util . GetDirectorySize ( repo . repoPath ( e ) )
2017-04-11 08:30:15 -05:00
if err != nil {
return fmt . Errorf ( "UpdateSize: %v" , err )
}
2019-11-10 15:33:47 -06:00
repo . Size = size
2017-10-04 23:43:04 -05:00
_ , err = e . ID ( repo . ID ) . Cols ( "size" ) . Update ( repo )
2017-04-11 08:30:15 -05:00
return err
}
2019-11-10 15:33:47 -06:00
// UpdateSize updates the repository size, calculating it using util.GetDirectorySize
2017-05-26 00:08:13 -05:00
func ( repo * Repository ) UpdateSize ( ) error {
return repo . updateSize ( x )
}
2017-10-15 10:06:07 -05:00
// CanUserFork returns true if specified user can fork repository.
func ( repo * Repository ) CanUserFork ( user * User ) ( bool , error ) {
if user == nil {
return false , nil
}
if repo . OwnerID != user . ID && ! user . HasForkedRepo ( repo . ID ) {
return true , nil
}
if err := user . GetOwnedOrganizations ( ) ; err != nil {
return false , err
}
for _ , org := range user . OwnedOrgs {
if repo . OwnerID != org . ID && ! org . HasForkedRepo ( repo . ID ) {
return true , nil
}
}
return false , nil
}
2019-10-26 01:54:11 -05: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 ( ) {
isOwner , err := repo . Owner . IsOwnedBy ( user . ID )
if err != nil {
return false , err
} else if isOwner {
return true , nil
}
}
return false , nil
}
2016-02-19 13:33:06 -06:00
// CanEnablePulls returns true if repository meets the requirements of accepting pulls.
func ( repo * Repository ) CanEnablePulls ( ) bool {
2019-01-17 18:01:04 -06:00
return ! repo . IsMirror && ! repo . IsEmpty
2016-02-19 13:33:06 -06:00
}
2016-11-28 11:27:55 -06:00
// AllowsPulls returns true if repository meets the requirements of accepting pulls and has them enabled.
2016-02-19 13:33:06 -06:00
func ( repo * Repository ) AllowsPulls ( ) bool {
2017-08-02 03:46:54 -05:00
return repo . CanEnablePulls ( ) && repo . UnitEnabled ( UnitTypePullRequests )
2015-09-05 13:31:52 -05:00
}
2016-08-28 06:56:41 -05:00
// CanEnableEditor returns true if repository meets the requirements of web editor.
func ( repo * Repository ) CanEnableEditor ( ) bool {
return ! repo . IsMirror
}
2019-10-08 14:18:17 -05:00
// GetReaders returns all users that have explicit read access or higher to the repository.
func ( repo * Repository ) GetReaders ( ) ( _ [ ] * User , err error ) {
return repo . getUsersWithAccessMode ( x , AccessModeRead )
}
2017-09-14 03:16:22 -05:00
// GetWriters returns all users that have write access to the repository.
func ( repo * Repository ) GetWriters ( ) ( _ [ ] * User , err error ) {
return repo . getUsersWithAccessMode ( x , AccessModeWrite )
}
2019-10-08 14:18:17 -05: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
}
return x . Where ( "repo_id = ? AND user_id = ? AND mode >= ?" , repo . ID , userID , AccessModeRead ) . Get ( & Access { } )
}
2017-09-14 03:16:22 -05:00
// getUsersWithAccessMode returns users that have at least given access mode to the repository.
func ( repo * Repository ) getUsersWithAccessMode ( e Engine , mode AccessMode ) ( _ [ ] * User , err error ) {
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-28 11:27:55 -06:00
// DescriptionHTML does special handles to description and return HTML string.
func ( repo * Repository ) DescriptionHTML ( ) template . HTML {
2019-03-11 21:23:34 -05:00
desc , err := markup . RenderDescriptionHTML ( [ ] byte ( repo . Description ) , repo . HTMLURL ( ) , repo . ComposeMetas ( ) )
if err != nil {
2019-04-02 02:48:31 -05:00
log . Error ( "Failed to render description for %s (ID: %d): %v" , repo . Name , repo . ID , err )
2019-03-11 21:23:34 -05:00
return template . HTML ( markup . Sanitize ( repo . Description ) )
2014-07-22 13:08:04 -05:00
}
2019-03-11 21:23:34 -05:00
return template . HTML ( markup . Sanitize ( string ( desc ) ) )
2014-07-22 12:52:37 -05:00
}
2015-08-08 04:10:34 -05:00
func isRepositoryExist ( e Engine , u * User , repoName string ) ( bool , error ) {
has , err := e . Get ( & Repository {
2016-07-23 12:08:22 -05:00
OwnerID : u . ID ,
2015-02-23 23:27:22 -06:00
LowerName : strings . ToLower ( repoName ) ,
} )
2015-03-26 16:11:47 -05:00
return has && com . IsDir ( RepoPath ( u . Name , repoName ) ) , err
2014-02-14 08:20:57 -06:00
}
2015-08-08 04:10:34 -05:00
// IsRepositoryExist returns true if the repository with given name under user has already existed.
func IsRepositoryExist ( u * User , repoName string ) ( bool , error ) {
return isRepositoryExist ( x , u , repoName )
}
2014-12-13 15:46:00 -06:00
// CloneLink represents different types of clone URLs of repository.
type CloneLink struct {
SSH string
HTTPS string
Git string
}
2016-08-07 16:29:16 -05:00
// ComposeHTTPSCloneURL returns HTTPS clone URL based on given owner and repository name.
func ComposeHTTPSCloneURL ( owner , repo string ) string {
2019-03-18 09:00:23 -05:00
return fmt . Sprintf ( "%s%s/%s.git" , setting . AppURL , url . PathEscape ( owner ) , url . PathEscape ( repo ) )
2016-08-07 16:29:16 -05:00
}
2018-10-19 11:36:42 -05:00
func ( repo * Repository ) cloneLink ( e Engine , isWiki bool ) * CloneLink {
2015-11-30 19:45:55 -06:00
repoName := repo . Name
if isWiki {
repoName += ".wiki"
2014-12-13 15:46:00 -06:00
}
2015-04-18 05:21:07 -05:00
2017-10-14 10:51:00 -05:00
sshUser := setting . RunUser
if setting . SSH . StartBuiltinServer {
sshUser = setting . SSH . BuiltinServerUser
}
2018-10-19 11:36:42 -05:00
repo . Owner = repo . mustOwner ( e )
2015-11-30 19:45:55 -06:00
cl := new ( CloneLink )
2016-02-27 19:48:39 -06:00
if setting . SSH . Port != 22 {
2017-10-14 10:51:00 -05:00
cl . SSH = fmt . Sprintf ( "ssh://%s@%s:%d/%s/%s.git" , sshUser , setting . SSH . Domain , setting . SSH . Port , repo . Owner . Name , repoName )
2017-08-26 08:57:41 -05:00
} else if setting . Repository . UseCompatSSHURI {
2017-10-14 10:51:00 -05:00
cl . SSH = fmt . Sprintf ( "ssh://%s@%s/%s/%s.git" , sshUser , setting . SSH . Domain , repo . Owner . Name , repoName )
2014-12-13 15:46:00 -06:00
} else {
2017-10-14 10:51:00 -05:00
cl . SSH = fmt . Sprintf ( "%s@%s:%s/%s.git" , sshUser , setting . SSH . Domain , repo . Owner . Name , repoName )
2014-12-13 15:46:00 -06:00
}
2016-08-27 14:29:52 -05:00
cl . HTTPS = ComposeHTTPSCloneURL ( repo . Owner . Name , repoName )
2015-11-30 19:45:55 -06:00
return cl
}
// CloneLink returns clone URLs of repository.
func ( repo * Repository ) CloneLink ( ) ( cl * CloneLink ) {
2018-10-19 11:36:42 -05:00
return repo . cloneLink ( x , false )
2014-12-13 15:46:00 -06:00
}
2019-10-13 08:23:14 -05:00
// CheckCreateRepository check if could created a repository
func CheckCreateRepository ( doer , u * User , name string ) error {
if ! doer . CanCreateRepo ( ) {
return ErrReachLimitOfRepo { u . MaxRepoCreation }
}
if err := IsUsableRepoName ( name ) ; err != nil {
return err
}
has , err := isRepositoryExist ( x , u , name )
2014-04-12 19:35:35 -05:00
if err != nil {
2019-10-13 08:23:14 -05:00
return fmt . Errorf ( "IsRepositoryExist: %v" , err )
} else if has {
return ErrRepoAlreadyExist { u . Name , name }
2014-04-12 19:35:35 -05:00
}
2019-10-13 08:23:14 -05:00
return nil
}
2014-04-12 19:35:35 -05:00
2020-04-06 15:15:20 -05:00
func getHookTemplates ( ) ( hookNames , hookTpls , giteaHookTpls [ ] string ) {
hookNames = [ ] string { "pre-receive" , "update" , "post-receive" }
hookTpls = [ ] string {
fmt . Sprintf ( "#!/usr/bin/env %s\ndata=$(cat)\nexitcodes=\"\"\nhookname=$(basename $0)\nGIT_DIR=${GIT_DIR:-$(dirname $0)}\n\nfor hook in ${GIT_DIR}/hooks/${hookname}.d/*; do\ntest -x \"${hook}\" && test -f \"${hook}\" || continue\necho \"${data}\" | \"${hook}\"\nexitcodes=\"${exitcodes} $?\"\ndone\n\nfor i in ${exitcodes}; do\n[ ${i} -eq 0 ] || exit ${i}\ndone\n" , setting . ScriptType ) ,
fmt . Sprintf ( "#!/usr/bin/env %s\nexitcodes=\"\"\nhookname=$(basename $0)\nGIT_DIR=${GIT_DIR:-$(dirname $0)}\n\nfor hook in ${GIT_DIR}/hooks/${hookname}.d/*; do\ntest -x \"${hook}\" && test -f \"${hook}\" || continue\n\"${hook}\" $1 $2 $3\nexitcodes=\"${exitcodes} $?\"\ndone\n\nfor i in ${exitcodes}; do\n[ ${i} -eq 0 ] || exit ${i}\ndone\n" , setting . ScriptType ) ,
fmt . Sprintf ( "#!/usr/bin/env %s\ndata=$(cat)\nexitcodes=\"\"\nhookname=$(basename $0)\nGIT_DIR=${GIT_DIR:-$(dirname $0)}\n\nfor hook in ${GIT_DIR}/hooks/${hookname}.d/*; do\ntest -x \"${hook}\" && test -f \"${hook}\" || continue\necho \"${data}\" | \"${hook}\"\nexitcodes=\"${exitcodes} $?\"\ndone\n\nfor i in ${exitcodes}; do\n[ ${i} -eq 0 ] || exit ${i}\ndone\n" , setting . ScriptType ) ,
}
giteaHookTpls = [ ] string {
fmt . Sprintf ( "#!/usr/bin/env %s\n\"%s\" hook --config='%s' pre-receive\n" , setting . ScriptType , setting . AppPath , setting . CustomConf ) ,
fmt . Sprintf ( "#!/usr/bin/env %s\n\"%s\" hook --config='%s' update $1 $2 $3\n" , setting . ScriptType , setting . AppPath , setting . CustomConf ) ,
fmt . Sprintf ( "#!/usr/bin/env %s\n\"%s\" hook --config='%s' post-receive\n" , setting . ScriptType , setting . AppPath , setting . CustomConf ) ,
}
return
}
2019-12-14 11:30:01 -06:00
// CreateDelegateHooks creates all the hooks scripts for the repo
func CreateDelegateHooks ( repoPath string ) error {
return createDelegateHooks ( repoPath )
2016-08-11 12:53:51 -05:00
}
2017-02-22 21:40:44 -06:00
// createDelegateHooks creates all the hooks scripts for the repo
func createDelegateHooks ( repoPath string ) ( err error ) {
2020-04-06 15:15:20 -05:00
hookNames , hookTpls , giteaHookTpls := getHookTemplates ( )
2017-02-22 21:40:44 -06:00
hookDir := filepath . Join ( repoPath , "hooks" )
for i , hookName := range hookNames {
oldHookPath := filepath . Join ( hookDir , hookName )
newHookPath := filepath . Join ( hookDir , hookName + ".d" , "gitea" )
if err := os . MkdirAll ( filepath . Join ( hookDir , hookName + ".d" ) , os . ModePerm ) ; err != nil {
return fmt . Errorf ( "create hooks dir '%s': %v" , filepath . Join ( hookDir , hookName + ".d" ) , err )
}
// WARNING: This will override all old server-side hooks
2019-12-27 10:41:49 -06:00
if err = os . Remove ( oldHookPath ) ; err != nil && ! os . IsNotExist ( err ) {
return fmt . Errorf ( "unable to pre-remove old hook file '%s' prior to rewriting: %v " , oldHookPath , err )
}
2017-07-02 08:50:57 -05:00
if err = ioutil . WriteFile ( oldHookPath , [ ] byte ( hookTpls [ i ] ) , 0777 ) ; err != nil {
2017-02-22 21:40:44 -06:00
return fmt . Errorf ( "write old hook file '%s': %v" , oldHookPath , err )
}
2020-04-06 15:15:20 -05:00
if err = ensureExecutable ( oldHookPath ) ; err != nil {
return fmt . Errorf ( "Unable to set %s executable. Error %v" , oldHookPath , err )
}
2019-12-27 10:41:49 -06:00
if err = os . Remove ( newHookPath ) ; err != nil && ! os . IsNotExist ( err ) {
return fmt . Errorf ( "unable to pre-remove new hook file '%s' prior to rewriting: %v" , newHookPath , err )
}
2017-02-22 21:40:44 -06:00
if err = ioutil . WriteFile ( newHookPath , [ ] byte ( giteaHookTpls [ i ] ) , 0777 ) ; err != nil {
return fmt . Errorf ( "write new hook file '%s': %v" , newHookPath , err )
}
2020-04-06 15:15:20 -05:00
if err = ensureExecutable ( newHookPath ) ; err != nil {
return fmt . Errorf ( "Unable to set %s executable. Error %v" , oldHookPath , err )
}
2017-02-22 21:40:44 -06:00
}
return nil
2016-08-30 18:18:33 -05:00
}
2020-04-06 15:15:20 -05:00
func checkExecutable ( filename string ) bool {
fileInfo , err := os . Stat ( filename )
if err != nil {
return false
}
return ( fileInfo . Mode ( ) & 0100 ) > 0
}
func ensureExecutable ( filename string ) error {
fileInfo , err := os . Stat ( filename )
if err != nil {
return err
}
if ( fileInfo . Mode ( ) & 0100 ) > 0 {
return nil
}
mode := fileInfo . Mode ( ) | 0100
return os . Chmod ( filename , mode )
}
// CheckDelegateHooks checks the hooks scripts for the repo
func CheckDelegateHooks ( repoPath string ) ( [ ] string , error ) {
hookNames , hookTpls , giteaHookTpls := getHookTemplates ( )
hookDir := filepath . Join ( repoPath , "hooks" )
results := make ( [ ] string , 0 , 10 )
for i , hookName := range hookNames {
oldHookPath := filepath . Join ( hookDir , hookName )
newHookPath := filepath . Join ( hookDir , hookName + ".d" , "gitea" )
cont := false
if ! com . IsExist ( oldHookPath ) {
results = append ( results , fmt . Sprintf ( "old hook file %s does not exist" , oldHookPath ) )
cont = true
}
if ! com . IsExist ( oldHookPath + ".d" ) {
results = append ( results , fmt . Sprintf ( "hooks directory %s does not exist" , oldHookPath + ".d" ) )
cont = true
}
if ! com . IsExist ( newHookPath ) {
results = append ( results , fmt . Sprintf ( "new hook file %s does not exist" , newHookPath ) )
cont = true
}
if cont {
continue
}
contents , err := ioutil . ReadFile ( oldHookPath )
if err != nil {
return results , err
}
if string ( contents ) != hookTpls [ i ] {
results = append ( results , fmt . Sprintf ( "old hook file %s is out of date" , oldHookPath ) )
}
if ! checkExecutable ( oldHookPath ) {
results = append ( results , fmt . Sprintf ( "old hook file %s is not executable" , oldHookPath ) )
}
contents , err = ioutil . ReadFile ( newHookPath )
if err != nil {
return results , err
}
if string ( contents ) != giteaHookTpls [ i ] {
results = append ( results , fmt . Sprintf ( "new hook file %s is out of date" , newHookPath ) )
}
if ! checkExecutable ( newHookPath ) {
results = append ( results , fmt . Sprintf ( "new hook file %s is not executable" , newHookPath ) )
}
}
return results , nil
}
2016-08-30 18:18:33 -05:00
2014-03-17 10:56:50 -05:00
// initRepoCommit temporarily changes with work directory.
2019-12-27 15:15:04 -06:00
func initRepoCommit ( tmpPath string , repo * Repository , u * User ) ( err error ) {
2019-10-16 08:42:42 -05:00
commitTimeStr := time . Now ( ) . Format ( time . RFC3339 )
sig := u . NewGitSig ( )
// Because this may call hooks we should pass in the environment
env := append ( os . Environ ( ) ,
"GIT_AUTHOR_NAME=" + sig . Name ,
"GIT_AUTHOR_EMAIL=" + sig . Email ,
"GIT_AUTHOR_DATE=" + commitTimeStr ,
"GIT_COMMITTER_NAME=" + sig . Name ,
"GIT_COMMITTER_EMAIL=" + sig . Email ,
"GIT_COMMITTER_DATE=" + commitTimeStr ,
)
2019-11-30 08:40:22 -06:00
if stdout , err := git . NewCommand ( "add" , "--all" ) .
SetDescription ( fmt . Sprintf ( "initRepoCommit (git add): %s" , tmpPath ) ) .
RunInDir ( tmpPath ) ; err != nil {
log . Error ( "git add --all failed: Stdout: %s\nError: %v" , stdout , err )
return fmt . Errorf ( "git add --all: %v" , err )
2014-03-27 14:24:11 -05:00
}
2014-06-19 00:08:03 -05:00
2019-10-16 08:42:42 -05:00
binVersion , err := git . BinVersion ( )
if err != nil {
return fmt . Errorf ( "Unable to get git version: %v" , err )
}
args := [ ] string {
"commit" , fmt . Sprintf ( "--author='%s <%s>'" , sig . Name , sig . Email ) ,
"-m" , "Initial commit" ,
}
if version . Compare ( binVersion , "1.7.9" , ">=" ) {
sign , keyID := SignInitialCommit ( tmpPath , u )
if sign {
args = append ( args , "-S" + keyID )
} else if version . Compare ( binVersion , "2.0.0" , ">=" ) {
args = append ( args , "--no-gpg-sign" )
}
}
2019-11-30 08:40:22 -06:00
if stdout , err := git . NewCommand ( args ... ) .
SetDescription ( fmt . Sprintf ( "initRepoCommit (git commit): %s" , tmpPath ) ) .
RunInDirWithEnv ( tmpPath , env ) ; err != nil {
log . Error ( "Failed to commit: %v: Stdout: %s\nError: %v" , args , stdout , err )
return fmt . Errorf ( "git commit: %v" , err )
2014-03-27 14:24:11 -05:00
}
2019-11-30 08:40:22 -06:00
if stdout , err := git . NewCommand ( "push" , "origin" , "master" ) .
SetDescription ( fmt . Sprintf ( "initRepoCommit (git push): %s" , tmpPath ) ) .
2019-12-27 15:15:04 -06:00
RunInDirWithEnv ( tmpPath , InternalPushingEnvironment ( u , repo ) ) ; err != nil {
2019-11-30 08:40:22 -06:00
log . Error ( "Failed to push back to master: Stdout: %s\nError: %v" , stdout , err )
return fmt . Errorf ( "git push: %v" , err )
2014-03-27 14:24:11 -05:00
}
2019-11-30 08:40:22 -06:00
2014-03-17 10:56:50 -05:00
return nil
2014-02-14 08:20:57 -06:00
}
2016-11-28 11:27:55 -06:00
// CreateRepoOptions contains the create repository options
2015-08-28 05:33:09 -05:00
type CreateRepoOptions struct {
Name string
Description string
2019-07-07 21:14:12 -05:00
OriginalURL string
2015-08-28 05:33:09 -05:00
Gitignores string
2019-09-08 03:28:40 -05:00
IssueLabels string
2015-08-28 05:33:09 -05:00
License string
Readme string
IsPrivate bool
IsMirror bool
AutoInit bool
2019-10-13 08:23:14 -05:00
Status RepositoryStatus
2015-08-28 05:33:09 -05:00
}
func getRepoInitFile ( tp , name string ) ( [ ] byte , error ) {
2018-04-30 20:46:04 -05:00
cleanedName := strings . TrimLeft ( path . Clean ( "/" + name ) , "/" )
2016-12-22 12:12:23 -06:00
relPath := path . Join ( "options" , tp , cleanedName )
2015-08-28 05:33:09 -05:00
// Use custom file when available.
customPath := path . Join ( setting . CustomPath , relPath )
if com . IsFile ( customPath ) {
return ioutil . ReadFile ( customPath )
}
2016-12-22 12:12:23 -06:00
switch tp {
case "readme" :
return options . Readme ( cleanedName )
case "gitignore" :
return options . Gitignore ( cleanedName )
case "license" :
return options . License ( cleanedName )
2016-12-23 01:18:05 -06:00
case "label" :
return options . Labels ( cleanedName )
2016-12-22 12:12:23 -06:00
default :
return [ ] byte { } , fmt . Errorf ( "Invalid init file type" )
}
2015-08-28 05:33:09 -05:00
}
2018-10-19 11:36:42 -05:00
func prepareRepoCommit ( e Engine , repo * Repository , tmpDir , repoPath string , opts CreateRepoOptions ) error {
2019-10-16 08:42:42 -05:00
commitTimeStr := time . Now ( ) . Format ( time . RFC3339 )
authorSig := repo . Owner . NewGitSig ( )
// Because this may call hooks we should pass in the environment
env := append ( os . Environ ( ) ,
"GIT_AUTHOR_NAME=" + authorSig . Name ,
"GIT_AUTHOR_EMAIL=" + authorSig . Email ,
"GIT_AUTHOR_DATE=" + commitTimeStr ,
"GIT_COMMITTER_NAME=" + authorSig . Name ,
"GIT_COMMITTER_EMAIL=" + authorSig . Email ,
"GIT_COMMITTER_DATE=" + commitTimeStr ,
)
2017-01-04 18:50:34 -06:00
// Clone to temporary path and do the init commit.
2019-11-30 08:40:22 -06:00
if stdout , err := git . NewCommand ( "clone" , repoPath , tmpDir ) .
SetDescription ( fmt . Sprintf ( "initRepository (git clone): %s to %s" , repoPath , tmpDir ) ) .
RunInDirWithEnv ( "" , env ) ; err != nil {
log . Error ( "Failed to clone from %v into %s: stdout: %s\nError: %v" , repo , tmpDir , stdout , err )
return fmt . Errorf ( "git clone: %v" , err )
2015-08-28 05:33:09 -05:00
}
// README
data , err := getRepoInitFile ( "readme" , opts . Readme )
if err != nil {
return fmt . Errorf ( "getRepoInitFile[%s]: %v" , opts . Readme , err )
}
2018-10-19 11:36:42 -05:00
cloneLink := repo . cloneLink ( e , false )
2015-08-28 05:33:09 -05:00
match := map [ string ] string {
2015-08-28 05:36:32 -05:00
"Name" : repo . Name ,
"Description" : repo . Description ,
"CloneURL.SSH" : cloneLink . SSH ,
"CloneURL.HTTPS" : cloneLink . HTTPS ,
2015-08-28 05:33:09 -05:00
}
if err = ioutil . WriteFile ( filepath . Join ( tmpDir , "README.md" ) ,
[ ] byte ( com . Expand ( string ( data ) , match ) ) , 0644 ) ; err != nil {
return fmt . Errorf ( "write README.md: %v" , err )
}
// .gitignore
if len ( opts . Gitignores ) > 0 {
var buf bytes . Buffer
names := strings . Split ( opts . Gitignores , "," )
for _ , name := range names {
data , err = getRepoInitFile ( "gitignore" , name )
if err != nil {
return fmt . Errorf ( "getRepoInitFile[%s]: %v" , name , err )
}
buf . WriteString ( "# ---> " + name + "\n" )
buf . Write ( data )
buf . WriteString ( "\n" )
}
if buf . Len ( ) > 0 {
if err = ioutil . WriteFile ( filepath . Join ( tmpDir , ".gitignore" ) , buf . Bytes ( ) , 0644 ) ; err != nil {
return fmt . Errorf ( "write .gitignore: %v" , err )
}
}
}
// LICENSE
if len ( opts . License ) > 0 {
data , err = getRepoInitFile ( "license" , opts . License )
if err != nil {
return fmt . Errorf ( "getRepoInitFile[%s]: %v" , opts . License , err )
}
if err = ioutil . WriteFile ( filepath . Join ( tmpDir , "LICENSE" ) , data , 0644 ) ; err != nil {
return fmt . Errorf ( "write LICENSE: %v" , err )
}
}
return nil
}
2019-11-11 09:15:29 -06:00
func checkInitRepository ( repoPath string ) ( err error ) {
2015-07-26 06:22:17 -05:00
// Somehow the directory could exist.
if com . IsExist ( repoPath ) {
return fmt . Errorf ( "initRepository: path already exists: %s" , repoPath )
}
2019-01-17 18:01:04 -06:00
// Init git bare new repository.
2015-11-26 23:24:24 -06:00
if err = git . InitRepository ( repoPath , true ) ; err != nil {
return fmt . Errorf ( "InitRepository: %v" , err )
2017-02-22 21:40:44 -06:00
} else if err = createDelegateHooks ( repoPath ) ; err != nil {
return fmt . Errorf ( "createDelegateHooks: %v" , err )
2014-03-17 16:00:35 -05:00
}
2019-11-11 09:15:29 -06:00
return nil
}
// InitRepository initializes README and .gitignore if needed.
func initRepository ( e Engine , repoPath string , u * User , repo * Repository , opts CreateRepoOptions ) ( err error ) {
if err = checkInitRepository ( repoPath ) ; err != nil {
return err
}
2014-03-17 16:00:35 -05:00
2015-08-28 05:33:09 -05:00
// Initialize repository according to user's choice.
if opts . AutoInit {
2019-12-15 23:17:55 -06:00
tmpDir , err := ioutil . TempDir ( os . TempDir ( ) , "gitea-" + repo . Name )
if err != nil {
return fmt . Errorf ( "Failed to create temp dir for repository %s: %v" , repo . repoPath ( e ) , err )
2016-11-30 17:56:15 -06:00
}
2015-08-28 05:33:09 -05:00
defer os . RemoveAll ( tmpDir )
2015-03-18 05:37:44 -05:00
2018-10-19 11:36:42 -05:00
if err = prepareRepoCommit ( e , repo , tmpDir , repoPath , opts ) ; err != nil {
2015-08-28 05:33:09 -05:00
return fmt . Errorf ( "prepareRepoCommit: %v" , err )
2014-03-11 00:32:36 -05:00
}
2014-03-10 23:53:53 -05:00
2015-08-28 05:33:09 -05:00
// Apply changes and commit.
2019-12-27 15:15:04 -06:00
if err = initRepoCommit ( tmpDir , repo , u ) ; err != nil {
2015-08-28 05:33:09 -05:00
return fmt . Errorf ( "initRepoCommit: %v" , err )
2014-03-11 00:32:36 -05:00
}
}
2014-03-10 23:18:44 -05:00
2015-08-25 23:26:01 -05:00
// Re-fetch the repository from database before updating it (else it would
// override changes that were done earlier with sql)
if repo , err = getRepositoryByID ( e , repo . ID ) ; err != nil {
return fmt . Errorf ( "getRepositoryByID: %v" , err )
}
2015-08-28 05:33:09 -05:00
2015-08-28 06:34:23 -05:00
if ! opts . AutoInit {
2019-01-17 18:01:04 -06:00
repo . IsEmpty = true
2015-08-28 06:34:23 -05:00
}
2015-08-25 23:26:01 -05:00
repo . DefaultBranch = "master"
if err = updateRepository ( e , repo , false ) ; err != nil {
return fmt . Errorf ( "updateRepository: %v" , err )
}
2015-08-28 05:33:09 -05:00
return nil
2014-03-10 19:48:58 -05:00
}
2016-07-23 05:58:18 -05:00
var (
reservedRepoNames = [ ] string { "." , ".." }
reservedRepoPatterns = [ ] string { "*.git" , "*.wiki" }
)
2016-11-28 11:27:55 -06:00
// IsUsableRepoName returns true when repository is usable
2016-07-23 05:58:18 -05:00
func IsUsableRepoName ( name string ) error {
return isUsableName ( reservedRepoNames , reservedRepoPatterns , name )
}
2019-11-24 11:57:52 -06:00
func createRepository ( e Engine , doer , u * User , repo * Repository ) ( err error ) {
2016-07-23 05:58:18 -05:00
if err = IsUsableRepoName ( repo . Name ) ; err != nil {
2015-08-08 04:10:34 -05:00
return err
2014-06-19 00:08:03 -05:00
}
2015-08-08 04:10:34 -05:00
has , err := isRepositoryExist ( e , u , repo . Name )
2015-03-26 16:11:47 -05:00
if err != nil {
2015-08-08 04:10:34 -05:00
return fmt . Errorf ( "IsRepositoryExist: %v" , err )
2015-03-26 16:11:47 -05:00
} else if has {
2015-08-08 04:10:34 -05:00
return ErrRepoAlreadyExist { u . Name , repo . Name }
2014-06-19 00:08:03 -05:00
}
2015-08-08 04:10:34 -05:00
if _ , err = e . Insert ( repo ) ; err != nil {
return err
2015-08-29 12:13:24 -05:00
}
2017-02-05 08:35:03 -06:00
if err = deleteRepoRedirect ( e , u . ID , repo . Name ) ; err != nil {
return err
}
2015-08-29 12:13:24 -05:00
2017-02-04 09:53:46 -06:00
// insert units for repo
2019-05-30 10:09:05 -05:00
var units = make ( [ ] RepoUnit , 0 , len ( DefaultRepoUnits ) )
for _ , tp := range DefaultRepoUnits {
2017-09-12 01:48:13 -05:00
if tp == UnitTypeIssues {
units = append ( units , RepoUnit {
RepoID : repo . ID ,
Type : tp ,
2018-07-17 16:23:58 -05:00
Config : & IssuesConfig {
EnableTimetracker : setting . Service . DefaultEnableTimetracking ,
AllowOnlyContributorsToTrackTime : setting . Service . DefaultAllowOnlyContributorsToTrackTime ,
EnableDependencies : setting . Service . DefaultEnableDependencies ,
} ,
2017-09-12 01:48:13 -05:00
} )
2018-07-04 22:02:54 -05:00
} else if tp == UnitTypePullRequests {
units = append ( units , RepoUnit {
RepoID : repo . ID ,
Type : tp ,
2018-12-27 04:27:08 -06:00
Config : & PullRequestsConfig { AllowMerge : true , AllowRebase : true , AllowRebaseMerge : true , AllowSquash : true } ,
2018-07-04 22:02:54 -05:00
} )
2017-09-12 01:48:13 -05:00
} else {
units = append ( units , RepoUnit {
RepoID : repo . ID ,
Type : tp ,
} )
}
2017-02-04 09:53:46 -06:00
}
if _ , err = e . Insert ( & units ) ; err != nil {
return err
}
2015-08-29 12:13:24 -05:00
// Remember visibility preference.
u . LastRepoVisibility = repo . IsPrivate
2019-07-17 12:34:13 -05:00
if err = updateUserCols ( e , u , "last_repo_visibility" ) ; err != nil {
2015-08-29 12:13:24 -05:00
return fmt . Errorf ( "updateUser: %v" , err )
2015-08-08 04:10:34 -05:00
}
2019-07-17 12:34:13 -05:00
if _ , err = e . Incr ( "num_repos" ) . ID ( u . ID ) . Update ( new ( User ) ) ; err != nil {
return fmt . Errorf ( "increment user total_repos: %v" , err )
}
u . NumRepos ++
2019-11-06 03:37:14 -06:00
// Give access to all members in teams with access to all repositories.
2015-08-08 04:10:34 -05:00
if u . IsOrganization ( ) {
2019-11-06 03:37:14 -06:00
if err := u . GetTeams ( ) ; err != nil {
return fmt . Errorf ( "GetTeams: %v" , err )
2019-10-26 01:54:11 -05:00
}
2019-11-06 03:37:14 -06:00
for _ , t := range u . Teams {
if t . IncludesAllRepositories {
if err := t . addRepository ( e , repo ) ; err != nil {
return fmt . Errorf ( "addRepository: %v" , err )
}
}
2015-08-08 04:10:34 -05:00
}
2019-11-20 05:27:49 -06:00
if isAdmin , err := isUserRepoAdmin ( e , repo , doer ) ; err != nil {
return fmt . Errorf ( "isUserRepoAdmin: %v" , err )
} else if ! isAdmin {
// Make creator repo admin if it wan't assigned automatically
if err = repo . addCollaborator ( e , doer ) ; err != nil {
return fmt . Errorf ( "AddCollaborator: %v" , err )
}
if err = repo . changeCollaborationAccessMode ( e , doer . ID , AccessModeAdmin ) ; err != nil {
return fmt . Errorf ( "ChangeCollaborationAccessMode: %v" , err )
}
}
2019-06-12 14:41:28 -05:00
} else if err = repo . recalculateAccesses ( e ) ; err != nil {
2015-08-08 04:10:34 -05:00
// Organization automatically called this in addRepository method.
2019-06-12 14:41:28 -05:00
return fmt . Errorf ( "recalculateAccesses: %v" , err )
2015-08-08 04:10:34 -05:00
}
2019-01-27 03:25:21 -06:00
if setting . Service . AutoWatchNewRepos {
if err = watchRepo ( e , doer . ID , repo . ID , true ) ; err != nil {
return fmt . Errorf ( "watchRepo: %v" , err )
}
}
2015-08-08 04:10:34 -05:00
2019-03-18 21:33:20 -05:00
if err = copyDefaultWebhooksToRepo ( e , repo . ID ) ; err != nil {
return fmt . Errorf ( "copyDefaultWebhooksToRepo: %v" , err )
}
2015-08-08 04:10:34 -05:00
return nil
}
2019-02-03 17:21:08 -06:00
// CreateRepository creates a repository for the user/organization.
2017-09-03 03:20:24 -05:00
func CreateRepository ( doer , u * User , opts CreateRepoOptions ) ( _ * Repository , err error ) {
2018-08-08 20:04:16 -05:00
if ! doer . IsAdmin && ! u . CanCreateRepo ( ) {
2015-12-10 11:37:53 -06:00
return nil , ErrReachLimitOfRepo { u . MaxRepoCreation }
}
2014-06-19 00:08:03 -05:00
repo := & Repository {
2019-02-10 13:27:19 -06:00
OwnerID : u . ID ,
Owner : u ,
Name : opts . Name ,
LowerName : strings . ToLower ( opts . Name ) ,
Description : opts . Description ,
2019-07-07 21:14:12 -05:00
OriginalURL : opts . OriginalURL ,
2019-02-10 13:27:19 -06:00
IsPrivate : opts . IsPrivate ,
IsFsckEnabled : ! opts . IsMirror ,
CloseIssuesViaCommitInAnyBranch : setting . Repository . DefaultCloseIssuesViaCommitsInAnyBranch ,
2019-10-13 08:23:14 -05:00
Status : opts . Status ,
2019-10-16 03:59:02 -05:00
IsEmpty : ! opts . AutoInit ,
2014-06-19 00:08:03 -05:00
}
2015-02-12 23:58:46 -06:00
sess := x . NewSession ( )
2017-06-20 19:57:05 -05:00
defer sess . Close ( )
2015-02-12 23:58:46 -06:00
if err = sess . Begin ( ) ; err != nil {
return nil , err
}
2017-09-03 03:20:24 -05:00
if err = createRepository ( sess , doer , u , repo ) ; err != nil {
2015-02-13 01:14:57 -06:00
return nil , err
2014-06-19 00:08:03 -05:00
}
2014-07-25 23:24:27 -05:00
// No need for init mirror.
2015-08-28 05:33:09 -05:00
if ! opts . IsMirror {
2015-02-12 23:58:46 -06:00
repoPath := RepoPath ( u . Name , repo . Name )
2015-08-28 05:33:09 -05:00
if err = initRepository ( sess , repoPath , u , repo , opts ) ; err != nil {
2015-02-12 23:58:46 -06:00
if err2 := os . RemoveAll ( repoPath ) ; err2 != nil {
2019-04-02 02:48:31 -05:00
log . Error ( "initRepository: %v" , err )
2015-02-12 23:58:46 -06:00
return nil , fmt . Errorf (
"delete repo directory %s/%s failed(2): %v" , u . Name , repo . Name , err2 )
}
return nil , fmt . Errorf ( "initRepository: %v" , err )
2014-06-25 04:14:36 -05:00
}
2014-06-19 00:08:03 -05:00
2019-09-08 03:28:40 -05:00
// Initialize Issue Labels if selected
if len ( opts . IssueLabels ) > 0 {
if err = initalizeLabels ( sess , repo . ID , opts . IssueLabels ) ; err != nil {
return nil , fmt . Errorf ( "initalizeLabels: %v" , err )
}
}
2019-11-30 08:40:22 -06:00
if stdout , err := git . NewCommand ( "update-server-info" ) .
SetDescription ( fmt . Sprintf ( "CreateRepository(git update-server-info): %s" , repoPath ) ) .
RunInDir ( repoPath ) ; err != nil {
log . Error ( "CreateRepitory(git update-server-info) in %v: Stdout: %s\nError: %v" , repo , stdout , err )
return nil , fmt . Errorf ( "CreateRepository(git update-server-info): %v" , err )
2015-02-12 23:58:46 -06:00
}
2014-06-19 00:08:03 -05:00
}
2019-07-31 01:13:04 -05:00
if err = sess . Commit ( ) ; err != nil {
return nil , err
}
return repo , err
2014-06-19 00:08:03 -05:00
}
2016-07-24 01:32:46 -05:00
func countRepositories ( userID int64 , private bool ) int64 {
sess := x . Where ( "id > 0" )
2015-09-04 04:54:22 -05:00
2016-07-24 01:32:46 -05:00
if userID > 0 {
sess . And ( "owner_id = ?" , userID )
}
if ! private {
sess . And ( "is_private=?" , false )
2015-09-04 04:54:22 -05:00
}
2016-02-10 16:30:24 -06:00
count , err := sess . Count ( new ( Repository ) )
if err != nil {
2019-04-02 02:48:31 -05:00
log . Error ( "countRepositories: %v" , err )
2016-02-10 16:30:24 -06:00
}
2015-09-04 04:54:22 -05:00
return count
}
2014-07-07 03:15:08 -05:00
// CountRepositories returns number of repositories.
2016-07-24 01:32:46 -05: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 04:54:22 -05:00
}
2016-07-24 01:32:46 -05: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 03:15:08 -05:00
}
2015-09-25 18:07:21 -05:00
2014-04-12 19:35:35 -05:00
// RepoPath returns repository path by given user and repository name.
2014-03-20 15:04:56 -05:00
func RepoPath ( userName , repoName string ) string {
2014-03-29 21:13:02 -05:00
return filepath . Join ( UserPath ( userName ) , strings . ToLower ( repoName ) + ".git" )
2014-03-20 15:04:56 -05:00
}
2014-04-04 17:31:09 -05:00
// TransferOwnership transfers all corresponding setting from old user to new one.
2016-08-17 01:06:38 -05:00
func TransferOwnership ( doer * User , newOwnerName string , repo * Repository ) error {
2015-02-23 01:15:53 -06:00
newOwner , err := GetUserByName ( newOwnerName )
2014-04-04 17:31:09 -05:00
if err != nil {
2015-02-23 23:27:22 -06:00
return fmt . Errorf ( "get new owner '%s': %v" , newOwnerName , err )
2014-04-04 17:31:09 -05:00
}
2014-09-12 17:29:58 -05:00
// Check if new owner has repository with same name.
2015-03-26 16:11:47 -05:00
has , err := IsRepositoryExist ( newOwner , repo . Name )
if err != nil {
return fmt . Errorf ( "IsRepositoryExist: %v" , err )
} else if has {
2015-08-08 04:10:34 -05:00
return ErrRepoAlreadyExist { newOwnerName , repo . Name }
2014-09-12 17:29:58 -05:00
}
2014-06-20 23:51:41 -05:00
sess := x . NewSession ( )
2017-06-20 19:57:05 -05:00
defer sess . Close ( )
2014-04-04 17:55:17 -05:00
if err = sess . Begin ( ) ; err != nil {
2015-02-23 23:27:22 -06:00
return fmt . Errorf ( "sess.Begin: %v" , err )
2014-04-04 17:55:17 -05:00
}
2019-12-05 22:00:50 -06:00
oldOwner := repo . Owner
2014-04-04 17:31:09 -05:00
2015-02-23 23:27:22 -06:00
// Note: we have to set value here to make sure recalculate accesses is based on
2016-08-17 01:06:38 -05:00
// new owner.
2016-07-23 12:08:22 -05:00
repo . OwnerID = newOwner . ID
2015-02-23 01:15:53 -06:00
repo . Owner = newOwner
2015-02-23 23:27:22 -06:00
// Update repository.
2017-10-04 23:43:04 -05:00
if _ , err := sess . ID ( repo . ID ) . Update ( repo ) ; err != nil {
2015-02-23 23:27:22 -06:00
return fmt . Errorf ( "update owner: %v" , err )
2014-04-04 17:31:09 -05:00
}
2015-02-23 23:27:22 -06:00
// Remove redundant collaborators.
2015-11-03 12:00:04 -06:00
collaborators , err := repo . getCollaborators ( sess )
2015-02-16 05:25:55 -06:00
if err != nil {
2015-11-03 12:00:04 -06:00
return fmt . Errorf ( "getCollaborators: %v" , err )
2015-02-16 05:25:55 -06:00
}
2015-02-23 23:27:22 -06:00
// Dummy object.
2015-08-08 09:43:14 -05:00
collaboration := & Collaboration { RepoID : repo . ID }
2015-02-16 05:25:55 -06:00
for _ , c := range collaborators {
2017-12-21 01:43:26 -06:00
if c . ID != newOwner . ID {
2019-01-13 20:29:58 -06:00
isMember , err := isOrganizationMember ( sess , newOwner . ID , c . ID )
2017-12-21 01:43:26 -06:00
if err != nil {
return fmt . Errorf ( "IsOrgMember: %v" , err )
} else if ! isMember {
continue
2015-02-16 05:25:55 -06:00
}
}
2017-12-21 01:43:26 -06:00
collaboration . UserID = c . ID
if _ , err = sess . Delete ( collaboration ) ; err != nil {
return fmt . Errorf ( "remove collaborator '%d': %v" , c . ID , err )
}
2015-02-16 05:25:55 -06:00
}
2015-02-28 20:44:09 -06:00
// Remove old team-repository relations.
2019-12-05 22:00:50 -06:00
if oldOwner . IsOrganization ( ) {
if err = oldOwner . removeOrgRepo ( sess , repo . ID ) ; err != nil {
2015-02-28 20:44:09 -06:00
return fmt . Errorf ( "removeOrgRepo: %v" , err )
}
}
2015-02-23 01:15:53 -06:00
if newOwner . IsOrganization ( ) {
2019-11-06 03:37:14 -06:00
if err := newOwner . GetTeams ( ) ; err != nil {
return fmt . Errorf ( "GetTeams: %v" , err )
}
for _ , t := range newOwner . Teams {
if t . IncludesAllRepositories {
if err := t . addRepository ( sess , repo ) ; err != nil {
return fmt . Errorf ( "addRepository: %v" , err )
}
}
2015-02-23 23:27:22 -06:00
}
2019-06-12 14:41:28 -05:00
} else if err = repo . recalculateAccesses ( sess ) ; err != nil {
2015-02-23 23:27:22 -06:00
// Organization called this in addRepository method.
2019-06-12 14:41:28 -05:00
return fmt . Errorf ( "recalculateAccesses: %v" , err )
2015-02-23 01:15:53 -06:00
}
2015-02-23 23:27:22 -06:00
// Update repository count.
2016-07-23 12:08:22 -05:00
if _ , err = sess . Exec ( "UPDATE `user` SET num_repos=num_repos+1 WHERE id=?" , newOwner . ID ) ; err != nil {
2015-02-23 23:27:22 -06:00
return fmt . Errorf ( "increase new owner repository count: %v" , err )
2019-12-05 22:00:50 -06:00
} else if _ , err = sess . Exec ( "UPDATE `user` SET num_repos=num_repos-1 WHERE id=?" , oldOwner . ID ) ; err != nil {
2015-02-23 23:27:22 -06:00
return fmt . Errorf ( "decrease old owner repository count: %v" , err )
}
2017-10-14 09:37:43 -05:00
if err = watchRepo ( sess , doer . ID , repo . ID , true ) ; err != nil {
2015-02-23 23:27:22 -06:00
return fmt . Errorf ( "watchRepo: %v" , err )
2019-11-15 02:06:11 -06:00
}
// Remove watch for organization.
2019-12-05 22:00:50 -06:00
if oldOwner . IsOrganization ( ) {
if err = watchRepo ( sess , oldOwner . ID , repo . ID , false ) ; err != nil {
2019-11-15 02:06:11 -06:00
return fmt . Errorf ( "watchRepo [false]: %v" , err )
}
2014-07-04 21:43:39 -05:00
}
2016-02-14 18:42:38 -06:00
// Rename remote repository to new path and delete local copy.
2016-11-30 17:56:15 -06:00
dir := UserPath ( newOwner . Name )
if err := os . MkdirAll ( dir , os . ModePerm ) ; err != nil {
2017-01-29 14:13:57 -06:00
return fmt . Errorf ( "Failed to create dir %s: %v" , dir , err )
2016-11-30 17:56:15 -06:00
}
2019-12-05 22:00:50 -06:00
if err = os . Rename ( RepoPath ( oldOwner . Name , repo . Name ) , RepoPath ( newOwner . Name , repo . Name ) ) ; err != nil {
2015-11-30 19:45:55 -06:00
return fmt . Errorf ( "rename repository directory: %v" , err )
2015-12-03 01:08:25 -06:00
}
2016-02-14 18:42:38 -06:00
// Rename remote wiki repository to new path and delete local copy.
2019-12-05 22:00:50 -06:00
wikiPath := WikiPath ( oldOwner . Name , repo . Name )
2015-12-03 01:08:25 -06:00
if com . IsExist ( wikiPath ) {
if err = os . Rename ( wikiPath , WikiPath ( newOwner . Name , repo . Name ) ) ; err != nil {
return fmt . Errorf ( "rename repository wiki: %v" , err )
}
2014-04-04 17:55:17 -05:00
}
2019-03-02 07:07:19 -06:00
// If there was previously a redirect at this location, remove it.
if err = deleteRepoRedirect ( sess , newOwner . ID , repo . Name ) ; err != nil {
return fmt . Errorf ( "delete repo redirect: %v" , err )
}
2019-12-05 22:00:50 -06:00
if err := NewRepoRedirect ( DBContext { sess } , oldOwner . ID , repo . ID , repo . Name , repo . Name ) ; err != nil {
return fmt . Errorf ( "NewRepoRedirect: %v" , err )
}
2015-02-12 23:58:46 -06:00
return sess . Commit ( )
2014-04-04 17:31:09 -05:00
}
2014-04-03 14:50:55 -05:00
// ChangeRepositoryName changes all corresponding setting from old repository name to new one.
2019-11-15 02:06:11 -06:00
func ChangeRepositoryName ( doer * User , repo * Repository , newRepoName string ) ( err error ) {
2019-12-05 22:00:50 -06:00
oldRepoName := repo . Name
2014-12-12 00:29:36 -06:00
newRepoName = strings . ToLower ( newRepoName )
2016-07-23 05:58:18 -05:00
if err = IsUsableRepoName ( newRepoName ) ; err != nil {
2015-03-26 16:11:47 -05:00
return err
}
2019-11-15 02:06:11 -06:00
if err := repo . GetOwner ( ) ; err != nil {
return err
2014-08-24 08:09:05 -05:00
}
2019-11-15 02:06:11 -06:00
has , err := IsRepositoryExist ( repo . Owner , newRepoName )
2016-02-05 13:11:53 -06:00
if err != nil {
2019-11-15 02:06:11 -06:00
return fmt . Errorf ( "IsRepositoryExist: %v" , err )
} else if has {
return ErrRepoAlreadyExist { repo . Owner . Name , newRepoName }
2016-02-05 13:11:53 -06:00
}
2019-11-15 02:06:11 -06:00
newRepoPath := RepoPath ( repo . Owner . Name , newRepoName )
2018-01-27 11:54:26 -06:00
if err = os . Rename ( repo . RepoPath ( ) , newRepoPath ) ; err != nil {
2015-11-30 19:45:55 -06:00
return fmt . Errorf ( "rename repository directory: %v" , err )
}
2015-12-03 00:59:32 -06:00
2016-02-05 13:11:53 -06:00
wikiPath := repo . WikiPath ( )
2015-12-03 00:59:32 -06:00
if com . IsExist ( wikiPath ) {
2019-11-15 02:06:11 -06:00
if err = os . Rename ( wikiPath , WikiPath ( repo . Owner . Name , newRepoName ) ) ; err != nil {
2015-12-03 01:08:25 -06:00
return fmt . Errorf ( "rename repository wiki: %v" , err )
}
2015-12-03 00:59:32 -06:00
}
2015-12-03 01:08:25 -06:00
2019-03-02 07:07:19 -06:00
sess := x . NewSession ( )
defer sess . Close ( )
if err = sess . Begin ( ) ; err != nil {
return fmt . Errorf ( "sess.Begin: %v" , err )
}
// If there was previously a redirect at this location, remove it.
2019-11-15 02:06:11 -06:00
if err = deleteRepoRedirect ( sess , repo . OwnerID , newRepoName ) ; err != nil {
2019-03-02 07:07:19 -06:00
return fmt . Errorf ( "delete repo redirect: %v" , err )
}
2019-12-05 22:00:50 -06:00
if err := NewRepoRedirect ( DBContext { sess } , repo . Owner . ID , repo . ID , oldRepoName , newRepoName ) ; err != nil {
return err
}
2019-03-02 07:07:19 -06:00
return sess . Commit ( )
2014-04-03 14:50:55 -05:00
}
2015-09-01 10:43:53 -05:00
func getRepositoriesByForkID ( e Engine , forkID int64 ) ( [ ] * Repository , error ) {
repos := make ( [ ] * Repository , 0 , 10 )
2016-11-10 09:16:32 -06:00
return repos , e .
Where ( "fork_id=?" , forkID ) .
Find ( & repos )
2015-09-01 10:43:53 -05:00
}
// GetRepositoriesByForkID returns all repositories with given fork ID.
func GetRepositoriesByForkID ( forkID int64 ) ( [ ] * Repository , error ) {
return getRepositoriesByForkID ( x , forkID )
}
2015-03-16 03:52:11 -05:00
func updateRepository ( e Engine , repo * Repository , visibilityChanged bool ) ( err error ) {
2014-04-03 14:50:55 -05:00
repo . LowerName = strings . ToLower ( repo . Name )
2014-03-22 15:00:46 -05:00
if len ( repo . Description ) > 255 {
repo . Description = repo . Description [ : 255 ]
}
if len ( repo . Website ) > 255 {
repo . Website = repo . Website [ : 255 ]
}
2015-03-16 03:52:11 -05:00
2017-10-04 23:43:04 -05:00
if _ , err = e . ID ( repo . ID ) . AllCols ( ) . Update ( repo ) ; err != nil {
2015-03-16 03:52:11 -05:00
return fmt . Errorf ( "update: %v" , err )
}
if visibilityChanged {
if err = repo . getOwner ( e ) ; err != nil {
return fmt . Errorf ( "getOwner: %v" , err )
}
2015-10-08 21:38:42 -05:00
if repo . Owner . IsOrganization ( ) {
2017-01-04 18:50:34 -06:00
// Organization repository need to recalculate access table when visibility is changed.
2015-10-08 21:38:42 -05:00
if err = repo . recalculateTeamAccesses ( e , 0 ) ; err != nil {
return fmt . Errorf ( "recalculateTeamAccesses: %v" , err )
}
2015-03-16 03:52:11 -05:00
}
2015-09-01 10:43:53 -05:00
2017-02-11 04:57:57 -06: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-10 22:08:09 -05:00
// Create/Remove git-daemon-export-ok for git-daemon...
2018-10-26 04:37:57 -05:00
daemonExportFile := path . Join ( repo . repoPath ( e ) , ` git-daemon-export-ok ` )
2016-08-10 22:08:09 -05:00
if repo . IsPrivate && com . IsExist ( daemonExportFile ) {
if err = os . Remove ( daemonExportFile ) ; err != nil {
2019-04-02 02:48:31 -05:00
log . Error ( "Failed to remove %s: %v" , daemonExportFile , err )
2016-08-10 22:08:09 -05:00
}
} else if ! repo . IsPrivate && ! com . IsExist ( daemonExportFile ) {
if f , err := os . Create ( daemonExportFile ) ; err != nil {
2019-04-02 02:48:31 -05:00
log . Error ( "Failed to create %s: %v" , daemonExportFile , err )
2016-08-10 22:08:09 -05:00
} else {
f . Close ( )
}
}
2015-09-01 10:43:53 -05:00
forkRepos , err := getRepositoriesByForkID ( e , repo . ID )
if err != nil {
return fmt . Errorf ( "getRepositoriesByForkID: %v" , err )
}
for i := range forkRepos {
forkRepos [ i ] . IsPrivate = repo . IsPrivate
if err = updateRepository ( e , forkRepos [ i ] , true ) ; err != nil {
return fmt . Errorf ( "updateRepository[%d]: %v" , forkRepos [ i ] . ID , err )
}
}
2017-04-11 08:30:15 -05:00
2017-05-26 00:08:13 -05:00
if err = repo . updateSize ( e ) ; err != nil {
2019-04-02 02:48:31 -05:00
log . Error ( "Failed to update size for repository: %v" , err )
2017-04-11 08:30:15 -05:00
}
2015-03-16 03:52:11 -05:00
}
return nil
2014-03-22 03:44:57 -05:00
}
2016-11-28 11:27:55 -06:00
// UpdateRepository updates a repository
2015-03-16 03:52:11 -05:00
func UpdateRepository ( repo * Repository , visibilityChanged bool ) ( err error ) {
sess := x . NewSession ( )
2017-06-20 19:57:05 -05:00
defer sess . Close ( )
2015-03-16 03:52:11 -05:00
if err = sess . Begin ( ) ; err != nil {
return err
}
2017-01-13 20:07:04 -06:00
if err = updateRepository ( sess , repo , visibilityChanged ) ; err != nil {
2015-03-16 03:52:11 -05:00
return fmt . Errorf ( "updateRepository: %v" , err )
}
return sess . Commit ( )
2015-02-12 23:58:46 -06:00
}
2019-10-01 08:40:17 -05:00
// UpdateRepositoryUpdatedTime updates a repository's updated time
func UpdateRepositoryUpdatedTime ( repoID int64 , updateTime time . Time ) error {
_ , err := x . Exec ( "UPDATE repository SET updated_unix = ? WHERE id = ?" , updateTime . Unix ( ) , repoID )
return err
}
2017-02-04 09:53:46 -06:00
// UpdateRepositoryUnits updates a repository's units
func UpdateRepositoryUnits ( repo * Repository , units [ ] RepoUnit ) ( err error ) {
sess := x . NewSession ( )
defer sess . Close ( )
if err = sess . Begin ( ) ; err != nil {
return err
}
if _ , err = sess . Where ( "repo_id = ?" , repo . ID ) . Delete ( new ( RepoUnit ) ) ; err != nil {
return err
}
if _ , err = sess . Insert ( units ) ; err != nil {
return err
}
return sess . Commit ( )
}
2014-12-06 19:22:48 -06:00
// DeleteRepository deletes a repository for a user or organization.
2017-09-03 03:20:24 -05:00
func DeleteRepository ( doer * User , uid , repoID int64 ) error {
2014-08-27 03:39:36 -05:00
// In case is a organization.
2015-08-08 09:43:14 -05:00
org , err := GetUserByID ( uid )
2014-08-27 03:39:36 -05:00
if err != nil {
return err
}
if org . IsOrganization ( ) {
if err = org . GetTeams ( ) ; err != nil {
return err
}
}
2014-06-20 23:51:41 -05:00
sess := x . NewSession ( )
2017-06-20 19:57:05 -05:00
defer sess . Close ( )
2014-04-04 17:31:09 -05:00
if err = sess . Begin ( ) ; err != nil {
2014-03-20 15:04:56 -05:00
return err
}
2014-06-27 23:40:07 -05:00
2017-02-28 22:05:45 -06:00
repo := & Repository { ID : repoID , OwnerID : uid }
has , err := sess . Get ( repo )
if err != nil {
return err
} else if ! has {
2017-12-02 01:34:39 -06:00
return ErrRepoNotExist { repoID , uid , "" , "" }
2017-02-28 22:05:45 -06:00
}
2019-02-03 17:56:53 -06:00
// Delete Deploy Keys
deployKeys , err := listDeployKeys ( sess , repo . ID )
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-04 23:43:04 -05:00
if cnt , err := sess . ID ( repoID ) . Delete ( & Repository { } ) ; err != nil {
2017-02-28 22:05:45 -06:00
return err
} else if cnt != 1 {
2017-12-02 01:34:39 -06:00
return ErrRepoNotExist { repoID , uid , "" , "" }
2017-02-28 22:05:45 -06:00
}
2014-08-27 03:39:36 -05:00
if org . IsOrganization ( ) {
for _ , t := range org . Teams {
2015-02-23 01:15:53 -06:00
if ! t . hasRepository ( sess , repoID ) {
2014-08-27 03:39:36 -05:00
continue
2015-02-28 20:44:09 -06:00
} else if err = t . removeRepository ( sess , repo , false ) ; err != nil {
2014-08-27 03:39:36 -05:00
return err
}
}
}
2019-12-11 23:31:05 -06:00
attachments := make ( [ ] * Attachment , 0 , 20 )
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 ++ {
releaseAttachments = append ( releaseAttachments , attachments [ i ] . LocalPath ( ) )
}
2015-11-30 19:45:55 -06:00
if err = deleteBeans ( sess ,
& Access { RepoID : repo . ID } ,
& Action { RepoID : repo . ID } ,
& Watch { RepoID : repoID } ,
2015-11-30 19:50:40 -06:00
& Star { RepoID : repoID } ,
2015-11-30 19:45:55 -06:00
& Mirror { RepoID : repoID } ,
& Milestone { RepoID : repoID } ,
& Release { RepoID : repoID } ,
& Collaboration { RepoID : repoID } ,
& PullRequest { BaseRepoID : repoID } ,
2017-05-18 09:54:24 -05:00
& RepoUnit { RepoID : repoID } ,
2017-05-23 03:00:10 -05:00
& RepoRedirect { RedirectRepoID : repoID } ,
2018-05-21 08:30:30 -05:00
& Webhook { RepoID : repoID } ,
& HookTask { RepoID : repoID } ,
2018-12-10 14:01:01 -06:00
& Notification { RepoID : repoID } ,
2019-02-02 22:03:45 -06:00
& CommitStatus { RepoID : repoID } ,
2019-08-04 01:53:17 -05:00
& RepoIndexerStatus { RepoID : repoID } ,
2019-09-20 00:45:38 -05:00
& Comment { RefRepoID : repoID } ,
2019-10-13 08:23:14 -05:00
& Task { RepoID : repoID } ,
2015-11-30 19:45:55 -06:00
) ; err != nil {
return fmt . Errorf ( "deleteBeans: %v" , err )
2014-05-14 12:04:57 -05:00
}
2018-11-30 06:59:12 -06:00
deleteCond := builder . Select ( "id" ) . From ( "issue" ) . Where ( builder . Eq { "repo_id" : repoID } )
// Delete comments and attachments
if _ , err = sess . In ( "issue_id" , deleteCond ) .
Delete ( & Comment { } ) ; err != nil {
2015-02-23 01:15:53 -06:00
return err
}
2017-02-28 22:05:45 -06:00
2020-03-05 07:56:53 -06:00
// Dependencies for issues in this repository
if _ , err = sess . In ( "issue_id" , deleteCond ) .
Delete ( & IssueDependency { } ) ; err != nil {
return err
}
// Delete dependencies for issues in other repositories
if _ , err = sess . In ( "dependency_id" , deleteCond ) .
Delete ( & IssueDependency { } ) ; err != nil {
return err
}
2018-11-30 06:59:12 -06:00
if _ , err = sess . In ( "issue_id" , deleteCond ) .
Delete ( & IssueUser { } ) ; err != nil {
return err
}
2015-08-13 14:07:20 -05:00
2018-11-30 06:59:12 -06:00
if _ , err = sess . In ( "issue_id" , deleteCond ) .
Delete ( & Reaction { } ) ; err != nil {
return err
}
2015-08-13 14:07:20 -05:00
2018-11-30 06:59:12 -06:00
if _ , err = sess . In ( "issue_id" , deleteCond ) .
Delete ( & IssueWatch { } ) ; err != nil {
return err
}
2014-05-14 12:04:57 -05:00
2018-11-30 06:59:12 -06:00
if _ , err = sess . In ( "issue_id" , deleteCond ) .
Delete ( & Stopwatch { } ) ; err != nil {
return err
}
2020-04-18 21:39:48 -05:00
if _ , err = sess . In ( "issue_id" , deleteCond ) .
Delete ( & TrackedTime { } ) ; err != nil {
return err
}
2019-12-11 23:31:05 -06:00
attachments = attachments [ : 0 ]
2018-11-30 06:59:12 -06:00
if err = sess . Join ( "INNER" , "issue" , "issue.id = attachment.issue_id" ) .
Where ( "issue.repo_id = ?" , repoID ) .
Find ( & attachments ) ; err != nil {
return err
}
2019-12-11 23:31:05 -06:00
attachmentPaths := make ( [ ] string , 0 , len ( attachments ) )
2018-11-30 06:59:12 -06:00
for j := range attachments {
attachmentPaths = append ( attachmentPaths , attachments [ j ] . LocalPath ( ) )
}
if _ , err = sess . In ( "issue_id" , deleteCond ) .
Delete ( & Attachment { } ) ; err != nil {
return err
}
if _ , err = sess . Delete ( & Issue { RepoID : repoID } ) ; err != nil {
return err
2014-05-14 08:23:33 -05:00
}
2014-10-19 00:35:24 -05:00
2017-02-04 09:53:46 -06:00
if _ , err = sess . Where ( "repo_id = ?" , repoID ) . Delete ( new ( RepoUnit ) ) ; err != nil {
return err
}
2014-10-13 14:23:30 -05:00
if repo . IsFork {
2015-08-08 09:43:14 -05:00
if _ , err = sess . Exec ( "UPDATE `repository` SET num_forks=num_forks-1 WHERE id=?" , repo . ForkID ) ; err != nil {
2015-09-01 10:43:53 -05:00
return fmt . Errorf ( "decrease fork count: %v" , err )
2014-10-19 00:35:24 -05:00
}
2014-10-13 14:23:30 -05:00
}
2014-04-12 19:35:35 -05:00
2015-02-23 01:15:53 -06:00
if _ , err = sess . Exec ( "UPDATE `user` SET num_repos=num_repos-1 WHERE id=?" , uid ) ; err != nil {
2014-04-12 19:35:35 -05:00
return err
}
2014-10-08 17:29:18 -05:00
2020-01-31 02:04:49 -06:00
if len ( repo . Topics ) > 0 {
if err = removeTopicsFromRepo ( sess , repo . ID ) ; err != nil {
return err
}
}
2017-02-24 09:19:13 -06:00
// FIXME: Remove repository files should be executed after transaction succeed.
2015-11-26 16:33:45 -06:00
repoPath := repo . repoPath ( sess )
2017-02-24 09:19:13 -06:00
removeAllWithNotice ( sess , "Delete repository files" , repoPath )
2015-11-30 19:45:55 -06:00
2019-06-12 14:41:28 -05:00
err = repo . deleteWiki ( sess )
if err != nil {
return err
}
2015-02-13 01:14:57 -06:00
2016-12-25 19:16:37 -06:00
// Remove LFS objects
var lfsObjects [ ] * LFSMetaObject
if err = sess . Where ( "repository_id=?" , repoID ) . Find ( & lfsObjects ) ; err != nil {
return err
}
for _ , v := range lfsObjects {
count , err := sess . Count ( & LFSMetaObject { Oid : v . Oid } )
if err != nil {
return err
}
if count > 1 {
continue
}
2019-10-10 16:08:33 -05:00
oidPath := filepath . Join ( setting . LFS . ContentPath , v . Oid [ 0 : 2 ] , v . Oid [ 2 : 4 ] , v . Oid [ 4 : len ( v . Oid ) ] )
2019-06-15 17:20:29 -05:00
removeAllWithNotice ( sess , "Delete orphaned LFS file" , oidPath )
2016-12-25 19:16:37 -06:00
}
if _ , err := sess . Delete ( & LFSMetaObject { RepositoryID : repoID } ) ; err != nil {
return err
}
2015-09-01 10:43:53 -05:00
if repo . NumForks > 0 {
2017-01-27 10:11:41 -06:00
if _ , err = sess . Exec ( "UPDATE `repository` SET fork_id=0,is_fork=? WHERE fork_id=?" , false , repo . ID ) ; err != nil {
2019-04-02 02:48:31 -05:00
log . Error ( "reset 'fork_id' and 'is_fork': %v" , err )
2016-07-09 00:42:05 -05:00
}
2015-09-01 10:43:53 -05:00
}
2017-01-27 10:11:41 -06:00
if err = sess . Commit ( ) ; err != nil {
2019-02-03 17:56:53 -06:00
if len ( deployKeys ) > 0 {
// We need to rewrite the public keys because the commit failed
if err2 := RewriteAllPublicKeys ( ) ; err2 != nil {
return fmt . Errorf ( "Commit: %v SSH Keys: %v" , err , err2 )
}
}
2017-01-27 10:11:41 -06:00
return fmt . Errorf ( "Commit: %v" , err )
}
2019-12-11 23:31:05 -06:00
sess . Close ( )
// We should always delete the files after the database transaction succeed. If
// we delete the file but the database rollback, the repository will be borken.
// Remove issue attachment files.
for i := range attachmentPaths {
removeAllWithNotice ( x , "Delete issue attachment" , attachmentPaths [ i ] )
}
// Remove release attachment files.
for i := range releaseAttachments {
removeAllWithNotice ( x , "Delete release attachment" , releaseAttachments [ i ] )
}
2019-05-29 21:22:26 -05:00
if len ( repo . Avatar ) > 0 {
avatarPath := repo . CustomAvatarPath ( )
if com . IsExist ( avatarPath ) {
if err := os . Remove ( avatarPath ) ; err != nil {
return fmt . Errorf ( "Failed to remove %s: %v" , avatarPath , err )
}
}
}
2015-09-01 10:43:53 -05:00
return nil
2014-03-20 15:04:56 -05:00
}
2017-12-02 01:34:39 -06:00
// GetRepositoryByOwnerAndName returns the repository by given ownername and reponame.
func GetRepositoryByOwnerAndName ( ownerName , repoName string ) ( * Repository , error ) {
2019-09-20 00:45:38 -05:00
return getRepositoryByOwnerAndName ( x , ownerName , repoName )
}
func getRepositoryByOwnerAndName ( e Engine , ownerName , repoName string ) ( * Repository , error ) {
2017-12-02 01:34:39 -06:00
var repo Repository
2019-09-20 00:45:38 -05:00
has , err := e . Table ( "repository" ) . Select ( "repository.*" ) .
2018-07-19 21:10:17 -05:00
Join ( "INNER" , "`user`" , "`user`.id = repository.owner_id" ) .
2017-12-02 01:34:39 -06:00
Where ( "repository.lower_name = ?" , strings . ToLower ( repoName ) ) .
And ( "`user`.lower_name = ?" , strings . ToLower ( ownerName ) ) .
Get ( & repo )
2014-07-23 06:48:06 -05:00
if err != nil {
return nil , err
2017-12-02 01:34:39 -06:00
} else if ! has {
return nil , ErrRepoNotExist { 0 , 0 , ownerName , repoName }
2014-07-23 06:48:06 -05:00
}
2017-12-02 01:34:39 -06:00
return & repo , nil
2014-07-23 06:48:06 -05:00
}
2014-03-17 13:03:58 -05:00
// GetRepositoryByName returns the repository by given name under user if exists.
2016-08-17 01:06:38 -05:00
func GetRepositoryByName ( ownerID int64 , name string ) ( * Repository , error ) {
2014-03-12 21:27:11 -05:00
repo := & Repository {
2016-08-17 01:06:38 -05:00
OwnerID : ownerID ,
LowerName : strings . ToLower ( name ) ,
2014-03-12 21:27:11 -05:00
}
2014-06-20 23:51:41 -05:00
has , err := x . Get ( repo )
2014-03-12 21:27:11 -05:00
if err != nil {
return nil , err
} else if ! has {
2017-12-02 01:34:39 -06:00
return nil , ErrRepoNotExist { 0 , ownerID , "" , name }
2014-03-12 21:27:11 -05:00
}
return repo , err
}
2015-08-08 09:43:14 -05:00
func getRepositoryByID ( e Engine , id int64 ) ( * Repository , error ) {
2015-03-16 03:04:27 -05:00
repo := new ( Repository )
2017-10-04 23:43:04 -05:00
has , err := e . ID ( id ) . Get ( repo )
2014-03-12 21:27:11 -05:00
if err != nil {
return nil , err
} else if ! has {
2017-12-02 01:34:39 -06:00
return nil , ErrRepoNotExist { id , 0 , "" , "" }
2014-03-12 21:27:11 -05:00
}
2014-05-05 20:36:08 -05:00
return repo , nil
2014-03-12 21:27:11 -05:00
}
2015-08-08 09:43:14 -05:00
// GetRepositoryByID returns the repository by given id if exists.
func GetRepositoryByID ( id int64 ) ( * Repository , error ) {
return getRepositoryByID ( x , id )
2015-02-12 23:58:46 -06:00
}
2018-03-16 09:04:33 -05:00
// GetRepositoriesMapByIDs returns the repositories by given id slice.
func GetRepositoriesMapByIDs ( ids [ ] int64 ) ( map [ int64 ] * Repository , error ) {
var repos = make ( map [ int64 ] * Repository , len ( ids ) )
return repos , x . In ( "id" , ids ) . Find ( & repos )
}
2016-07-24 01:32:46 -05:00
// GetUserRepositories returns a list of repositories of given user.
2017-02-04 06:20:20 -06:00
func GetUserRepositories ( userID int64 , private bool , page , pageSize int , orderBy string ) ( [ ] * Repository , error ) {
if len ( orderBy ) == 0 {
orderBy = "updated_unix DESC"
}
2016-11-10 09:16:32 -06:00
sess := x .
Where ( "owner_id = ?" , userID ) .
2017-02-04 06:20:20 -06:00
OrderBy ( orderBy )
2014-04-13 17:12:07 -05:00
if ! private {
2016-07-24 01:32:46 -05:00
sess . And ( "is_private=?" , false )
2014-04-13 17:12:07 -05:00
}
2016-07-24 01:32:46 -05:00
if page <= 0 {
page = 1
}
sess . Limit ( pageSize , ( page - 1 ) * pageSize )
repos := make ( [ ] * Repository , 0 , pageSize )
return repos , sess . Find ( & repos )
}
2016-11-28 11:27:55 -06:00
// GetUserMirrorRepositories returns a list of mirror repositories of given user.
2016-07-24 01:32:46 -05:00
func GetUserMirrorRepositories ( userID int64 ) ( [ ] * Repository , error ) {
repos := make ( [ ] * Repository , 0 , 10 )
2016-11-10 09:16:32 -06:00
return repos , x .
Where ( "owner_id = ?" , userID ) .
And ( "is_mirror = ?" , true ) .
Find ( & repos )
2014-02-14 08:20:57 -06:00
}
2015-09-06 07:54:08 -05:00
func getRepositoryCount ( e Engine , u * User ) ( int64 , error ) {
2017-02-26 20:16:35 -06:00
return e . Count ( & Repository { OwnerID : u . ID } )
2015-09-06 07:54:08 -05:00
}
2017-02-06 09:18:36 -06:00
func getPublicRepositoryCount ( e Engine , u * User ) ( int64 , error ) {
2017-02-26 20:16:35 -06:00
return e . Where ( "is_private = ?" , false ) . Count ( & Repository { OwnerID : u . ID } )
2017-02-06 09:18:36 -06:00
}
func getPrivateRepositoryCount ( e Engine , u * User ) ( int64 , error ) {
2017-02-26 20:16:35 -06:00
return e . Where ( "is_private = ?" , true ) . Count ( & Repository { OwnerID : u . ID } )
2017-02-06 09:18:36 -06:00
}
2014-04-14 03:11:33 -05:00
// GetRepositoryCount returns the total number of repositories of user.
2015-08-08 09:43:14 -05:00
func GetRepositoryCount ( u * User ) ( int64 , error ) {
2015-09-06 07:54:08 -05:00
return getRepositoryCount ( x , u )
2014-03-11 01:17:05 -05:00
}
2017-02-06 09:18:36 -06:00
// GetPublicRepositoryCount returns the total number of public repositories of user.
func GetPublicRepositoryCount ( u * User ) ( int64 , error ) {
return getPublicRepositoryCount ( x , u )
}
// GetPrivateRepositoryCount returns the total number of private repositories of user.
func GetPrivateRepositoryCount ( u * User ) ( int64 , error ) {
return getPrivateRepositoryCount ( x , u )
}
2014-11-18 18:05:33 -06:00
// DeleteRepositoryArchives deletes all repositories' archives.
func DeleteRepositoryArchives ( ) error {
2016-11-10 09:16:32 -06:00
return x .
Where ( "id > 0" ) .
Iterate ( new ( Repository ) ,
func ( idx int , bean interface { } ) error {
repo := bean . ( * Repository )
return os . RemoveAll ( filepath . Join ( repo . RepoPath ( ) , "archives" ) )
} )
2014-11-18 18:05:33 -06:00
}
2017-02-10 22:00:46 -06:00
// DeleteOldRepositoryArchives deletes old repository archives.
2019-12-15 03:51:28 -06:00
func DeleteOldRepositoryArchives ( ctx context . Context ) {
2017-02-10 22:00:46 -06:00
log . Trace ( "Doing: ArchiveCleanup" )
2019-12-15 03:51:28 -06:00
if err := x . Where ( "id > 0" ) . Iterate ( new ( Repository ) , func ( idx int , bean interface { } ) error {
return deleteOldRepositoryArchives ( ctx , idx , bean )
} ) ; err != nil {
2019-04-02 02:48:31 -05:00
log . Error ( "ArchiveClean: %v" , err )
2017-02-10 22:00:46 -06:00
}
}
2019-12-15 03:51:28 -06:00
func deleteOldRepositoryArchives ( ctx context . Context , idx int , bean interface { } ) error {
2017-02-10 22:00:46 -06:00
repo := bean . ( * Repository )
basePath := filepath . Join ( repo . RepoPath ( ) , "archives" )
for _ , ty := range [ ] string { "zip" , "targz" } {
2019-12-15 03:51:28 -06:00
select {
case <- ctx . Done ( ) :
return fmt . Errorf ( "Aborted due to shutdown:\nin delete of old repository archives %v\nat delete file %s" , repo , ty )
default :
}
2017-02-10 22:00:46 -06:00
path := filepath . Join ( basePath , ty )
file , err := os . Open ( path )
if err != nil {
if ! os . IsNotExist ( err ) {
log . Warn ( "Unable to open directory %s: %v" , path , err )
return err
}
// If the directory doesn't exist, that's okay.
continue
}
files , err := file . Readdir ( 0 )
file . Close ( )
if err != nil {
log . Warn ( "Unable to read directory %s: %v" , path , err )
return err
}
minimumOldestTime := time . Now ( ) . Add ( - setting . Cron . ArchiveCleanup . OlderThan )
for _ , info := range files {
if info . ModTime ( ) . Before ( minimumOldestTime ) && ! info . IsDir ( ) {
2019-12-15 03:51:28 -06:00
select {
case <- ctx . Done ( ) :
return fmt . Errorf ( "Aborted due to shutdown:\nin delete of old repository archives %v\nat delete file %s - %s" , repo , ty , info . Name ( ) )
default :
}
2017-02-10 22:00:46 -06:00
toDelete := filepath . Join ( path , info . Name ( ) )
// This is a best-effort purge, so we do not check error codes to confirm removal.
if err = os . Remove ( toDelete ) ; err != nil {
log . Trace ( "Unable to delete %s, but proceeding: %v" , toDelete , err )
}
}
}
}
return nil
}
2016-02-04 11:51:00 -06:00
func gatherMissingRepoRecords ( ) ( [ ] * Repository , error ) {
repos := make ( [ ] * Repository , 0 , 10 )
2016-11-10 09:16:32 -06:00
if err := x .
Where ( "id > 0" ) .
Iterate ( new ( Repository ) ,
func ( idx int , bean interface { } ) error {
repo := bean . ( * Repository )
if ! com . IsDir ( repo . RepoPath ( ) ) {
repos = append ( repos , repo )
}
return nil
} ) ; err != nil {
2016-02-04 11:51:00 -06:00
if err2 := CreateRepositoryNotice ( fmt . Sprintf ( "gatherMissingRepoRecords: %v" , err ) ) ; err2 != nil {
return nil , fmt . Errorf ( "CreateRepositoryNotice: %v" , err )
2015-11-18 14:37:48 -06:00
}
2016-02-04 11:51:00 -06:00
}
return repos , nil
}
// DeleteMissingRepositories deletes all repository records that lost Git files.
2017-09-03 03:20:24 -05:00
func DeleteMissingRepositories ( doer * User ) error {
2016-02-04 11:51:00 -06:00
repos , err := gatherMissingRepoRecords ( )
if err != nil {
return fmt . Errorf ( "gatherMissingRepoRecords: %v" , err )
2015-11-18 14:37:48 -06:00
}
if len ( repos ) == 0 {
return nil
}
for _ , repo := range repos {
2015-11-21 18:11:57 -06:00
log . Trace ( "Deleting %d/%d..." , repo . OwnerID , repo . ID )
2017-09-03 03:20:24 -05:00
if err := DeleteRepository ( doer , repo . OwnerID , repo . ID ) ; err != nil {
2015-11-18 14:37:48 -06:00
if err2 := CreateRepositoryNotice ( fmt . Sprintf ( "DeleteRepository [%d]: %v" , repo . ID , err ) ) ; err2 != nil {
2016-02-04 11:51:00 -06:00
return fmt . Errorf ( "CreateRepositoryNotice: %v" , err )
}
}
}
return nil
}
// ReinitMissingRepositories reinitializes all repository records that lost Git files.
func ReinitMissingRepositories ( ) error {
repos , err := gatherMissingRepoRecords ( )
if err != nil {
return fmt . Errorf ( "gatherMissingRepoRecords: %v" , err )
}
if len ( repos ) == 0 {
return nil
}
for _ , repo := range repos {
log . Trace ( "Initializing %d/%d..." , repo . OwnerID , repo . ID )
if err := git . InitRepository ( repo . RepoPath ( ) , true ) ; err != nil {
if err2 := CreateRepositoryNotice ( fmt . Sprintf ( "InitRepository [%d]: %v" , repo . ID , err ) ) ; err2 != nil {
return fmt . Errorf ( "CreateRepositoryNotice: %v" , err )
2015-11-18 14:37:48 -06:00
}
}
}
return nil
}
2017-02-22 21:40:44 -06:00
// SyncRepositoryHooks rewrites all repositories' pre-receive, update and post-receive hooks
// to make sure the binary and custom conf path are up-to-date.
func SyncRepositoryHooks ( ) error {
2018-05-02 01:10:19 -05:00
return x . Cols ( "owner_id" , "name" ) . Where ( "id > 0" ) . Iterate ( new ( Repository ) ,
2017-02-22 21:40:44 -06:00
func ( idx int , bean interface { } ) error {
if err := createDelegateHooks ( bean . ( * Repository ) . RepoPath ( ) ) ; err != nil {
return fmt . Errorf ( "SyncRepositoryHook: %v" , err )
}
2017-03-22 20:12:51 -05:00
if bean . ( * Repository ) . HasWiki ( ) {
if err := createDelegateHooks ( bean . ( * Repository ) . WikiPath ( ) ) ; err != nil {
return fmt . Errorf ( "SyncRepositoryHook: %v" , err )
}
}
2017-02-22 21:40:44 -06:00
return nil
} )
2015-02-08 20:26:14 -06:00
}
2014-11-30 01:26:29 -06:00
// GitFsck calls 'git fsck' to check repository health.
2019-12-15 03:51:28 -06:00
func GitFsck ( ctx context . Context ) {
2015-08-17 15:03:11 -05:00
log . Trace ( "Doing: GitFsck" )
2016-11-10 09:16:32 -06:00
if err := x .
2019-08-24 04:24:45 -05:00
Where ( "id>0 AND is_fsck_enabled=?" , true ) . BufferSize ( setting . Database . IterateBufferSize ) .
2016-11-10 09:16:32 -06:00
Iterate ( new ( Repository ) ,
func ( idx int , bean interface { } ) error {
2019-12-15 03:51:28 -06:00
select {
case <- ctx . Done ( ) :
return fmt . Errorf ( "Aborted due to shutdown" )
default :
}
2016-11-10 09:16:32 -06:00
repo := bean . ( * Repository )
repoPath := repo . RepoPath ( )
2018-03-27 09:13:20 -05:00
log . Trace ( "Running health check on repository %s" , repoPath )
2016-11-10 09:16:32 -06:00
if err := git . Fsck ( repoPath , setting . Cron . RepoHealthCheck . Timeout , setting . Cron . RepoHealthCheck . Args ... ) ; err != nil {
2017-01-29 14:13:57 -06:00
desc := fmt . Sprintf ( "Failed to health check repository (%s): %v" , repoPath , err )
2016-11-10 09:16:32 -06:00
log . Warn ( desc )
if err = CreateRepositoryNotice ( desc ) ; err != nil {
2019-04-02 02:48:31 -05:00
log . Error ( "CreateRepositoryNotice: %v" , err )
2016-11-10 09:16:32 -06:00
}
2014-11-30 01:26:29 -06:00
}
2016-11-10 09:16:32 -06:00
return nil
} ) ; err != nil {
2019-04-02 02:48:31 -05:00
log . Error ( "GitFsck: %v" , err )
2014-11-30 01:26:29 -06:00
}
2018-03-02 03:09:43 -06:00
log . Trace ( "Finished: GitFsck" )
2014-11-30 01:26:29 -06:00
}
2016-11-28 11:27:55 -06:00
// GitGcRepos calls 'git gc' to remove unnecessary files and optimize the local repository
2014-11-30 01:26:29 -06:00
func GitGcRepos ( ) error {
2016-08-09 19:24:32 -05:00
args := append ( [ ] string { "gc" } , setting . Git . GCArgs ... )
2016-11-10 09:16:32 -06:00
return x .
2019-08-24 04:24:45 -05:00
Where ( "id > 0" ) . BufferSize ( setting . Database . IterateBufferSize ) .
2016-11-10 09:16:32 -06:00
Iterate ( new ( Repository ) ,
func ( idx int , bean interface { } ) error {
repo := bean . ( * Repository )
if err := repo . GetOwner ( ) ; err != nil {
return err
}
2019-11-30 08:40:22 -06:00
if stdout , err := git . NewCommand ( args ... ) .
SetDescription ( fmt . Sprintf ( "Repository Garbage Collection: %s" , repo . FullName ( ) ) ) .
RunInDirTimeout (
time . Duration ( setting . Git . Timeout . GC ) * time . Second ,
RepoPath ( repo . Owner . Name , repo . Name ) ) ; err != nil {
log . Error ( "Repository garbage collection failed for %v. Stdout: %s\nError: %v" , repo , stdout , err )
return fmt . Errorf ( "Repository garbage collection failed: Error: %v" , err )
2016-11-10 09:16:32 -06:00
}
return nil
} )
2014-11-30 01:26:29 -06:00
}
2015-09-01 10:43:53 -05:00
type repoChecker struct {
querySQL , correctSQL string
desc string
}
2015-08-17 15:03:11 -05:00
2019-12-15 03:51:28 -06:00
func repoStatsCheck ( ctx context . Context , checker * repoChecker ) {
2015-09-01 10:43:53 -05:00
results , err := x . Query ( checker . querySQL )
2015-08-17 15:03:11 -05:00
if err != nil {
2019-04-02 02:48:31 -05:00
log . Error ( "Select %s: %v" , checker . desc , err )
2015-08-17 15:03:11 -05:00
return
}
2015-09-01 10:43:53 -05:00
for _ , result := range results {
2019-12-15 03:51:28 -06:00
select {
case <- ctx . Done ( ) :
log . Warn ( "CheckRepoStats: Aborting due to shutdown" )
return
default :
}
2015-09-01 10:43:53 -05:00
id := com . StrTo ( result [ "id" ] ) . MustInt64 ( )
log . Trace ( "Updating %s: %d" , checker . desc , id )
_ , err = x . Exec ( checker . correctSQL , id , id )
2015-08-17 15:03:11 -05:00
if err != nil {
2019-04-02 02:48:31 -05:00
log . Error ( "Update %s[%d]: %v" , checker . desc , id , err )
2015-08-17 15:03:11 -05:00
}
}
2015-09-01 10:43:53 -05:00
}
2015-08-17 15:03:11 -05:00
2016-11-28 11:27:55 -06:00
// CheckRepoStats checks the repository stats
2019-12-15 03:51:28 -06:00
func CheckRepoStats ( ctx context . Context ) {
2015-09-01 10:43:53 -05:00
log . Trace ( "Doing: CheckRepoStats" )
2015-08-29 12:13:24 -05:00
2015-09-01 10:43:53 -05:00
checkers := [ ] * repoChecker {
// Repository.NumWatches
{
2019-11-10 03:22:19 -06: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 10:43:53 -05: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-29 19:40:57 -05: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 10:43:53 -05:00
}
for i := range checkers {
2019-12-15 03:51:28 -06:00
select {
case <- ctx . Done ( ) :
log . Warn ( "CheckRepoStats: Aborting due to shutdown" )
return
default :
repoStatsCheck ( ctx , checkers [ i ] )
}
2015-09-01 10:43:53 -05:00
}
2016-05-27 20:23:39 -05:00
// ***** START: Repository.NumClosedIssues *****
desc := "repository count 'num_closed_issues'"
2016-06-26 12:53:30 -05:00
results , err := x . 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-27 20:23:39 -05:00
if err != nil {
2019-04-02 02:48:31 -05:00
log . Error ( "Select %s: %v" , desc , err )
2016-05-27 20:23:39 -05:00
} else {
for _ , result := range results {
2019-12-15 03:51:28 -06:00
select {
case <- ctx . Done ( ) :
log . Warn ( "CheckRepoStats: Aborting due to shutdown" )
return
default :
}
2016-05-27 20:23:39 -05:00
id := com . StrTo ( result [ "id" ] ) . MustInt64 ( )
log . Trace ( "Updating %s: %d" , desc , id )
2016-06-26 12:53:30 -05:00
_ , err = x . 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-27 20:23:39 -05:00
if err != nil {
2019-04-02 02:48:31 -05:00
log . Error ( "Update %s[%d]: %v" , desc , id , err )
2016-05-27 20:23:39 -05:00
}
}
}
// ***** END: Repository.NumClosedIssues *****
2019-07-18 16:51:33 -05:00
// ***** START: Repository.NumClosedPulls *****
desc = "repository count 'num_closed_pulls'"
results , err = x . 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 )
if err != nil {
log . Error ( "Select %s: %v" , desc , err )
} else {
for _ , result := range results {
2019-12-15 03:51:28 -06:00
select {
case <- ctx . Done ( ) :
log . Warn ( "CheckRepoStats: Aborting due to shutdown" )
return
default :
}
2019-07-18 16:51:33 -05:00
id := com . StrTo ( result [ "id" ] ) . MustInt64 ( )
log . Trace ( "Updating %s: %d" , desc , id )
_ , err = x . 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 )
if err != nil {
log . Error ( "Update %s[%d]: %v" , desc , id , err )
}
}
}
// ***** END: Repository.NumClosedPulls *****
2016-05-27 20:23:39 -05:00
// FIXME: use checker when stop supporting old fork repo format.
2015-09-01 10:43:53 -05:00
// ***** START: Repository.NumForks *****
2016-05-27 20:23:39 -05:00
results , err = x . Query ( "SELECT repo.id FROM `repository` repo WHERE repo.num_forks!=(SELECT COUNT(*) FROM `repository` WHERE fork_id=repo.id)" )
2015-08-29 12:13:24 -05:00
if err != nil {
2019-04-02 02:48:31 -05:00
log . Error ( "Select repository count 'num_forks': %v" , err )
2015-09-01 10:43:53 -05:00
} else {
for _ , result := range results {
2019-12-15 03:51:28 -06:00
select {
case <- ctx . Done ( ) :
log . Warn ( "CheckRepoStats: Aborting due to shutdown" )
return
default :
}
2015-09-01 10:43:53 -05:00
id := com . StrTo ( result [ "id" ] ) . MustInt64 ( )
log . Trace ( "Updating repository count 'num_forks': %d" , id )
repo , err := GetRepositoryByID ( id )
if err != nil {
2019-04-02 02:48:31 -05:00
log . Error ( "GetRepositoryByID[%d]: %v" , id , err )
2015-09-01 10:43:53 -05:00
continue
}
rawResult , err := x . Query ( "SELECT COUNT(*) FROM `repository` WHERE fork_id=?" , repo . ID )
if err != nil {
2019-04-02 02:48:31 -05:00
log . Error ( "Select count of forks[%d]: %v" , repo . ID , err )
2015-09-01 10:43:53 -05:00
continue
}
repo . NumForks = int ( parseCountResult ( rawResult ) )
if err = UpdateRepository ( repo , false ) ; err != nil {
2019-04-02 02:48:31 -05:00
log . Error ( "UpdateRepository[%d]: %v" , id , err )
2015-09-01 10:43:53 -05:00
continue
}
2015-08-29 12:13:24 -05:00
}
}
2015-09-01 10:43:53 -05:00
// ***** END: Repository.NumForks *****
2015-03-21 07:55:00 -05:00
}
2019-01-23 12:58:38 -06:00
// SetArchiveRepoState sets if a repo is archived
func ( repo * Repository ) SetArchiveRepoState ( isArchived bool ) ( err error ) {
repo . IsArchived = isArchived
_ , err = x . Where ( "id = ?" , repo . ID ) . Cols ( "is_archived" ) . Update ( repo )
return
}
2014-10-19 00:35:24 -05:00
// ___________ __
// \_ _____/__________| | __
// | __)/ _ \_ __ \ |/ /
// | \( <_> ) | \/ <
// \___ / \____/|__| |__|_ \
// \/ \/
2015-08-08 04:24:10 -05:00
// HasForkedRepo checks if given user has already forked a repository with given ID.
func HasForkedRepo ( ownerID , repoID int64 ) ( * Repository , bool ) {
repo := new ( Repository )
2016-11-10 09:16:32 -06:00
has , _ := x .
Where ( "owner_id=? AND fork_id=?" , ownerID , repoID ) .
Get ( repo )
2015-08-08 04:24:10 -05:00
return repo , has
}
2019-11-11 09:15:29 -06:00
// CopyLFS copies LFS data from one repo to another
func CopyLFS ( newRepo , oldRepo * Repository ) error {
return copyLFS ( x , newRepo , oldRepo )
}
func copyLFS ( e Engine , newRepo , oldRepo * Repository ) error {
var lfsObjects [ ] * LFSMetaObject
if err := e . Where ( "repository_id=?" , oldRepo . ID ) . Find ( & lfsObjects ) ; err != nil {
return err
}
for _ , v := range lfsObjects {
v . ID = 0
v . RepositoryID = newRepo . ID
if _ , err := e . Insert ( v ) ; err != nil {
return err
}
}
return nil
}
2016-11-28 11:27:55 -06:00
// ForkRepository forks a repository
2019-10-26 01:54:11 -05:00
func ForkRepository ( doer , owner * User , oldRepo * Repository , name , desc string ) ( _ * Repository , err error ) {
forkedRepo , err := oldRepo . GetUserFork ( owner . ID )
2017-02-14 08:14:29 -06:00
if err != nil {
return nil , err
}
if forkedRepo != nil {
2019-06-12 15:20:43 -05:00
return nil , ErrForkAlreadyExist {
2019-10-26 01:54:11 -05:00
Uname : owner . Name ,
2019-06-12 15:20:43 -05:00
RepoName : oldRepo . FullName ( ) ,
ForkName : forkedRepo . FullName ( ) ,
2017-02-14 08:14:29 -06:00
}
}
2014-10-19 00:35:24 -05:00
repo := & Repository {
2019-10-26 01:54:11 -05:00
OwnerID : owner . ID ,
Owner : owner ,
2015-09-01 10:43:53 -05:00
Name : name ,
LowerName : strings . ToLower ( name ) ,
Description : desc ,
DefaultBranch : oldRepo . DefaultBranch ,
IsPrivate : oldRepo . IsPrivate ,
2019-04-15 15:48:35 -05:00
IsEmpty : oldRepo . IsEmpty ,
2015-09-01 10:43:53 -05:00
IsFork : true ,
ForkID : oldRepo . ID ,
2014-10-19 00:35:24 -05:00
}
2015-02-12 23:58:46 -06:00
sess := x . NewSession ( )
2017-06-20 19:57:05 -05:00
defer sess . Close ( )
2015-02-12 23:58:46 -06:00
if err = sess . Begin ( ) ; err != nil {
return nil , err
}
2019-10-26 01:54:11 -05:00
if err = createRepository ( sess , doer , owner , repo ) ; err != nil {
2014-10-19 00:35:24 -05:00
return nil , err
}
2014-03-17 22:22:19 -05:00
2015-08-08 09:43:14 -05:00
if _ , err = sess . Exec ( "UPDATE `repository` SET num_forks=num_forks+1 WHERE id=?" , oldRepo . ID ) ; err != nil {
2014-10-19 00:35:24 -05:00
return nil , err
}
2019-10-26 01:54:11 -05:00
repoPath := RepoPath ( owner . Name , repo . Name )
2019-11-30 08:40:22 -06:00
if stdout , err := git . NewCommand (
"clone" , "--bare" , oldRepo . repoPath ( sess ) , repoPath ) .
SetDescription ( fmt . Sprintf ( "ForkRepository(git clone): %s to %s" , oldRepo . FullName ( ) , repo . FullName ( ) ) ) .
RunInDirTimeout ( 10 * time . Minute , "" ) ; err != nil {
log . Error ( "Fork Repository (git clone) Failed for %v (from %v):\nStdout: %s\nError: %v" , repo , oldRepo , stdout , err )
return nil , fmt . Errorf ( "git clone: %v" , err )
}
if stdout , err := git . NewCommand ( "update-server-info" ) .
SetDescription ( fmt . Sprintf ( "ForkRepository(git update-server-info): %s" , repo . FullName ( ) ) ) .
RunInDir ( repoPath ) ; err != nil {
log . Error ( "Fork Repository (git update-server-info) failed for %v:\nStdout: %s\nError: %v" , repo , stdout , err )
return nil , fmt . Errorf ( "git update-server-info: %v" , err )
2014-10-19 00:35:24 -05:00
}
2017-02-22 21:40:44 -06:00
if err = createDelegateHooks ( repoPath ) ; err != nil {
return nil , fmt . Errorf ( "createDelegateHooks: %v" , err )
2015-03-12 19:18:42 -05:00
}
2016-12-25 19:16:37 -06:00
//Commit repo to get Fork ID
err = sess . Commit ( )
if err != nil {
return nil , err
}
2017-04-11 08:30:15 -05:00
if err = repo . UpdateSize ( ) ; err != nil {
2019-04-02 02:48:31 -05:00
log . Error ( "Failed to update size for repository: %v" , err )
2017-04-11 08:30:15 -05:00
}
2019-11-11 09:15:29 -06:00
return repo , CopyLFS ( repo , oldRepo )
}
2016-11-28 11:27:55 -06:00
// GetForks returns all the forks of the repository
2015-10-01 08:17:27 -05:00
func ( repo * Repository ) GetForks ( ) ( [ ] * Repository , error ) {
2015-11-16 22:33:40 -06:00
forks := make ( [ ] * Repository , 0 , repo . NumForks )
return forks , x . Find ( & forks , & Repository { ForkID : repo . ID } )
2015-10-01 08:17:27 -05:00
}
2016-08-11 07:48:08 -05:00
2017-02-14 08:14:29 -06: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
has , err := x . Where ( "fork_id = ?" , repo . ID ) . And ( "owner_id = ?" , userID ) . Get ( & forkedRepo )
if err != nil {
return nil , err
}
if ! has {
return nil , nil
}
return & forkedRepo , nil
}
2019-05-29 21:22:26 -05:00
// CustomAvatarPath returns repository custom avatar file path.
func ( repo * Repository ) CustomAvatarPath ( ) string {
// Avatar empty by default
2019-06-12 14:41:28 -05:00
if len ( repo . Avatar ) == 0 {
2019-05-29 21:22:26 -05:00
return ""
}
return filepath . Join ( setting . RepositoryAvatarUploadPath , repo . Avatar )
}
2019-06-11 19:12:13 -05:00
// generateRandomAvatar generates a random avatar for repository.
2019-06-02 01:40:12 -05:00
func ( repo * Repository ) generateRandomAvatar ( e Engine ) error {
idToString := fmt . Sprintf ( "%d" , repo . ID )
seed := idToString
img , err := avatar . RandomImage ( [ ] byte ( seed ) )
if err != nil {
return fmt . Errorf ( "RandomImage: %v" , err )
}
repo . Avatar = idToString
if err = os . MkdirAll ( filepath . Dir ( repo . CustomAvatarPath ( ) ) , os . ModePerm ) ; err != nil {
return fmt . Errorf ( "MkdirAll: %v" , err )
}
fw , err := os . Create ( repo . CustomAvatarPath ( ) )
if err != nil {
return fmt . Errorf ( "Create: %v" , err )
}
defer fw . Close ( )
if err = png . Encode ( fw , img ) ; err != nil {
return fmt . Errorf ( "Encode: %v" , err )
}
log . Info ( "New random avatar created for repository: %d" , repo . ID )
if _ , err := e . ID ( repo . ID ) . Cols ( "avatar" ) . NoAutoTime ( ) . Update ( repo ) ; err != nil {
return err
}
return nil
}
// RemoveRandomAvatars removes the randomly generated avatars that were created for repositories
func RemoveRandomAvatars ( ) error {
2019-06-12 14:41:28 -05:00
return x .
2019-08-24 04:24:45 -05:00
Where ( "id > 0" ) . BufferSize ( setting . Database . IterateBufferSize ) .
2019-06-02 01:40:12 -05:00
Iterate ( new ( Repository ) ,
func ( idx int , bean interface { } ) error {
repository := bean . ( * Repository )
stringifiedID := strconv . FormatInt ( repository . ID , 10 )
if repository . Avatar == stringifiedID {
return repository . DeleteAvatar ( )
}
return nil
} )
}
// RelAvatarLink returns a relative link to the repository's avatar.
2019-05-29 21:22:26 -05:00
func ( repo * Repository ) RelAvatarLink ( ) string {
2019-06-11 19:12:13 -05:00
return repo . relAvatarLink ( x )
}
2019-06-02 01:40:12 -05:00
2019-06-11 19:12:13 -05:00
func ( repo * Repository ) relAvatarLink ( e Engine ) string {
2019-05-29 21:22:26 -05:00
// If no avatar - path is empty
avatarPath := repo . CustomAvatarPath ( )
2019-06-12 14:41:28 -05:00
if len ( avatarPath ) == 0 || ! com . IsFile ( avatarPath ) {
2019-06-02 01:40:12 -05:00
switch mode := setting . RepositoryAvatarFallback ; mode {
case "image" :
return setting . RepositoryAvatarFallbackImage
case "random" :
2019-06-11 19:12:13 -05:00
if err := repo . generateRandomAvatar ( e ) ; err != nil {
log . Error ( "generateRandomAvatar: %v" , err )
2019-06-02 01:40:12 -05:00
}
default :
// default behaviour: do not display avatar
return ""
}
2019-05-29 21:22:26 -05:00
}
return setting . AppSubURL + "/repo-avatars/" + repo . Avatar
}
2019-06-11 19:12:13 -05:00
// avatarLink returns user avatar absolute link.
func ( repo * Repository ) avatarLink ( e Engine ) string {
link := repo . relAvatarLink ( e )
2019-05-29 21:22:26 -05:00
// link may be empty!
if len ( link ) > 0 {
if link [ 0 ] == '/' && link [ 1 ] != '/' {
return setting . AppURL + strings . TrimPrefix ( link , setting . AppSubURL ) [ 1 : ]
}
}
return link
}
// UploadAvatar saves custom avatar for repository.
// FIXME: split uploads to different subdirs in case we have massive number of repos.
func ( repo * Repository ) UploadAvatar ( data [ ] byte ) error {
m , err := avatar . Prepare ( data )
if err != nil {
return err
}
sess := x . NewSession ( )
defer sess . Close ( )
if err = sess . Begin ( ) ; err != nil {
return err
}
oldAvatarPath := repo . CustomAvatarPath ( )
// Users can upload the same image to other repo - prefix it with ID
// Then repo will be removed - only it avatar file will be removed
repo . Avatar = fmt . Sprintf ( "%d-%x" , repo . ID , md5 . Sum ( data ) )
if _ , err := sess . ID ( repo . ID ) . Cols ( "avatar" ) . Update ( repo ) ; err != nil {
return fmt . Errorf ( "UploadAvatar: Update repository avatar: %v" , err )
}
if err := os . MkdirAll ( setting . RepositoryAvatarUploadPath , os . ModePerm ) ; err != nil {
return fmt . Errorf ( "UploadAvatar: Failed to create dir %s: %v" , setting . RepositoryAvatarUploadPath , err )
}
fw , err := os . Create ( repo . CustomAvatarPath ( ) )
if err != nil {
return fmt . Errorf ( "UploadAvatar: Create file: %v" , err )
}
defer fw . Close ( )
if err = png . Encode ( fw , * m ) ; err != nil {
return fmt . Errorf ( "UploadAvatar: Encode png: %v" , err )
}
if len ( oldAvatarPath ) > 0 && oldAvatarPath != repo . CustomAvatarPath ( ) {
if err := os . Remove ( oldAvatarPath ) ; err != nil {
return fmt . Errorf ( "UploadAvatar: Failed to remove old repo avatar %s: %v" , oldAvatarPath , err )
}
}
return sess . Commit ( )
}
// DeleteAvatar deletes the repos's custom avatar.
func ( repo * Repository ) DeleteAvatar ( ) error {
// Avatar not exists
if len ( repo . Avatar ) == 0 {
return nil
}
avatarPath := repo . CustomAvatarPath ( )
log . Trace ( "DeleteAvatar[%d]: %s" , repo . ID , avatarPath )
sess := x . NewSession ( )
defer sess . Close ( )
if err := sess . Begin ( ) ; err != nil {
return err
}
repo . Avatar = ""
if _ , err := sess . ID ( repo . ID ) . Cols ( "avatar" ) . Update ( repo ) ; err != nil {
return fmt . Errorf ( "DeleteAvatar: Update repository avatar: %v" , err )
}
if _ , err := os . Stat ( avatarPath ) ; err == nil {
if err := os . Remove ( avatarPath ) ; err != nil {
return fmt . Errorf ( "DeleteAvatar: Failed to remove %s: %v" , avatarPath , err )
}
} else {
// // Schrodinger: file may or may not exist. See err for details.
log . Trace ( "DeleteAvatar[%d]: %v" , err )
}
return sess . Commit ( )
}
2019-07-07 21:14:12 -05: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-29 16:32:21 -05:00
// GetTreePathLock returns LSF lock for the treePath
func ( repo * Repository ) GetTreePathLock ( treePath string ) ( * LFSLock , error ) {
if setting . LFS . StartServer {
2019-12-12 07:18:07 -06:00
locks , err := GetLFSLockByRepoID ( repo . ID , 0 , 0 )
2019-10-29 16:32:21 -05:00
if err != nil {
return nil , err
}
for _ , lock := range locks {
if lock . Path == treePath {
return lock , nil
}
}
}
return nil , nil
}
2019-11-08 16:21:00 -06:00
2019-11-24 23:17:51 -06:00
func updateRepositoryCols ( e Engine , repo * Repository , cols ... string ) error {
_ , err := e . ID ( repo . ID ) . Cols ( cols ... ) . Update ( repo )
return err
}
2019-11-08 16:21:00 -06:00
// UpdateRepositoryCols updates repository's columns
func UpdateRepositoryCols ( repo * Repository , cols ... string ) error {
2019-11-24 23:17:51 -06:00
return updateRepositoryCols ( x , repo , cols ... )
2019-11-08 16:21:00 -06:00
}