2020-09-25 05:09:23 +01:00
// Copyright 2020 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 16:45:36 +01:00
"context"
2020-09-25 05:09:23 +01:00
"fmt"
"os"
"path/filepath"
"strings"
"code.gitea.io/gitea/models"
2021-09-19 19:49:59 +08:00
"code.gitea.io/gitea/models/db"
2021-11-24 17:49:20 +08:00
user_model "code.gitea.io/gitea/models/user"
2020-09-25 05:09:23 +01:00
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/log"
2021-11-16 21:30:11 +08:00
"code.gitea.io/gitea/modules/notification"
repo_module "code.gitea.io/gitea/modules/repository"
2020-09-25 05:09:23 +01:00
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util"
2021-11-16 21:30:11 +08:00
2020-09-25 05:09:23 +01:00
"github.com/gobwas/glob"
)
2021-11-16 21:30:11 +08:00
// AdoptRepository adopts pre-existing repository files for the user/organization.
2021-11-24 17:49:20 +08:00
func AdoptRepository ( doer , u * user_model . User , opts models . CreateRepoOptions ) ( * models . Repository , error ) {
2020-09-25 05:09:23 +01:00
if ! doer . IsAdmin && ! u . CanCreateRepo ( ) {
return nil , models . ErrReachLimitOfRepo {
Limit : u . MaxRepoCreation ,
}
}
if len ( opts . DefaultBranch ) == 0 {
opts . DefaultBranch = setting . Repository . DefaultBranch
}
repo := & models . Repository {
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 ,
CloseIssuesViaCommitInAnyBranch : setting . Repository . DefaultCloseIssuesViaCommitsInAnyBranch ,
Status : opts . Status ,
IsEmpty : ! opts . AutoInit ,
}
2021-09-23 16:45:36 +01:00
if err := db . WithTx ( func ( ctx context . Context ) error {
2020-09-25 05:09:23 +01:00
repoPath := models . RepoPath ( u . Name , repo . Name )
2020-11-28 02:42:08 +00: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 05:09:23 +01:00
return models . ErrRepoNotExist {
OwnerName : u . Name ,
Name : repo . Name ,
}
}
if err := models . CreateRepository ( ctx , doer , u , repo , true ) ; err != nil {
return err
}
if err := adoptRepository ( ctx , repoPath , doer , repo , opts ) ; err != nil {
return fmt . Errorf ( "createDelegateHooks: %v" , err )
}
2021-10-13 20:47:02 +01:00
if err := repo . CheckDaemonExportOK ( ctx ) ; err != nil {
return fmt . Errorf ( "checkDaemonExportOK: %v" , err )
}
2020-09-25 05:09:23 +01:00
// Initialize Issue Labels if selected
if len ( opts . IssueLabels ) > 0 {
if err := models . InitializeLabels ( ctx , repo . ID , opts . IssueLabels , false ) ; err != nil {
return fmt . Errorf ( "InitializeLabels: %v" , err )
}
}
if stdout , err := git . NewCommand ( "update-server-info" ) .
SetDescription ( fmt . Sprintf ( "CreateRepository(git update-server-info): %s" , repoPath ) ) .
RunInDir ( repoPath ) ; err != nil {
log . Error ( "CreateRepository(git update-server-info) in %v: Stdout: %s\nError: %v" , repo , stdout , err )
return fmt . Errorf ( "CreateRepository(git update-server-info): %v" , err )
}
return nil
} ) ; err != nil {
return nil , err
}
2021-11-16 21:30:11 +08:00
notification . NotifyCreateRepository ( doer , u , repo )
2020-09-25 05:09:23 +01:00
return repo , nil
}
2021-11-24 17:49:20 +08:00
func adoptRepository ( ctx context . Context , repoPath string , u * user_model . User , repo * models . Repository , opts models . CreateRepoOptions ) ( err error ) {
2021-11-16 21:30:11 +08:00
isExist , err := util . IsExist ( repoPath )
if err != nil {
log . Error ( "Unable to check if %s exists. Error: %v" , repoPath , err )
return err
}
if ! isExist {
return fmt . Errorf ( "adoptRepository: path does not already exist: %s" , repoPath )
}
if err := repo_module . CreateDelegateHooks ( repoPath ) ; err != nil {
return fmt . Errorf ( "createDelegateHooks: %v" , err )
}
// Re-fetch the repository from database before updating it (else it would
// override changes that were done earlier with sql)
if repo , err = models . GetRepositoryByIDCtx ( ctx , repo . ID ) ; err != nil {
return fmt . Errorf ( "getRepositoryByID: %v" , err )
}
repo . IsEmpty = false
gitRepo , err := git . OpenRepository ( repo . RepoPath ( ) )
if err != nil {
return fmt . Errorf ( "openRepository: %v" , err )
}
defer gitRepo . Close ( )
if len ( opts . DefaultBranch ) > 0 {
repo . DefaultBranch = opts . DefaultBranch
if err = gitRepo . SetDefaultBranch ( repo . DefaultBranch ) ; err != nil {
return fmt . Errorf ( "setDefaultBranch: %v" , err )
}
} else {
repo . DefaultBranch , err = gitRepo . GetDefaultBranch ( )
if err != nil {
repo . DefaultBranch = setting . Repository . DefaultBranch
if err = gitRepo . SetDefaultBranch ( repo . DefaultBranch ) ; err != nil {
return fmt . Errorf ( "setDefaultBranch: %v" , err )
}
}
repo . DefaultBranch = strings . TrimPrefix ( repo . DefaultBranch , git . BranchPrefix )
}
2021-12-08 19:08:16 +00:00
branches , _ , _ := gitRepo . GetBranchNames ( 0 , 0 )
2021-11-16 21:30:11 +08:00
found := false
hasDefault := false
hasMaster := false
hasMain := false
for _ , branch := range branches {
if branch == repo . DefaultBranch {
found = true
break
} else if branch == setting . Repository . DefaultBranch {
hasDefault = true
} else if branch == "master" {
hasMaster = true
} else if branch == "main" {
hasMain = true
}
}
if ! found {
if hasDefault {
repo . DefaultBranch = setting . Repository . DefaultBranch
} else if hasMaster {
repo . DefaultBranch = "master"
} else if hasMain {
repo . DefaultBranch = "main"
} else if len ( branches ) > 0 {
repo . DefaultBranch = branches [ 0 ]
} else {
repo . IsEmpty = true
repo . DefaultBranch = setting . Repository . DefaultBranch
}
if err = gitRepo . SetDefaultBranch ( repo . DefaultBranch ) ; err != nil {
return fmt . Errorf ( "setDefaultBranch: %v" , err )
}
}
if err = models . UpdateRepositoryCtx ( ctx , repo , false ) ; err != nil {
return fmt . Errorf ( "updateRepository: %v" , err )
}
return nil
}
2020-09-25 05:09:23 +01:00
// DeleteUnadoptedRepository deletes unadopted repository files from the filesystem
2021-11-24 17:49:20 +08:00
func DeleteUnadoptedRepository ( doer , u * user_model . User , repoName string ) error {
2020-09-25 05:09:23 +01:00
if err := models . IsUsableRepoName ( repoName ) ; err != nil {
return err
}
repoPath := models . RepoPath ( u . Name , repoName )
2020-11-28 02:42:08 +00: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 05:09:23 +01:00
return models . ErrRepoNotExist {
OwnerName : u . Name ,
Name : repoName ,
}
}
if exist , err := models . IsRepositoryExist ( u , repoName ) ; err != nil {
return err
} else if exist {
return models . ErrRepoAlreadyExist {
Uname : u . Name ,
Name : repoName ,
}
}
return util . RemoveAll ( repoPath )
}
// ListUnadoptedRepositories lists all the unadopted repositories that match the provided query
2021-09-24 19:32:56 +08:00
func ListUnadoptedRepositories ( query string , opts * db . ListOptions ) ( [ ] string , int , error ) {
2020-09-25 05:09:23 +01:00
globUser , _ := glob . Compile ( "*" )
globRepo , _ := glob . Compile ( "*" )
qsplit := strings . SplitN ( query , "/" , 2 )
if len ( qsplit ) > 0 && len ( query ) > 0 {
var err error
globUser , err = glob . Compile ( qsplit [ 0 ] )
if err != nil {
2021-07-08 07:38:13 -04:00
log . Info ( "Invalid glob expression '%s' (skipped): %v" , qsplit [ 0 ] , err )
2020-09-25 05:09:23 +01:00
}
if len ( qsplit ) > 1 {
globRepo , err = glob . Compile ( qsplit [ 1 ] )
if err != nil {
2021-07-08 07:38:13 -04:00
log . Info ( "Invalid glob expression '%s' (skipped): %v" , qsplit [ 1 ] , err )
2020-09-25 05:09:23 +01:00
}
}
}
start := ( opts . Page - 1 ) * opts . PageSize
end := start + opts . PageSize
repoNamesToCheck := make ( [ ] string , 0 , opts . PageSize )
repoNames := make ( [ ] string , 0 , opts . PageSize )
2021-11-24 17:49:20 +08:00
var ctxUser * user_model . User
2020-09-25 05:09:23 +01:00
count := 0
// We're going to iterate by pagesize.
2021-11-15 06:02:53 +00:00
root := filepath . Clean ( setting . RepoRootPath )
2020-09-25 05:09:23 +01:00
if err := filepath . Walk ( root , func ( path string , info os . FileInfo , err error ) error {
if err != nil {
return err
}
if ! info . IsDir ( ) || path == root {
return nil
}
if ! strings . ContainsRune ( path [ len ( root ) + 1 : ] , filepath . Separator ) {
// Got a new user
// Clean up old repoNamesToCheck
if len ( repoNamesToCheck ) > 0 {
2021-09-24 19:32:56 +08:00
repos , _ , err := models . GetUserRepositories ( & models . SearchRepoOptions {
Actor : ctxUser ,
Private : true ,
ListOptions : db . ListOptions {
Page : 1 ,
PageSize : opts . PageSize ,
} , LowerNames : repoNamesToCheck } )
2020-09-25 05:09:23 +01:00
if err != nil {
return err
}
for _ , name := range repoNamesToCheck {
found := false
repoLoopCatchup :
for i , repo := range repos {
if repo . LowerName == name {
found = true
repos = append ( repos [ : i ] , repos [ i + 1 : ] ... )
break repoLoopCatchup
}
}
if ! found {
if count >= start && count < end {
repoNames = append ( repoNames , fmt . Sprintf ( "%s/%s" , ctxUser . Name , name ) )
}
count ++
}
}
repoNamesToCheck = repoNamesToCheck [ : 0 ]
}
if ! globUser . Match ( info . Name ( ) ) {
return filepath . SkipDir
}
2021-11-24 17:49:20 +08:00
ctxUser , err = user_model . GetUserByName ( info . Name ( ) )
2020-09-25 05:09:23 +01:00
if err != nil {
2021-11-24 17:49:20 +08:00
if user_model . IsErrUserNotExist ( err ) {
2020-09-25 05:09:23 +01:00
log . Debug ( "Missing user: %s" , info . Name ( ) )
return filepath . SkipDir
}
return err
}
return nil
}
name := info . Name ( )
if ! strings . HasSuffix ( name , ".git" ) {
return filepath . SkipDir
}
name = name [ : len ( name ) - 4 ]
if models . IsUsableRepoName ( name ) != nil || strings . ToLower ( name ) != name || ! globRepo . Match ( name ) {
return filepath . SkipDir
}
if count < end {
repoNamesToCheck = append ( repoNamesToCheck , name )
if len ( repoNamesToCheck ) >= opts . PageSize {
2021-09-24 19:32:56 +08:00
repos , _ , err := models . GetUserRepositories ( & models . SearchRepoOptions {
Actor : ctxUser ,
Private : true ,
ListOptions : db . ListOptions {
Page : 1 ,
PageSize : opts . PageSize ,
} , LowerNames : repoNamesToCheck } )
2020-09-25 05:09:23 +01:00
if err != nil {
return err
}
for _ , name := range repoNamesToCheck {
found := false
repoLoop :
for i , repo := range repos {
2021-05-07 02:21:34 +01:00
if repo . LowerName == name {
2020-09-25 05:09:23 +01:00
found = true
repos = append ( repos [ : i ] , repos [ i + 1 : ] ... )
break repoLoop
}
}
if ! found {
if count >= start && count < end {
repoNames = append ( repoNames , fmt . Sprintf ( "%s/%s" , ctxUser . Name , name ) )
}
count ++
}
}
repoNamesToCheck = repoNamesToCheck [ : 0 ]
}
return filepath . SkipDir
}
count ++
return filepath . SkipDir
} ) ; err != nil {
return nil , 0 , err
}
if len ( repoNamesToCheck ) > 0 {
2021-09-24 19:32:56 +08:00
repos , _ , err := models . GetUserRepositories ( & models . SearchRepoOptions {
Actor : ctxUser ,
Private : true ,
ListOptions : db . ListOptions {
Page : 1 ,
PageSize : opts . PageSize ,
} , LowerNames : repoNamesToCheck } )
2020-09-25 05:09:23 +01:00
if err != nil {
return nil , 0 , err
}
for _ , name := range repoNamesToCheck {
found := false
repoLoop :
for i , repo := range repos {
if repo . LowerName == name {
found = true
repos = append ( repos [ : i ] , repos [ i + 1 : ] ... )
break repoLoop
}
}
if ! found {
if count >= start && count < end {
repoNames = append ( repoNames , fmt . Sprintf ( "%s/%s" , ctxUser . Name , name ) )
}
count ++
}
}
}
return repoNames , count , nil
}