2020-01-12 15:11:17 +03:00
// Copyright 2019 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package repository
import (
2021-09-23 18:45:36 +03:00
"context"
2020-01-12 15:11:17 +03:00
"fmt"
2022-06-06 11:01:49 +03:00
"os"
"path"
2020-01-12 15:11:17 +03:00
"strings"
2022-06-06 11:01:49 +03:00
"unicode/utf8"
2020-01-12 15:11:17 +03:00
"code.gitea.io/gitea/models"
2021-09-19 14:49:59 +03:00
"code.gitea.io/gitea/models/db"
2022-06-06 11:01:49 +03:00
access_model "code.gitea.io/gitea/models/perm/access"
2021-12-10 04:27:50 +03:00
repo_model "code.gitea.io/gitea/models/repo"
2021-11-24 12:49:20 +03:00
user_model "code.gitea.io/gitea/models/user"
2020-01-12 15:11:17 +03:00
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
2022-06-06 11:01:49 +03:00
api "code.gitea.io/gitea/modules/structs"
2020-08-11 23:05:34 +03:00
"code.gitea.io/gitea/modules/util"
2020-01-12 15:11:17 +03:00
)
// CreateRepository creates a repository for the user/organization.
2021-12-10 04:27:50 +03:00
func CreateRepository ( doer , u * user_model . User , opts models . CreateRepoOptions ) ( * repo_model . Repository , error ) {
2020-01-12 15:11:17 +03:00
if ! doer . IsAdmin && ! u . CanCreateRepo ( ) {
2021-12-12 18:48:20 +03:00
return nil , repo_model . ErrReachLimitOfRepo {
2020-01-12 15:11:17 +03:00
Limit : u . MaxRepoCreation ,
}
}
2020-09-06 23:58:54 +03:00
if len ( opts . DefaultBranch ) == 0 {
opts . DefaultBranch = setting . Repository . DefaultBranch
}
2021-01-18 23:00:50 +03:00
// Check if label template exist
if len ( opts . IssueLabels ) > 0 {
2022-03-29 10:23:45 +03:00
if _ , err := GetLabelTemplateFile ( opts . IssueLabels ) ; err != nil {
2021-01-18 23:00:50 +03:00
return nil , err
}
}
2021-12-10 04:27:50 +03:00
repo := & repo_model . Repository {
2020-01-12 15:11:17 +03:00
OwnerID : u . ID ,
Owner : u ,
OwnerName : u . Name ,
Name : opts . Name ,
LowerName : strings . ToLower ( opts . Name ) ,
Description : opts . Description ,
OriginalURL : opts . OriginalURL ,
OriginalServiceType : opts . GitServiceType ,
IsPrivate : opts . IsPrivate ,
IsFsckEnabled : ! opts . IsMirror ,
2020-09-25 08:18:37 +03:00
IsTemplate : opts . IsTemplate ,
2020-01-12 15:11:17 +03:00
CloseIssuesViaCommitInAnyBranch : setting . Repository . DefaultCloseIssuesViaCommitsInAnyBranch ,
Status : opts . Status ,
IsEmpty : ! opts . AutoInit ,
2020-09-19 19:44:55 +03:00
TrustModel : opts . TrustModel ,
2022-04-01 17:14:36 +03:00
IsMirror : opts . IsMirror ,
2020-01-12 15:11:17 +03:00
}
2021-12-10 04:27:50 +03:00
var rollbackRepo * repo_model . Repository
2021-01-18 23:00:50 +03:00
2021-09-23 18:45:36 +03:00
if err := db . WithTx ( func ( ctx context . Context ) error {
2020-09-25 07:09:23 +03:00
if err := models . CreateRepository ( ctx , doer , u , repo , false ) ; err != nil {
2020-01-12 15:11:17 +03:00
return err
}
// No need for init mirror.
2020-09-25 07:09:23 +03:00
if opts . IsMirror {
return nil
}
2021-12-10 04:27:50 +03:00
repoPath := repo_model . RepoPath ( u . Name , repo . Name )
2020-11-28 05:42:08 +03:00
isExist , err := util . IsExist ( repoPath )
if err != nil {
log . Error ( "Unable to check if %s exists. Error: %v" , repoPath , err )
return err
}
if isExist {
2020-09-25 07:09:23 +03:00
// repo already exists - We have two or three options.
// 1. We fail stating that the directory exists
// 2. We create the db repository to go with this data and adopt the git repo
// 3. We delete it and start afresh
//
// Previously Gitea would just delete and start afresh - this was naughty.
// So we will now fail and delegate to other functionality to adopt or delete
log . Error ( "Files already exist in %s and we are not going to adopt or delete." , repoPath )
2021-12-12 18:48:20 +03:00
return repo_model . ErrRepoFilesAlreadyExist {
2020-09-25 07:09:23 +03:00
Uname : u . Name ,
Name : repo . Name ,
}
}
2021-01-18 23:00:50 +03:00
if err = initRepository ( ctx , repoPath , doer , repo , opts ) ; err != nil {
2020-09-25 07:09:23 +03:00
if err2 := util . RemoveAll ( repoPath ) ; err2 != nil {
log . Error ( "initRepository: %v" , err )
return fmt . Errorf (
"delete repo directory %s/%s failed(2): %v" , u . Name , repo . Name , err2 )
2020-01-12 15:11:17 +03:00
}
2020-09-25 07:09:23 +03:00
return fmt . Errorf ( "initRepository: %v" , err )
}
2020-01-12 15:11:17 +03:00
2020-09-25 07:09:23 +03:00
// Initialize Issue Labels if selected
if len ( opts . IssueLabels ) > 0 {
2022-03-29 10:23:45 +03:00
if err = InitializeLabels ( ctx , repo . ID , opts . IssueLabels , false ) ; err != nil {
2021-01-18 23:00:50 +03:00
rollbackRepo = repo
rollbackRepo . OwnerID = u . ID
2020-09-25 07:09:23 +03:00
return fmt . Errorf ( "InitializeLabels: %v" , err )
2020-01-12 15:11:17 +03:00
}
2020-09-25 07:09:23 +03:00
}
2020-01-12 15:11:17 +03:00
2022-06-06 11:01:49 +03:00
if err := CheckDaemonExportOK ( ctx , repo ) ; err != nil {
2021-10-13 22:47:02 +03:00
return fmt . Errorf ( "checkDaemonExportOK: %v" , err )
}
2022-04-01 05:55:30 +03:00
if stdout , _ , err := git . NewCommand ( ctx , "update-server-info" ) .
2020-09-25 07:09:23 +03:00
SetDescription ( fmt . Sprintf ( "CreateRepository(git update-server-info): %s" , repoPath ) ) .
2022-04-01 05:55:30 +03:00
RunStdString ( & git . RunOpts { Dir : repoPath } ) ; err != nil {
2020-09-25 07:09:23 +03:00
log . Error ( "CreateRepository(git update-server-info) in %v: Stdout: %s\nError: %v" , repo , stdout , err )
2021-01-18 23:00:50 +03:00
rollbackRepo = repo
rollbackRepo . OwnerID = u . ID
2020-09-25 07:09:23 +03:00
return fmt . Errorf ( "CreateRepository(git update-server-info): %v" , err )
2020-01-12 15:11:17 +03:00
}
return nil
2020-09-25 07:09:23 +03:00
} ) ; err != nil {
2021-01-18 23:00:50 +03:00
if rollbackRepo != nil {
if errDelete := models . DeleteRepository ( doer , rollbackRepo . OwnerID , rollbackRepo . ID ) ; errDelete != nil {
log . Error ( "Rollback deleteRepository: %v" , errDelete )
}
}
2020-09-25 07:09:23 +03:00
return nil , err
}
2020-01-12 15:11:17 +03:00
2020-09-25 07:09:23 +03:00
return repo , nil
2020-01-12 15:11:17 +03:00
}
2022-06-06 11:01:49 +03:00
// UpdateRepoSize updates the repository size, calculating it using util.GetDirectorySize
func UpdateRepoSize ( ctx context . Context , repo * repo_model . Repository ) error {
size , err := util . GetDirectorySize ( repo . RepoPath ( ) )
if err != nil {
return fmt . Errorf ( "updateSize: %v" , err )
}
lfsSize , err := models . GetRepoLFSSize ( ctx , repo . ID )
if err != nil {
return fmt . Errorf ( "updateSize: GetLFSMetaObjects: %v" , err )
}
return repo_model . UpdateRepoSize ( ctx , repo . ID , size + lfsSize )
}
// CheckDaemonExportOK creates/removes git-daemon-export-ok for git-daemon...
func CheckDaemonExportOK ( ctx context . Context , repo * repo_model . Repository ) error {
if err := repo . GetOwner ( ctx ) ; err != nil {
return err
}
// Create/Remove git-daemon-export-ok for git-daemon...
daemonExportFile := path . Join ( repo . RepoPath ( ) , ` git-daemon-export-ok ` )
isExist , err := util . IsExist ( daemonExportFile )
if err != nil {
log . Error ( "Unable to check if %s exists. Error: %v" , daemonExportFile , err )
return err
}
isPublic := ! repo . IsPrivate && repo . Owner . Visibility == api . VisibleTypePublic
if ! isPublic && isExist {
if err = util . Remove ( daemonExportFile ) ; err != nil {
log . Error ( "Failed to remove %s: %v" , daemonExportFile , err )
}
} else if isPublic && ! isExist {
if f , err := os . Create ( daemonExportFile ) ; err != nil {
log . Error ( "Failed to create %s: %v" , daemonExportFile , err )
} else {
f . Close ( )
}
}
return nil
}
// UpdateRepository updates a repository with db context
func UpdateRepository ( ctx context . Context , repo * repo_model . Repository , visibilityChanged bool ) ( err error ) {
repo . LowerName = strings . ToLower ( repo . Name )
if utf8 . RuneCountInString ( repo . Description ) > 255 {
repo . Description = string ( [ ] rune ( repo . Description ) [ : 255 ] )
}
if utf8 . RuneCountInString ( repo . Website ) > 255 {
repo . Website = string ( [ ] rune ( repo . Website ) [ : 255 ] )
}
e := db . GetEngine ( ctx )
if _ , err = e . ID ( repo . ID ) . AllCols ( ) . Update ( repo ) ; err != nil {
return fmt . Errorf ( "update: %v" , err )
}
if err = UpdateRepoSize ( ctx , repo ) ; err != nil {
log . Error ( "Failed to update size for repository: %v" , err )
}
if visibilityChanged {
if err = repo . GetOwner ( ctx ) ; err != nil {
return fmt . Errorf ( "getOwner: %v" , err )
}
if repo . Owner . IsOrganization ( ) {
// Organization repository need to recalculate access table when visibility is changed.
if err = access_model . RecalculateTeamAccesses ( ctx , repo , 0 ) ; err != nil {
return fmt . Errorf ( "recalculateTeamAccesses: %v" , err )
}
}
// 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 ( & models . Action {
IsPrivate : true ,
} )
if err != nil {
return err
}
}
// Create/Remove git-daemon-export-ok for git-daemon...
if err := CheckDaemonExportOK ( db . WithEngine ( ctx , e ) , repo ) ; err != nil {
return err
}
forkRepos , err := repo_model . GetRepositoriesByForkID ( ctx , repo . ID )
if err != nil {
return fmt . Errorf ( "getRepositoriesByForkID: %v" , err )
}
for i := range forkRepos {
forkRepos [ i ] . IsPrivate = repo . IsPrivate || repo . Owner . Visibility == api . VisibleTypePrivate
if err = UpdateRepository ( ctx , forkRepos [ i ] , true ) ; err != nil {
return fmt . Errorf ( "updateRepository[%d]: %v" , forkRepos [ i ] . ID , err )
}
}
}
return nil
}