2020-01-12 15:11:17 +03:00
// Copyright 2019 The Gitea Authors. All rights reserved.
2022-11-27 21:20:29 +03:00
// SPDX-License-Identifier: MIT
2020-01-12 15:11:17 +03:00
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"
2023-01-13 21:54:02 +03:00
"path/filepath"
2020-01-12 15:11:17 +03:00
"strings"
"code.gitea.io/gitea/models"
2022-08-25 05:31:57 +03:00
activities_model "code.gitea.io/gitea/models/activities"
2021-09-19 14:49:59 +03:00
"code.gitea.io/gitea/models/db"
2022-06-12 18:51:54 +03:00
git_model "code.gitea.io/gitea/models/git"
2022-08-25 05:31:57 +03:00
"code.gitea.io/gitea/models/organization"
"code.gitea.io/gitea/models/perm"
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"
2022-08-25 05:31:57 +03:00
"code.gitea.io/gitea/models/unit"
2021-11-24 12:49:20 +03:00
user_model "code.gitea.io/gitea/models/user"
2022-08-25 05:31:57 +03:00
"code.gitea.io/gitea/models/webhook"
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
)
2022-08-25 05:31:57 +03:00
// CreateRepositoryByExample creates a repository for the user/organization.
2023-02-04 09:48:38 +03:00
func CreateRepositoryByExample ( ctx context . Context , doer , u * user_model . User , repo * repo_model . Repository , overwriteOrAdopt , isFork bool ) ( err error ) {
2022-08-25 05:31:57 +03:00
if err = repo_model . IsUsableRepoName ( repo . Name ) ; err != nil {
return err
}
has , err := repo_model . IsRepositoryExist ( ctx , u , repo . Name )
if err != nil {
2022-10-24 22:29:17 +03:00
return fmt . Errorf ( "IsRepositoryExist: %w" , err )
2022-08-25 05:31:57 +03:00
} else if has {
return repo_model . ErrRepoAlreadyExist {
Uname : u . Name ,
Name : repo . Name ,
}
}
repoPath := repo_model . RepoPath ( u . Name , repo . Name )
isExist , err := util . IsExist ( repoPath )
if err != nil {
log . Error ( "Unable to check if %s exists. Error: %v" , repoPath , err )
return err
}
if ! overwriteOrAdopt && isExist {
log . Error ( "Files already exist in %s and we are not going to adopt or delete." , repoPath )
return repo_model . ErrRepoFilesAlreadyExist {
Uname : u . Name ,
Name : repo . Name ,
}
}
if err = db . Insert ( ctx , repo ) ; err != nil {
return err
}
if err = repo_model . DeleteRedirect ( ctx , u . ID , repo . Name ) ; err != nil {
return err
}
// insert units for repo
2023-02-04 09:48:38 +03:00
defaultUnits := unit . DefaultRepoUnits
if isFork {
defaultUnits = unit . DefaultForkRepoUnits
}
units := make ( [ ] repo_model . RepoUnit , 0 , len ( defaultUnits ) )
for _ , tp := range defaultUnits {
2022-08-25 05:31:57 +03:00
if tp == unit . TypeIssues {
units = append ( units , repo_model . RepoUnit {
RepoID : repo . ID ,
Type : tp ,
Config : & repo_model . IssuesConfig {
EnableTimetracker : setting . Service . DefaultEnableTimetracking ,
AllowOnlyContributorsToTrackTime : setting . Service . DefaultAllowOnlyContributorsToTrackTime ,
EnableDependencies : setting . Service . DefaultEnableDependencies ,
} ,
} )
} else if tp == unit . TypePullRequests {
units = append ( units , repo_model . RepoUnit {
RepoID : repo . ID ,
Type : tp ,
Config : & repo_model . PullRequestsConfig { AllowMerge : true , AllowRebase : true , AllowRebaseMerge : true , AllowSquash : true , DefaultMergeStyle : repo_model . MergeStyle ( setting . Repository . PullRequest . DefaultMergeStyle ) , AllowRebaseUpdate : true } ,
} )
} else {
units = append ( units , repo_model . RepoUnit {
RepoID : repo . ID ,
Type : tp ,
} )
}
}
if err = db . Insert ( ctx , units ) ; err != nil {
return err
}
// Remember visibility preference.
u . LastRepoVisibility = repo . IsPrivate
if err = user_model . UpdateUserCols ( ctx , u , "last_repo_visibility" ) ; err != nil {
2022-10-24 22:29:17 +03:00
return fmt . Errorf ( "UpdateUserCols: %w" , err )
2022-08-25 05:31:57 +03:00
}
if err = user_model . IncrUserRepoNum ( ctx , u . ID ) ; err != nil {
2022-10-24 22:29:17 +03:00
return fmt . Errorf ( "IncrUserRepoNum: %w" , err )
2022-08-25 05:31:57 +03:00
}
u . NumRepos ++
// Give access to all members in teams with access to all repositories.
if u . IsOrganization ( ) {
teams , err := organization . FindOrgTeams ( ctx , u . ID )
if err != nil {
2022-10-24 22:29:17 +03:00
return fmt . Errorf ( "FindOrgTeams: %w" , err )
2022-08-25 05:31:57 +03:00
}
for _ , t := range teams {
if t . IncludesAllRepositories {
if err := models . AddRepository ( ctx , t , repo ) ; err != nil {
2022-10-24 22:29:17 +03:00
return fmt . Errorf ( "AddRepository: %w" , err )
2022-08-25 05:31:57 +03:00
}
}
}
if isAdmin , err := access_model . IsUserRepoAdmin ( ctx , repo , doer ) ; err != nil {
2022-10-24 22:29:17 +03:00
return fmt . Errorf ( "IsUserRepoAdmin: %w" , err )
2022-08-25 05:31:57 +03:00
} else if ! isAdmin {
// Make creator repo admin if it wasn't assigned automatically
2022-12-10 05:46:31 +03:00
if err = AddCollaborator ( ctx , repo , doer ) ; err != nil {
return fmt . Errorf ( "AddCollaborator: %w" , err )
2022-08-25 05:31:57 +03:00
}
2022-12-10 05:46:31 +03:00
if err = repo_model . ChangeCollaborationAccessMode ( ctx , repo , doer . ID , perm . AccessModeAdmin ) ; err != nil {
2022-10-24 22:29:17 +03:00
return fmt . Errorf ( "ChangeCollaborationAccessModeCtx: %w" , err )
2022-08-25 05:31:57 +03:00
}
}
} else if err = access_model . RecalculateAccesses ( ctx , repo ) ; err != nil {
// Organization automatically called this in AddRepository method.
2022-10-24 22:29:17 +03:00
return fmt . Errorf ( "RecalculateAccesses: %w" , err )
2022-08-25 05:31:57 +03:00
}
if setting . Service . AutoWatchNewRepos {
if err = repo_model . WatchRepo ( ctx , doer . ID , repo . ID , true ) ; err != nil {
2022-10-24 22:29:17 +03:00
return fmt . Errorf ( "WatchRepo: %w" , err )
2022-08-25 05:31:57 +03:00
}
}
if err = webhook . CopyDefaultWebhooksToRepo ( ctx , repo . ID ) ; err != nil {
2022-10-24 22:29:17 +03:00
return fmt . Errorf ( "CopyDefaultWebhooksToRepo: %w" , err )
2022-08-25 05:31:57 +03:00
}
return nil
}
// CreateRepoOptions contains the create repository options
type CreateRepoOptions struct {
Name string
Description string
OriginalURL string
GitServiceType api . GitServiceType
Gitignores string
IssueLabels string
License string
Readme string
DefaultBranch string
IsPrivate bool
IsMirror bool
IsTemplate bool
AutoInit bool
Status repo_model . RepositoryStatus
TrustModel repo_model . TrustModelType
MirrorInterval string
}
2020-01-12 15:11:17 +03:00
// CreateRepository creates a repository for the user/organization.
2022-08-25 05:31:57 +03:00
func CreateRepository ( doer , u * user_model . User , opts 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 ,
2023-02-05 13:12:31 +03:00
DefaultBranch : opts . DefaultBranch ,
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
2022-11-12 23:18:50 +03:00
if err := db . WithTx ( db . DefaultContext , func ( ctx context . Context ) error {
2023-02-04 09:48:38 +03:00
if err := CreateRepositoryByExample ( ctx , doer , u , repo , false , 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
}
2022-10-24 22:29:17 +03:00
return fmt . Errorf ( "initRepository: %w" , err )
2020-09-25 07:09:23 +03:00
}
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
2022-10-24 22:29:17 +03:00
return fmt . Errorf ( "InitializeLabels: %w" , 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 {
2022-10-24 22:29:17 +03:00
return fmt . Errorf ( "checkDaemonExportOK: %w" , err )
2021-10-13 22:47:02 +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 )
2021-01-18 23:00:50 +03:00
rollbackRepo = repo
rollbackRepo . OwnerID = u . ID
2022-10-24 22:29:17 +03:00
return fmt . Errorf ( "CreateRepository(git update-server-info): %w" , 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
2023-01-13 21:54:02 +03:00
const notRegularFileMode = os . ModeSymlink | os . ModeNamedPipe | os . ModeSocket | os . ModeDevice | os . ModeCharDevice | os . ModeIrregular
// getDirectorySize returns the disk consumption for a given path
func getDirectorySize ( path string ) ( int64 , error ) {
var size int64
err := filepath . WalkDir ( path , func ( _ string , info os . DirEntry , err error ) error {
if err != nil {
if os . IsNotExist ( err ) { // ignore the error because the file maybe deleted during traversing.
return nil
}
return err
}
if info . IsDir ( ) {
return nil
}
f , err := info . Info ( )
if err != nil {
return err
}
if ( f . Mode ( ) & notRegularFileMode ) == 0 {
size += f . Size ( )
}
return err
} )
return size , err
}
// UpdateRepoSize updates the repository size, calculating it using getDirectorySize
2022-06-06 11:01:49 +03:00
func UpdateRepoSize ( ctx context . Context , repo * repo_model . Repository ) error {
2023-01-13 21:54:02 +03:00
size , err := getDirectorySize ( repo . RepoPath ( ) )
2022-06-06 11:01:49 +03:00
if err != nil {
2022-10-24 22:29:17 +03:00
return fmt . Errorf ( "updateSize: %w" , err )
2022-06-06 11:01:49 +03:00
}
2022-06-12 18:51:54 +03:00
lfsSize , err := git_model . GetRepoLFSSize ( ctx , repo . ID )
2022-06-06 11:01:49 +03:00
if err != nil {
2022-10-24 22:29:17 +03:00
return fmt . Errorf ( "updateSize: GetLFSMetaObjects: %w" , err )
2022-06-06 11:01:49 +03:00
}
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 )
e := db . GetEngine ( ctx )
if _ , err = e . ID ( repo . ID ) . AllCols ( ) . Update ( repo ) ; err != nil {
2022-10-24 22:29:17 +03:00
return fmt . Errorf ( "update: %w" , err )
2022-06-06 11:01:49 +03:00
}
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 {
2022-10-24 22:29:17 +03:00
return fmt . Errorf ( "getOwner: %w" , err )
2022-06-06 11:01:49 +03:00
}
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 {
2022-10-24 22:29:17 +03:00
return fmt . Errorf ( "recalculateTeamAccesses: %w" , err )
2022-06-06 11:01:49 +03:00
}
}
// If repo has become private, we need to set its actions to private.
if repo . IsPrivate {
2022-08-25 05:31:57 +03:00
_ , err = e . Where ( "repo_id = ?" , repo . ID ) . Cols ( "is_private" ) . Update ( & activities_model . Action {
2022-06-06 11:01:49 +03:00
IsPrivate : true ,
} )
if err != nil {
return err
}
}
// Create/Remove git-daemon-export-ok for git-daemon...
2022-06-20 15:38:58 +03:00
if err := CheckDaemonExportOK ( ctx , repo ) ; err != nil {
2022-06-06 11:01:49 +03:00
return err
}
forkRepos , err := repo_model . GetRepositoriesByForkID ( ctx , repo . ID )
if err != nil {
2022-10-24 22:29:17 +03:00
return fmt . Errorf ( "getRepositoriesByForkID: %w" , err )
2022-06-06 11:01:49 +03:00
}
for i := range forkRepos {
forkRepos [ i ] . IsPrivate = repo . IsPrivate || repo . Owner . Visibility == api . VisibleTypePrivate
if err = UpdateRepository ( ctx , forkRepos [ i ] , true ) ; err != nil {
2022-10-24 22:29:17 +03:00
return fmt . Errorf ( "updateRepository[%d]: %w" , forkRepos [ i ] . ID , err )
2022-06-06 11:01:49 +03:00
}
}
}
return nil
}