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"
2021-07-28 12:42:56 +03:00
"code.gitea.io/gitea/modules/git"
2020-01-20 23:01:19 +03:00
"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-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 {
2021-05-07 16:19:09 +03:00
fmt . Sprintf ( ` # ! / usr / bin / env % s
data = $ ( cat )
exitcodes = ""
hookname = $ ( basename $ 0 )
GIT_DIR = $ { GIT_DIR : - $ ( dirname $ 0 ) / . . }
for hook in $ { GIT_DIR } / hooks / $ { hookname } . d / * ; do
test - x "${hook}" && test - f "${hook}" || continue
echo "${data}" | "${hook}"
exitcodes = "${exitcodes} $?"
done
for i in $ { exitcodes } ; do
[ $ { i } - eq 0 ] || exit $ { i }
done
` , setting . ScriptType ) ,
fmt . Sprintf ( ` # ! / usr / bin / env % s
exitcodes = ""
hookname = $ ( basename $ 0 )
GIT_DIR = $ { GIT_DIR : - $ ( dirname $ 0 / . . ) }
for hook in $ { GIT_DIR } / hooks / $ { hookname } . d / * ; do
test - x "${hook}" && test - f "${hook}" || continue
"${hook}" $ 1 $ 2 $ 3
exitcodes = "${exitcodes} $?"
done
for i in $ { exitcodes } ; do
[ $ { i } - eq 0 ] || exit $ { i }
done
` , setting . ScriptType ) ,
fmt . Sprintf ( ` # ! / usr / bin / env % s
data = $ ( cat )
exitcodes = ""
hookname = $ ( basename $ 0 )
GIT_DIR = $ { GIT_DIR : - $ ( dirname $ 0 ) / . . }
for hook in $ { GIT_DIR } / hooks / $ { hookname } . d / * ; do
test - x "${hook}" && test - f "${hook}" || continue
echo "${data}" | "${hook}"
exitcodes = "${exitcodes} $?"
done
for i in $ { exitcodes } ; do
[ $ { i } - eq 0 ] || exit $ { i }
done
` , setting . ScriptType ) ,
2020-04-06 13:44:47 +03:00
}
giteaHookTpls = [ ] string {
2020-09-29 04:16:52 +03:00
fmt . Sprintf ( "#!/usr/bin/env %s\n%s hook --config=%s pre-receive\n" , setting . ScriptType , util . ShellEscape ( setting . AppPath ) , util . ShellEscape ( setting . CustomConf ) ) ,
fmt . Sprintf ( "#!/usr/bin/env %s\n%s hook --config=%s update $1 $2 $3\n" , setting . ScriptType , util . ShellEscape ( setting . AppPath ) , util . ShellEscape ( setting . CustomConf ) ) ,
fmt . Sprintf ( "#!/usr/bin/env %s\n%s hook --config=%s post-receive\n" , setting . ScriptType , util . ShellEscape ( setting . AppPath ) , util . ShellEscape ( setting . CustomConf ) ) ,
2020-04-06 13:44:47 +03:00
}
2021-07-28 12:42:56 +03:00
if git . SupportProcReceive {
hookNames = append ( hookNames , "proc-receive" )
hookTpls = append ( hookTpls ,
fmt . Sprintf ( "#!/usr/bin/env %s\n%s hook --config=%s proc-receive\n" , setting . ScriptType , util . ShellEscape ( setting . AppPath ) , util . ShellEscape ( setting . CustomConf ) ) )
giteaHookTpls = append ( giteaHookTpls , "" )
}
2020-04-06 13:44:47 +03:00
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
2020-11-28 05:42:08 +03:00
isExist , err := util . IsExist ( oldHookPath )
if err != nil {
results = append ( results , fmt . Sprintf ( "unable to check if %s exists. Error: %v" , oldHookPath , err ) )
}
if err == nil && ! isExist {
2020-04-06 13:44:47 +03:00
results = append ( results , fmt . Sprintf ( "old hook file %s does not exist" , oldHookPath ) )
cont = true
}
2020-11-28 05:42:08 +03:00
isExist , err = util . IsExist ( oldHookPath + ".d" )
if err != nil {
results = append ( results , fmt . Sprintf ( "unable to check if %s exists. Error: %v" , oldHookPath + ".d" , err ) )
}
if err == nil && ! isExist {
2020-04-06 13:44:47 +03:00
results = append ( results , fmt . Sprintf ( "hooks directory %s does not exist" , oldHookPath + ".d" ) )
cont = true
}
2020-11-28 05:42:08 +03:00
isExist , err = util . IsExist ( newHookPath )
if err != nil {
results = append ( results , fmt . Sprintf ( "unable to check if %s exists. Error: %v" , newHookPath , err ) )
}
if err == nil && ! isExist {
2020-04-06 13:44:47 +03:00
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
}