2020-09-25 07:09:23 +03:00
// Copyright 2020 The Gitea Authors. All rights reserved.
2022-11-27 21:20:29 +03:00
// SPDX-License-Identifier: MIT
2020-09-25 07:09:23 +03:00
package repository
import (
2021-09-23 18:45:36 +03:00
"context"
2020-09-25 07:09:23 +03:00
"fmt"
"os"
2022-11-01 02:16:48 +03:00
"path"
2020-09-25 07:09:23 +03:00
"path/filepath"
"strings"
2021-09-19 14:49:59 +03:00
"code.gitea.io/gitea/models/db"
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"
2022-10-12 08:18:26 +03:00
"code.gitea.io/gitea/modules/container"
2020-09-25 07:09:23 +03:00
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/log"
2021-11-16 16:30:11 +03:00
"code.gitea.io/gitea/modules/notification"
repo_module "code.gitea.io/gitea/modules/repository"
2020-09-25 07:09:23 +03:00
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util"
2021-11-16 16:30:11 +03:00
2020-09-25 07:09:23 +03:00
"github.com/gobwas/glob"
)
2021-11-16 16:30:11 +03:00
// AdoptRepository adopts pre-existing repository files for the user/organization.
2022-08-25 05:31:57 +03:00
func AdoptRepository ( doer , u * user_model . User , opts repo_module . CreateRepoOptions ) ( * repo_model . Repository , error ) {
2020-09-25 07:09:23 +03:00
if ! doer . IsAdmin && ! u . CanCreateRepo ( ) {
2021-12-12 18:48:20 +03:00
return nil , repo_model . ErrReachLimitOfRepo {
2020-09-25 07:09:23 +03:00
Limit : u . MaxRepoCreation ,
}
}
if len ( opts . DefaultBranch ) == 0 {
opts . DefaultBranch = setting . Repository . DefaultBranch
}
2021-12-10 04:27:50 +03:00
repo := & repo_model . Repository {
2020-09-25 07:09:23 +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 ,
CloseIssuesViaCommitInAnyBranch : setting . Repository . DefaultCloseIssuesViaCommitsInAnyBranch ,
Status : opts . Status ,
IsEmpty : ! opts . AutoInit ,
}
2022-11-12 23:18:50 +03:00
if err := db . WithTx ( db . DefaultContext , func ( ctx context . Context ) error {
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 {
2021-12-10 04:27:50 +03:00
return repo_model . ErrRepoNotExist {
2020-09-25 07:09:23 +03:00
OwnerName : u . Name ,
Name : repo . Name ,
}
}
2022-08-25 05:31:57 +03:00
if err := repo_module . CreateRepositoryByExample ( ctx , doer , u , repo , true ) ; err != nil {
2020-09-25 07:09:23 +03:00
return err
}
if err := adoptRepository ( ctx , repoPath , doer , repo , opts ) ; err != nil {
2022-10-24 22:29:17 +03:00
return fmt . Errorf ( "createDelegateHooks: %w" , err )
2020-09-25 07:09:23 +03:00
}
2022-06-06 11:01:49 +03:00
if err := repo_module . CheckDaemonExportOK ( ctx , repo ) ; err != nil {
2022-10-24 22:29:17 +03:00
return fmt . Errorf ( "checkDaemonExportOK: %w" , err )
2021-10-13 22:47:02 +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 := repo_module . InitializeLabels ( ctx , repo . ID , opts . IssueLabels , false ) ; err != nil {
2022-10-24 22:29:17 +03:00
return fmt . Errorf ( "InitializeLabels: %w" , err )
2020-09-25 07:09:23 +03:00
}
}
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 )
2022-10-24 22:29:17 +03:00
return fmt . Errorf ( "CreateRepository(git update-server-info): %w" , err )
2020-09-25 07:09:23 +03:00
}
return nil
} ) ; err != nil {
return nil , err
}
2022-11-19 11:12:33 +03:00
notification . NotifyCreateRepository ( db . DefaultContext , doer , u , repo )
2021-11-16 16:30:11 +03:00
2020-09-25 07:09:23 +03:00
return repo , nil
}
2022-08-25 05:31:57 +03:00
func adoptRepository ( ctx context . Context , repoPath string , u * user_model . User , repo * repo_model . Repository , opts repo_module . CreateRepoOptions ) ( err error ) {
2021-11-16 16:30:11 +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 {
return fmt . Errorf ( "adoptRepository: path does not already exist: %s" , repoPath )
}
if err := repo_module . CreateDelegateHooks ( repoPath ) ; err != nil {
2022-10-24 22:29:17 +03:00
return fmt . Errorf ( "createDelegateHooks: %w" , err )
2021-11-16 16:30:11 +03:00
}
// Re-fetch the repository from database before updating it (else it would
// override changes that were done earlier with sql)
2022-12-03 05:48:26 +03:00
if repo , err = repo_model . GetRepositoryByID ( ctx , repo . ID ) ; err != nil {
2022-10-24 22:29:17 +03:00
return fmt . Errorf ( "getRepositoryByID: %w" , err )
2021-11-16 16:30:11 +03:00
}
repo . IsEmpty = false
2022-01-20 02:26:57 +03:00
// Don't bother looking this repo in the context it won't be there
2022-03-29 22:13:41 +03:00
gitRepo , err := git . OpenRepository ( ctx , repo . RepoPath ( ) )
2021-11-16 16:30:11 +03:00
if err != nil {
2022-10-24 22:29:17 +03:00
return fmt . Errorf ( "openRepository: %w" , err )
2021-11-16 16:30:11 +03:00
}
defer gitRepo . Close ( )
2022-01-20 02:26:57 +03:00
2021-11-16 16:30:11 +03:00
if len ( opts . DefaultBranch ) > 0 {
repo . DefaultBranch = opts . DefaultBranch
if err = gitRepo . SetDefaultBranch ( repo . DefaultBranch ) ; err != nil {
2022-10-24 22:29:17 +03:00
return fmt . Errorf ( "setDefaultBranch: %w" , err )
2021-11-16 16:30:11 +03:00
}
} else {
repo . DefaultBranch , err = gitRepo . GetDefaultBranch ( )
if err != nil {
repo . DefaultBranch = setting . Repository . DefaultBranch
if err = gitRepo . SetDefaultBranch ( repo . DefaultBranch ) ; err != nil {
2022-10-24 22:29:17 +03:00
return fmt . Errorf ( "setDefaultBranch: %w" , err )
2021-11-16 16:30:11 +03:00
}
}
}
2021-12-08 22:08:16 +03:00
branches , _ , _ := gitRepo . GetBranchNames ( 0 , 0 )
2021-11-16 16:30:11 +03: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 {
2022-10-24 22:29:17 +03:00
return fmt . Errorf ( "setDefaultBranch: %w" , err )
2021-11-16 16:30:11 +03:00
}
}
2022-06-06 11:01:49 +03:00
if err = repo_module . UpdateRepository ( ctx , repo , false ) ; err != nil {
2022-10-24 22:29:17 +03:00
return fmt . Errorf ( "updateRepository: %w" , err )
2021-11-16 16:30:11 +03:00
}
return nil
}
2020-09-25 07:09:23 +03:00
// DeleteUnadoptedRepository deletes unadopted repository files from the filesystem
2021-11-24 12:49:20 +03:00
func DeleteUnadoptedRepository ( doer , u * user_model . User , repoName string ) error {
2021-12-12 18:48:20 +03:00
if err := repo_model . IsUsableRepoName ( repoName ) ; err != nil {
2020-09-25 07:09:23 +03:00
return err
}
2021-12-10 04:27:50 +03:00
repoPath := repo_model . RepoPath ( u . Name , repoName )
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 {
2021-12-10 04:27:50 +03:00
return repo_model . ErrRepoNotExist {
2020-09-25 07:09:23 +03:00
OwnerName : u . Name ,
Name : repoName ,
}
}
2022-05-20 17:08:52 +03:00
if exist , err := repo_model . IsRepositoryExist ( db . DefaultContext , u , repoName ) ; err != nil {
2020-09-25 07:09:23 +03:00
return err
} else if exist {
2021-12-12 18:48:20 +03:00
return repo_model . ErrRepoAlreadyExist {
2020-09-25 07:09:23 +03:00
Uname : u . Name ,
Name : repoName ,
}
}
return util . RemoveAll ( repoPath )
}
2022-11-01 02:16:48 +03:00
type unadoptedRepositories struct {
2022-01-01 05:52:00 +03:00
repositories [ ] string
index int
start int
end int
}
2022-11-01 02:16:48 +03:00
func ( unadopted * unadoptedRepositories ) add ( repository string ) {
2022-01-01 05:52:00 +03:00
if unadopted . index >= unadopted . start && unadopted . index < unadopted . end {
unadopted . repositories = append ( unadopted . repositories , repository )
}
unadopted . index ++
}
2022-11-01 02:16:48 +03:00
func checkUnadoptedRepositories ( userName string , repoNamesToCheck [ ] string , unadopted * unadoptedRepositories ) error {
2022-01-01 05:52:00 +03:00
if len ( repoNamesToCheck ) == 0 {
return nil
}
2022-05-20 17:08:52 +03:00
ctxUser , err := user_model . GetUserByName ( db . DefaultContext , userName )
2022-01-01 05:52:00 +03:00
if err != nil {
if user_model . IsErrUserNotExist ( err ) {
log . Debug ( "Missing user: %s" , userName )
return nil
}
return err
}
2022-06-06 11:01:49 +03:00
repos , _ , err := repo_model . GetUserRepositories ( & repo_model . SearchRepoOptions {
2022-01-01 05:52:00 +03:00
Actor : ctxUser ,
Private : true ,
ListOptions : db . ListOptions {
Page : 1 ,
PageSize : len ( repoNamesToCheck ) ,
2022-01-20 20:46:10 +03:00
} , LowerNames : repoNamesToCheck ,
} )
2022-01-01 05:52:00 +03:00
if err != nil {
return err
}
if len ( repos ) == len ( repoNamesToCheck ) {
return nil
}
2022-10-12 08:18:26 +03:00
repoNames := make ( container . Set [ string ] , len ( repos ) )
2022-01-01 05:52:00 +03:00
for _ , repo := range repos {
2022-10-12 08:18:26 +03:00
repoNames . Add ( repo . LowerName )
2022-01-01 05:52:00 +03:00
}
for _ , repoName := range repoNamesToCheck {
2022-10-12 08:18:26 +03:00
if ! repoNames . Contains ( repoName ) {
2022-11-01 02:16:48 +03:00
unadopted . add ( path . Join ( userName , repoName ) ) // These are not used as filepaths - but as reponames - therefore use path.Join not filepath.Join
2022-01-01 05:52:00 +03:00
}
}
return nil
}
2020-09-25 07:09:23 +03:00
// ListUnadoptedRepositories lists all the unadopted repositories that match the provided query
2021-09-24 14:32:56 +03:00
func ListUnadoptedRepositories ( query string , opts * db . ListOptions ) ( [ ] string , int , error ) {
2020-09-25 07:09:23 +03: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 14:38:13 +03:00
log . Info ( "Invalid glob expression '%s' (skipped): %v" , qsplit [ 0 ] , err )
2020-09-25 07:09:23 +03:00
}
if len ( qsplit ) > 1 {
globRepo , err = glob . Compile ( qsplit [ 1 ] )
if err != nil {
2021-07-08 14:38:13 +03:00
log . Info ( "Invalid glob expression '%s' (skipped): %v" , qsplit [ 1 ] , err )
2020-09-25 07:09:23 +03:00
}
}
}
2022-01-01 05:52:00 +03:00
var repoNamesToCheck [ ] string
2020-09-25 07:09:23 +03:00
2022-01-01 05:52:00 +03:00
start := ( opts . Page - 1 ) * opts . PageSize
2022-11-01 02:16:48 +03:00
unadopted := & unadoptedRepositories {
2022-01-01 05:52:00 +03:00
repositories : make ( [ ] string , 0 , opts . PageSize ) ,
start : start ,
end : start + opts . PageSize ,
index : 0 ,
}
2020-09-25 07:09:23 +03:00
2022-01-01 05:52:00 +03:00
var userName string
2020-09-25 07:09:23 +03:00
// We're going to iterate by pagesize.
2021-11-15 09:02:53 +03:00
root := filepath . Clean ( setting . RepoRootPath )
2020-09-25 07:09:23 +03: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
2022-01-01 05:52:00 +03:00
if err = checkUnadoptedRepositories ( userName , repoNamesToCheck , unadopted ) ; err != nil {
return err
2020-09-25 07:09:23 +03:00
}
2022-01-01 05:52:00 +03:00
repoNamesToCheck = repoNamesToCheck [ : 0 ]
2020-09-25 07:09:23 +03:00
if ! globUser . Match ( info . Name ( ) ) {
return filepath . SkipDir
}
2022-01-01 05:52:00 +03:00
userName = info . Name ( )
2020-09-25 07:09:23 +03:00
return nil
}
name := info . Name ( )
if ! strings . HasSuffix ( name , ".git" ) {
return filepath . SkipDir
}
name = name [ : len ( name ) - 4 ]
2021-12-12 18:48:20 +03:00
if repo_model . IsUsableRepoName ( name ) != nil || strings . ToLower ( name ) != name || ! globRepo . Match ( name ) {
2020-09-25 07:09:23 +03:00
return filepath . SkipDir
}
2022-01-01 05:52:00 +03:00
repoNamesToCheck = append ( repoNamesToCheck , name )
2022-12-16 11:58:56 +03:00
if len ( repoNamesToCheck ) >= setting . Database . IterateBufferSize {
2022-03-21 11:09:42 +03:00
if err = checkUnadoptedRepositories ( userName , repoNamesToCheck , unadopted ) ; err != nil {
return err
}
repoNamesToCheck = repoNamesToCheck [ : 0 ]
}
2020-09-25 07:09:23 +03:00
return filepath . SkipDir
} ) ; err != nil {
return nil , 0 , err
}
2022-01-01 05:52:00 +03:00
if err := checkUnadoptedRepositories ( userName , repoNamesToCheck , unadopted ) ; err != nil {
return nil , 0 , err
2020-09-25 07:09:23 +03:00
}
2022-01-01 05:52:00 +03:00
return unadopted . repositories , unadopted . index , nil
2020-09-25 07:09:23 +03:00
}