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 (
"bytes"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"strings"
"time"
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/log"
2020-06-17 23:53:55 +03:00
"code.gitea.io/gitea/modules/setting"
2020-08-11 23:05:34 +03:00
"code.gitea.io/gitea/modules/util"
2020-01-12 15:11:17 +03:00
"github.com/unknwon/com"
)
func prepareRepoCommit ( ctx models . DBContext , repo * models . Repository , tmpDir , repoPath string , opts models . CreateRepoOptions ) error {
commitTimeStr := time . Now ( ) . Format ( time . RFC3339 )
authorSig := repo . Owner . NewGitSig ( )
// Because this may call hooks we should pass in the environment
env := append ( os . Environ ( ) ,
"GIT_AUTHOR_NAME=" + authorSig . Name ,
"GIT_AUTHOR_EMAIL=" + authorSig . Email ,
"GIT_AUTHOR_DATE=" + commitTimeStr ,
"GIT_COMMITTER_NAME=" + authorSig . Name ,
"GIT_COMMITTER_EMAIL=" + authorSig . Email ,
"GIT_COMMITTER_DATE=" + commitTimeStr ,
)
// Clone to temporary path and do the init commit.
if stdout , err := git . NewCommand ( "clone" , repoPath , tmpDir ) .
SetDescription ( fmt . Sprintf ( "prepareRepoCommit (git clone): %s to %s" , repoPath , tmpDir ) ) .
RunInDirWithEnv ( "" , env ) ; err != nil {
log . Error ( "Failed to clone from %v into %s: stdout: %s\nError: %v" , repo , tmpDir , stdout , err )
return fmt . Errorf ( "git clone: %v" , err )
}
// README
data , err := models . GetRepoInitFile ( "readme" , opts . Readme )
if err != nil {
return fmt . Errorf ( "GetRepoInitFile[%s]: %v" , opts . Readme , err )
}
cloneLink := repo . CloneLink ( )
match := map [ string ] string {
"Name" : repo . Name ,
"Description" : repo . Description ,
"CloneURL.SSH" : cloneLink . SSH ,
"CloneURL.HTTPS" : cloneLink . HTTPS ,
2020-04-07 04:40:38 +03:00
"OwnerName" : repo . OwnerName ,
2020-01-12 15:11:17 +03:00
}
if err = ioutil . WriteFile ( filepath . Join ( tmpDir , "README.md" ) ,
[ ] byte ( com . Expand ( string ( data ) , match ) ) , 0644 ) ; err != nil {
return fmt . Errorf ( "write README.md: %v" , err )
}
// .gitignore
if len ( opts . Gitignores ) > 0 {
var buf bytes . Buffer
names := strings . Split ( opts . Gitignores , "," )
for _ , name := range names {
data , err = models . GetRepoInitFile ( "gitignore" , name )
if err != nil {
return fmt . Errorf ( "GetRepoInitFile[%s]: %v" , name , err )
}
buf . WriteString ( "# ---> " + name + "\n" )
buf . Write ( data )
buf . WriteString ( "\n" )
}
if buf . Len ( ) > 0 {
if err = ioutil . WriteFile ( filepath . Join ( tmpDir , ".gitignore" ) , buf . Bytes ( ) , 0644 ) ; err != nil {
return fmt . Errorf ( "write .gitignore: %v" , err )
}
}
}
// LICENSE
if len ( opts . License ) > 0 {
data , err = models . GetRepoInitFile ( "license" , opts . License )
if err != nil {
return fmt . Errorf ( "GetRepoInitFile[%s]: %v" , opts . License , err )
}
if err = ioutil . WriteFile ( filepath . Join ( tmpDir , "LICENSE" ) , data , 0644 ) ; err != nil {
return fmt . Errorf ( "write LICENSE: %v" , err )
}
}
return nil
}
// initRepoCommit temporarily changes with work directory.
2020-03-26 22:14:51 +03:00
func initRepoCommit ( tmpPath string , repo * models . Repository , u * models . User , defaultBranch string ) ( err error ) {
2020-01-12 15:11:17 +03:00
commitTimeStr := time . Now ( ) . Format ( time . RFC3339 )
sig := u . NewGitSig ( )
// Because this may call hooks we should pass in the environment
env := append ( os . Environ ( ) ,
"GIT_AUTHOR_NAME=" + sig . Name ,
"GIT_AUTHOR_EMAIL=" + sig . Email ,
"GIT_AUTHOR_DATE=" + commitTimeStr ,
"GIT_COMMITTER_DATE=" + commitTimeStr ,
)
2020-09-19 19:44:55 +03:00
committerName := sig . Name
committerEmail := sig . Email
2020-01-12 15:11:17 +03:00
if stdout , err := git . NewCommand ( "add" , "--all" ) .
SetDescription ( fmt . Sprintf ( "initRepoCommit (git add): %s" , tmpPath ) ) .
RunInDir ( tmpPath ) ; err != nil {
log . Error ( "git add --all failed: Stdout: %s\nError: %v" , stdout , err )
return fmt . Errorf ( "git add --all: %v" , err )
}
2020-09-05 19:42:58 +03:00
err = git . LoadGitVersion ( )
2020-01-12 15:11:17 +03:00
if err != nil {
return fmt . Errorf ( "Unable to get git version: %v" , err )
}
args := [ ] string {
"commit" , fmt . Sprintf ( "--author='%s <%s>'" , sig . Name , sig . Email ) ,
"-m" , "Initial commit" ,
}
2020-10-21 18:42:08 +03:00
if git . CheckGitVersionAtLeast ( "1.7.9" ) == nil {
2020-09-19 19:44:55 +03:00
sign , keyID , signer , _ := models . SignInitialCommit ( tmpPath , u )
2020-01-12 15:11:17 +03:00
if sign {
args = append ( args , "-S" + keyID )
2020-09-19 19:44:55 +03:00
if repo . GetTrustModel ( ) == models . CommitterTrustModel || repo . GetTrustModel ( ) == models . CollaboratorCommitterTrustModel {
// need to set the committer to the KeyID owner
committerName = signer . Name
committerEmail = signer . Email
}
2020-10-21 18:42:08 +03:00
} else if git . CheckGitVersionAtLeast ( "2.0.0" ) == nil {
2020-01-12 15:11:17 +03:00
args = append ( args , "--no-gpg-sign" )
}
}
2020-09-19 19:44:55 +03:00
env = append ( env ,
"GIT_COMMITTER_NAME=" + committerName ,
"GIT_COMMITTER_EMAIL=" + committerEmail ,
)
2020-01-12 15:11:17 +03:00
if stdout , err := git . NewCommand ( args ... ) .
SetDescription ( fmt . Sprintf ( "initRepoCommit (git commit): %s" , tmpPath ) ) .
RunInDirWithEnv ( tmpPath , env ) ; err != nil {
log . Error ( "Failed to commit: %v: Stdout: %s\nError: %v" , args , stdout , err )
return fmt . Errorf ( "git commit: %v" , err )
}
2020-03-26 22:14:51 +03:00
if len ( defaultBranch ) == 0 {
2020-06-17 23:53:55 +03:00
defaultBranch = setting . Repository . DefaultBranch
2020-03-26 22:14:51 +03:00
}
if stdout , err := git . NewCommand ( "push" , "origin" , "master:" + defaultBranch ) .
2020-01-12 15:11:17 +03:00
SetDescription ( fmt . Sprintf ( "initRepoCommit (git push): %s" , tmpPath ) ) .
RunInDirWithEnv ( tmpPath , models . InternalPushingEnvironment ( u , repo ) ) ; err != nil {
log . Error ( "Failed to push back to master: Stdout: %s\nError: %v" , stdout , err )
return fmt . Errorf ( "git push: %v" , err )
}
return nil
}
2020-09-25 07:09:23 +03:00
func checkInitRepository ( owner , name string ) ( err error ) {
2020-01-12 15:11:17 +03:00
// Somehow the directory could exist.
2020-09-25 07:09:23 +03:00
repoPath := models . RepoPath ( owner , name )
2020-01-12 15:11:17 +03:00
if com . IsExist ( repoPath ) {
2020-09-25 07:09:23 +03:00
return models . ErrRepoFilesAlreadyExist {
Uname : owner ,
Name : name ,
}
2020-01-12 15:11:17 +03:00
}
// Init git bare new repository.
if err = git . InitRepository ( repoPath , true ) ; err != nil {
return fmt . Errorf ( "git.InitRepository: %v" , err )
2020-01-20 23:01:19 +03:00
} else if err = createDelegateHooks ( repoPath ) ; err != nil {
2020-01-12 15:11:17 +03:00
return fmt . Errorf ( "createDelegateHooks: %v" , err )
}
return nil
}
2020-09-25 07:09:23 +03:00
func adoptRepository ( ctx models . DBContext , repoPath string , u * models . User , repo * models . Repository , opts models . CreateRepoOptions ) ( err error ) {
if ! com . IsExist ( repoPath ) {
return fmt . Errorf ( "adoptRepository: path does not already exist: %s" , repoPath )
}
if err := 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 )
}
branches , _ := gitRepo . GetBranches ( )
found := false
hasDefault := false
hasMaster := 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
}
}
if ! found {
if hasDefault {
repo . DefaultBranch = setting . Repository . DefaultBranch
} else if hasMaster {
repo . DefaultBranch = "master"
} 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-01-12 15:11:17 +03:00
// InitRepository initializes README and .gitignore if needed.
func initRepository ( ctx models . DBContext , repoPath string , u * models . User , repo * models . Repository , opts models . CreateRepoOptions ) ( err error ) {
2020-09-25 07:09:23 +03:00
if err = checkInitRepository ( repo . OwnerName , repo . Name ) ; err != nil {
2020-01-12 15:11:17 +03:00
return err
}
// Initialize repository according to user's choice.
if opts . AutoInit {
tmpDir , err := ioutil . TempDir ( os . TempDir ( ) , "gitea-" + repo . Name )
if err != nil {
return fmt . Errorf ( "Failed to create temp dir for repository %s: %v" , repo . RepoPath ( ) , err )
}
2020-08-11 23:05:34 +03:00
defer func ( ) {
if err := util . RemoveAll ( tmpDir ) ; err != nil {
log . Warn ( "Unable to remove temporary directory: %s: Error: %v" , tmpDir , err )
}
} ( )
2020-01-12 15:11:17 +03:00
if err = prepareRepoCommit ( ctx , repo , tmpDir , repoPath , opts ) ; err != nil {
return fmt . Errorf ( "prepareRepoCommit: %v" , err )
}
// Apply changes and commit.
2020-03-26 22:14:51 +03:00
if err = initRepoCommit ( tmpDir , repo , u , opts . DefaultBranch ) ; err != nil {
2020-01-12 15:11:17 +03:00
return fmt . Errorf ( "initRepoCommit: %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 )
}
if ! opts . AutoInit {
repo . IsEmpty = true
}
2020-09-25 07:09:23 +03:00
repo . DefaultBranch = setting . Repository . DefaultBranch
2020-03-26 22:14:51 +03:00
if len ( opts . DefaultBranch ) > 0 {
repo . DefaultBranch = opts . DefaultBranch
2020-07-05 18:25:46 +03:00
gitRepo , err := git . OpenRepository ( repo . RepoPath ( ) )
if err != nil {
return fmt . Errorf ( "openRepository: %v" , err )
}
if err = gitRepo . SetDefaultBranch ( repo . DefaultBranch ) ; err != nil {
return fmt . Errorf ( "setDefaultBranch: %v" , err )
}
2020-03-26 22:14:51 +03:00
}
2020-01-12 15:11:17 +03:00
if err = models . UpdateRepositoryCtx ( ctx , repo , false ) ; err != nil {
return fmt . Errorf ( "updateRepository: %v" , err )
}
return nil
}