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"
"strings"
"time"
"code.gitea.io/gitea/models"
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"
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"
2021-11-16 16:30:11 +03:00
"code.gitea.io/gitea/modules/notification"
repo_module "code.gitea.io/gitea/modules/repository"
2020-06-07 03:45:12 +03:00
"code.gitea.io/gitea/modules/structs"
2021-09-14 19:16:40 +03:00
"code.gitea.io/gitea/modules/util"
2020-01-12 15:11:17 +03:00
)
2021-12-12 18:48:20 +03:00
// ForkRepoOptions contains the fork repository options
type ForkRepoOptions struct {
BaseRepo * repo_model . Repository
Name string
Description string
}
2020-01-12 15:11:17 +03:00
// ForkRepository forks a repository
2022-03-29 22:13:41 +03:00
func ForkRepository ( ctx context . Context , doer , owner * user_model . User , opts ForkRepoOptions ) ( * repo_model . Repository , error ) {
forkedRepo , err := repo_model . GetUserFork ( ctx , opts . BaseRepo . ID , owner . ID )
2020-01-12 15:11:17 +03:00
if err != nil {
return nil , err
}
if forkedRepo != nil {
return nil , models . ErrForkAlreadyExist {
Uname : owner . Name ,
2021-08-28 11:37:14 +03:00
RepoName : opts . BaseRepo . FullName ( ) ,
2020-01-12 15:11:17 +03:00
ForkName : forkedRepo . FullName ( ) ,
}
}
2021-12-10 04:27:50 +03:00
repo := & repo_model . Repository {
2020-01-12 15:11:17 +03:00
OwnerID : owner . ID ,
Owner : owner ,
OwnerName : owner . Name ,
2021-08-28 11:37:14 +03:00
Name : opts . Name ,
LowerName : strings . ToLower ( opts . Name ) ,
Description : opts . Description ,
DefaultBranch : opts . BaseRepo . DefaultBranch ,
IsPrivate : opts . BaseRepo . IsPrivate || opts . BaseRepo . Owner . Visibility == structs . VisibleTypePrivate ,
IsEmpty : opts . BaseRepo . IsEmpty ,
2020-01-12 15:11:17 +03:00
IsFork : true ,
2021-08-28 11:37:14 +03:00
ForkID : opts . BaseRepo . ID ,
2020-01-12 15:11:17 +03:00
}
2021-08-28 11:37:14 +03:00
oldRepoPath := opts . BaseRepo . RepoPath ( )
2020-01-12 15:11:17 +03:00
2021-09-14 19:16:40 +03:00
needsRollback := false
rollbackFn := func ( ) {
if ! needsRollback {
return
}
2021-12-10 04:27:50 +03:00
repoPath := repo_model . RepoPath ( owner . Name , repo . Name )
2021-09-14 19:16:40 +03:00
if exists , _ := util . IsExist ( repoPath ) ; ! exists {
return
}
// As the transaction will be failed and hence database changes will be destroyed we only need
// to delete the related repository on the filesystem
if errDelete := util . RemoveAll ( repoPath ) ; errDelete != nil {
log . Error ( "Failed to remove fork repo" )
}
}
needsRollbackInPanic := true
defer func ( ) {
panicErr := recover ( )
if panicErr == nil {
return
}
if needsRollbackInPanic {
rollbackFn ( )
}
panic ( panicErr )
} ( )
2022-03-29 22:13:41 +03:00
err = db . WithTx ( func ( txCtx context . Context ) error {
if err = models . CreateRepository ( txCtx , doer , owner , repo , false ) ; err != nil {
2020-01-12 15:11:17 +03:00
return err
}
2022-06-06 11:01:49 +03:00
if err = repo_model . IncrementRepoForkNum ( txCtx , opts . BaseRepo . ID ) ; err != nil {
2020-01-12 15:11:17 +03:00
return err
}
2021-04-15 00:15:28 +03:00
// copy lfs files failure should not be ignored
2022-06-12 18:51:54 +03:00
if err = git_model . CopyLFS ( txCtx , repo , opts . BaseRepo ) ; err != nil {
2021-04-15 00:15:28 +03:00
return err
}
2021-09-14 19:16:40 +03:00
needsRollback = true
2021-12-10 04:27:50 +03:00
repoPath := repo_model . RepoPath ( owner . Name , repo . Name )
2022-03-31 14:56:22 +03:00
if stdout , _ , err := git . NewCommand ( txCtx ,
2020-01-12 15:11:17 +03:00
"clone" , "--bare" , oldRepoPath , repoPath ) .
2021-08-28 11:37:14 +03:00
SetDescription ( fmt . Sprintf ( "ForkRepository(git clone): %s to %s" , opts . BaseRepo . FullName ( ) , repo . FullName ( ) ) ) .
2022-04-01 05:55:30 +03:00
RunStdBytes ( & git . RunOpts { Timeout : 10 * time . Minute } ) ; err != nil {
2021-08-28 11:37:14 +03:00
log . Error ( "Fork Repository (git clone) Failed for %v (from %v):\nStdout: %s\nError: %v" , repo , opts . BaseRepo , stdout , err )
2020-01-12 15:11:17 +03:00
return fmt . Errorf ( "git clone: %v" , err )
}
2022-06-06 11:01:49 +03:00
if err := repo_module . CheckDaemonExportOK ( txCtx , 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 ( txCtx , "update-server-info" ) .
2020-01-12 15:11:17 +03:00
SetDescription ( fmt . Sprintf ( "ForkRepository(git update-server-info): %s" , repo . FullName ( ) ) ) .
2022-04-01 05:55:30 +03:00
RunStdString ( & git . RunOpts { Dir : repoPath } ) ; err != nil {
2020-01-12 15:11:17 +03:00
log . Error ( "Fork Repository (git update-server-info) failed for %v:\nStdout: %s\nError: %v" , repo , stdout , err )
return fmt . Errorf ( "git update-server-info: %v" , err )
}
2021-11-16 16:30:11 +03:00
if err = repo_module . CreateDelegateHooks ( repoPath ) ; err != nil {
2020-01-12 15:11:17 +03:00
return fmt . Errorf ( "createDelegateHooks: %v" , err )
}
return nil
} )
2021-09-14 19:16:40 +03:00
needsRollbackInPanic = false
2020-01-12 15:11:17 +03:00
if err != nil {
2021-09-14 19:16:40 +03:00
rollbackFn ( )
2020-01-12 15:11:17 +03:00
return nil , err
}
2021-04-15 00:15:28 +03:00
// even if below operations failed, it could be ignored. And they will be retried
2022-06-06 11:01:49 +03:00
if err := repo_module . UpdateRepoSize ( ctx , repo ) ; err != nil {
2020-01-12 15:11:17 +03:00
log . Error ( "Failed to update size for repository: %v" , err )
}
2021-12-10 04:27:50 +03:00
if err := repo_model . CopyLanguageStat ( opts . BaseRepo , repo ) ; err != nil {
2022-02-12 06:18:06 +03:00
log . Error ( "Copy language stat from oldRepo failed: %v" , err )
}
2022-03-29 22:13:41 +03:00
gitRepo , err := git . OpenRepository ( ctx , repo . RepoPath ( ) )
2022-02-12 06:18:06 +03:00
if err != nil {
log . Error ( "Open created git repository failed: %v" , err )
} else {
defer gitRepo . Close ( )
if err := repo_module . SyncReleasesWithTags ( repo , gitRepo ) ; err != nil {
log . Error ( "Sync releases from git tags failed: %v" , err )
}
2020-04-08 15:13:04 +03:00
}
2020-09-25 07:09:23 +03:00
2021-11-16 16:30:11 +03:00
notification . NotifyForkRepository ( doer , opts . BaseRepo , repo )
2020-09-25 07:09:23 +03:00
return repo , nil
2020-01-12 15:11:17 +03:00
}
2021-09-14 20:07:08 +03:00
// ConvertForkToNormalRepository convert the provided repo from a forked repo to normal repo
2021-12-10 04:27:50 +03:00
func ConvertForkToNormalRepository ( repo * repo_model . Repository ) error {
2021-09-23 18:45:36 +03:00
err := db . WithTx ( func ( ctx context . Context ) error {
2021-12-10 04:27:50 +03:00
repo , err := repo_model . GetRepositoryByIDCtx ( ctx , repo . ID )
2021-09-14 20:07:08 +03:00
if err != nil {
return err
}
if ! repo . IsFork {
return nil
}
2022-06-06 11:01:49 +03:00
if err := repo_model . DecrementRepoForkNum ( ctx , repo . ForkID ) ; err != nil {
2021-09-14 20:07:08 +03:00
log . Error ( "Unable to decrement repo fork num for old root repo %d of repository %-v whilst converting from fork. Error: %v" , repo . ForkID , repo , err )
return err
}
repo . IsFork = false
repo . ForkID = 0
2022-06-06 11:01:49 +03:00
if err := repo_module . UpdateRepository ( ctx , repo , false ) ; err != nil {
2021-09-14 20:07:08 +03:00
log . Error ( "Unable to update repository %-v whilst converting from fork. Error: %v" , repo , err )
return err
}
return nil
} )
return err
}