2020-01-20 23:01:19 +03: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 (
"context"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
2020-08-11 23:05:34 +03:00
"code.gitea.io/gitea/modules/util"
2020-04-06 13:44:47 +03:00
"github.com/unknwon/com"
2020-01-20 23:01:19 +03:00
"xorm.io/builder"
)
2020-04-06 13:44:47 +03:00
func getHookTemplates ( ) ( hookNames , hookTpls , giteaHookTpls [ ] string ) {
hookNames = [ ] string { "pre-receive" , "update" , "post-receive" }
hookTpls = [ ] string {
fmt . Sprintf ( "#!/usr/bin/env %s\ndata=$(cat)\nexitcodes=\"\"\nhookname=$(basename $0)\nGIT_DIR=${GIT_DIR:-$(dirname $0)}\n\nfor hook in ${GIT_DIR}/hooks/${hookname}.d/*; do\ntest -x \"${hook}\" && test -f \"${hook}\" || continue\necho \"${data}\" | \"${hook}\"\nexitcodes=\"${exitcodes} $?\"\ndone\n\nfor i in ${exitcodes}; do\n[ ${i} -eq 0 ] || exit ${i}\ndone\n" , setting . ScriptType ) ,
fmt . Sprintf ( "#!/usr/bin/env %s\nexitcodes=\"\"\nhookname=$(basename $0)\nGIT_DIR=${GIT_DIR:-$(dirname $0)}\n\nfor hook in ${GIT_DIR}/hooks/${hookname}.d/*; do\ntest -x \"${hook}\" && test -f \"${hook}\" || continue\n\"${hook}\" $1 $2 $3\nexitcodes=\"${exitcodes} $?\"\ndone\n\nfor i in ${exitcodes}; do\n[ ${i} -eq 0 ] || exit ${i}\ndone\n" , setting . ScriptType ) ,
fmt . Sprintf ( "#!/usr/bin/env %s\ndata=$(cat)\nexitcodes=\"\"\nhookname=$(basename $0)\nGIT_DIR=${GIT_DIR:-$(dirname $0)}\n\nfor hook in ${GIT_DIR}/hooks/${hookname}.d/*; do\ntest -x \"${hook}\" && test -f \"${hook}\" || continue\necho \"${data}\" | \"${hook}\"\nexitcodes=\"${exitcodes} $?\"\ndone\n\nfor i in ${exitcodes}; do\n[ ${i} -eq 0 ] || exit ${i}\ndone\n" , setting . ScriptType ) ,
}
giteaHookTpls = [ ] string {
fmt . Sprintf ( "#!/usr/bin/env %s\n\"%s\" hook --config='%s' pre-receive\n" , setting . ScriptType , setting . AppPath , setting . CustomConf ) ,
fmt . Sprintf ( "#!/usr/bin/env %s\n\"%s\" hook --config='%s' update $1 $2 $3\n" , setting . ScriptType , setting . AppPath , setting . CustomConf ) ,
fmt . Sprintf ( "#!/usr/bin/env %s\n\"%s\" hook --config='%s' post-receive\n" , setting . ScriptType , setting . AppPath , setting . CustomConf ) ,
}
return
}
2020-01-20 23:01:19 +03:00
// CreateDelegateHooks creates all the hooks scripts for the repo
func CreateDelegateHooks ( repoPath string ) error {
return createDelegateHooks ( repoPath )
}
// createDelegateHooks creates all the hooks scripts for the repo
func createDelegateHooks ( repoPath string ) ( err error ) {
2020-04-06 13:44:47 +03:00
hookNames , hookTpls , giteaHookTpls := getHookTemplates ( )
2020-01-20 23:01:19 +03:00
hookDir := filepath . Join ( repoPath , "hooks" )
for i , hookName := range hookNames {
oldHookPath := filepath . Join ( hookDir , hookName )
newHookPath := filepath . Join ( hookDir , hookName + ".d" , "gitea" )
if err := os . MkdirAll ( filepath . Join ( hookDir , hookName + ".d" ) , os . ModePerm ) ; err != nil {
return fmt . Errorf ( "create hooks dir '%s': %v" , filepath . Join ( hookDir , hookName + ".d" ) , err )
}
// WARNING: This will override all old server-side hooks
2020-08-11 23:05:34 +03:00
if err = util . Remove ( oldHookPath ) ; err != nil && ! os . IsNotExist ( err ) {
2020-01-20 23:01:19 +03:00
return fmt . Errorf ( "unable to pre-remove old hook file '%s' prior to rewriting: %v " , oldHookPath , err )
}
if err = ioutil . WriteFile ( oldHookPath , [ ] byte ( hookTpls [ i ] ) , 0777 ) ; err != nil {
return fmt . Errorf ( "write old hook file '%s': %v" , oldHookPath , err )
}
2020-04-06 13:44:47 +03:00
if err = ensureExecutable ( oldHookPath ) ; err != nil {
return fmt . Errorf ( "Unable to set %s executable. Error %v" , oldHookPath , err )
}
2020-08-11 23:05:34 +03:00
if err = util . Remove ( newHookPath ) ; err != nil && ! os . IsNotExist ( err ) {
2020-01-20 23:01:19 +03:00
return fmt . Errorf ( "unable to pre-remove new hook file '%s' prior to rewriting: %v" , newHookPath , err )
}
if err = ioutil . WriteFile ( newHookPath , [ ] byte ( giteaHookTpls [ i ] ) , 0777 ) ; err != nil {
return fmt . Errorf ( "write new hook file '%s': %v" , newHookPath , err )
}
2020-04-06 13:44:47 +03:00
if err = ensureExecutable ( newHookPath ) ; err != nil {
return fmt . Errorf ( "Unable to set %s executable. Error %v" , oldHookPath , err )
}
2020-01-20 23:01:19 +03:00
}
return nil
}
2020-04-06 13:44:47 +03:00
func checkExecutable ( filename string ) bool {
fileInfo , err := os . Stat ( filename )
if err != nil {
return false
}
return ( fileInfo . Mode ( ) & 0100 ) > 0
}
func ensureExecutable ( filename string ) error {
fileInfo , err := os . Stat ( filename )
if err != nil {
return err
}
if ( fileInfo . Mode ( ) & 0100 ) > 0 {
return nil
}
mode := fileInfo . Mode ( ) | 0100
return os . Chmod ( filename , mode )
}
// CheckDelegateHooks checks the hooks scripts for the repo
func CheckDelegateHooks ( repoPath string ) ( [ ] string , error ) {
hookNames , hookTpls , giteaHookTpls := getHookTemplates ( )
hookDir := filepath . Join ( repoPath , "hooks" )
results := make ( [ ] string , 0 , 10 )
for i , hookName := range hookNames {
oldHookPath := filepath . Join ( hookDir , hookName )
newHookPath := filepath . Join ( hookDir , hookName + ".d" , "gitea" )
cont := false
if ! com . IsExist ( oldHookPath ) {
results = append ( results , fmt . Sprintf ( "old hook file %s does not exist" , oldHookPath ) )
cont = true
}
if ! com . IsExist ( oldHookPath + ".d" ) {
results = append ( results , fmt . Sprintf ( "hooks directory %s does not exist" , oldHookPath + ".d" ) )
cont = true
}
if ! com . IsExist ( newHookPath ) {
results = append ( results , fmt . Sprintf ( "new hook file %s does not exist" , newHookPath ) )
cont = true
}
if cont {
continue
}
contents , err := ioutil . ReadFile ( oldHookPath )
if err != nil {
return results , err
}
if string ( contents ) != hookTpls [ i ] {
results = append ( results , fmt . Sprintf ( "old hook file %s is out of date" , oldHookPath ) )
}
if ! checkExecutable ( oldHookPath ) {
results = append ( results , fmt . Sprintf ( "old hook file %s is not executable" , oldHookPath ) )
}
contents , err = ioutil . ReadFile ( newHookPath )
if err != nil {
return results , err
}
if string ( contents ) != giteaHookTpls [ i ] {
results = append ( results , fmt . Sprintf ( "new hook file %s is out of date" , newHookPath ) )
}
if ! checkExecutable ( newHookPath ) {
results = append ( results , fmt . Sprintf ( "new hook file %s is not executable" , newHookPath ) )
}
}
return results , nil
}
2020-01-20 23:01:19 +03:00
// SyncRepositoryHooks rewrites all repositories' pre-receive, update and post-receive hooks
// to make sure the binary and custom conf path are up-to-date.
func SyncRepositoryHooks ( ctx context . Context ) error {
log . Trace ( "Doing: SyncRepositoryHooks" )
if err := models . Iterate (
models . DefaultDBContext ( ) ,
new ( models . Repository ) ,
builder . Gt { "id" : 0 } ,
func ( idx int , bean interface { } ) error {
2020-05-17 02:31:38 +03:00
repo := bean . ( * models . Repository )
2020-01-20 23:01:19 +03:00
select {
case <- ctx . Done ( ) :
2020-05-17 02:31:38 +03:00
return models . ErrCancelledf ( "before sync repository hooks for %s" , repo . FullName ( ) )
2020-01-20 23:01:19 +03:00
default :
}
2020-05-17 02:31:38 +03:00
if err := createDelegateHooks ( repo . RepoPath ( ) ) ; err != nil {
2020-01-20 23:01:19 +03:00
return fmt . Errorf ( "SyncRepositoryHook: %v" , err )
}
2020-05-17 02:31:38 +03:00
if repo . HasWiki ( ) {
if err := createDelegateHooks ( repo . WikiPath ( ) ) ; err != nil {
2020-01-20 23:01:19 +03:00
return fmt . Errorf ( "SyncRepositoryHook: %v" , err )
}
}
return nil
} ,
) ; err != nil {
return err
}
log . Trace ( "Finished: SyncRepositoryHooks" )
return nil
}