code.gitea.io/gitea@v1.19.3/modules/repository/hooks.go (about)

     1  // Copyright 2020 The Gitea Authors. All rights reserved.
     2  // SPDX-License-Identifier: MIT
     3  
     4  package repository
     5  
     6  import (
     7  	"fmt"
     8  	"os"
     9  	"path/filepath"
    10  	"runtime"
    11  
    12  	"code.gitea.io/gitea/modules/git"
    13  	"code.gitea.io/gitea/modules/setting"
    14  	"code.gitea.io/gitea/modules/util"
    15  )
    16  
    17  func getHookTemplates() (hookNames, hookTpls, giteaHookTpls []string) {
    18  	hookNames = []string{"pre-receive", "update", "post-receive"}
    19  	hookTpls = []string{
    20  		// for pre-receive
    21  		fmt.Sprintf(`#!/usr/bin/env %s
    22  # AUTO GENERATED BY GITEA, DO NOT MODIFY
    23  data=$(cat)
    24  exitcodes=""
    25  hookname=$(basename $0)
    26  GIT_DIR=${GIT_DIR:-$(dirname $0)/..}
    27  
    28  for hook in ${GIT_DIR}/hooks/${hookname}.d/*; do
    29    test -x "${hook}" && test -f "${hook}" || continue
    30    echo "${data}" | "${hook}"
    31    exitcodes="${exitcodes} $?"
    32  done
    33  
    34  for i in ${exitcodes}; do
    35    [ ${i} -eq 0 ] || exit ${i}
    36  done
    37  `, setting.ScriptType),
    38  
    39  		// for update
    40  		fmt.Sprintf(`#!/usr/bin/env %s
    41  # AUTO GENERATED BY GITEA, DO NOT MODIFY
    42  exitcodes=""
    43  hookname=$(basename $0)
    44  GIT_DIR=${GIT_DIR:-$(dirname $0/..)}
    45  
    46  for hook in ${GIT_DIR}/hooks/${hookname}.d/*; do
    47    test -x "${hook}" && test -f "${hook}" || continue
    48    "${hook}" $1 $2 $3
    49    exitcodes="${exitcodes} $?"
    50  done
    51  
    52  for i in ${exitcodes}; do
    53    [ ${i} -eq 0 ] || exit ${i}
    54  done
    55  `, setting.ScriptType),
    56  
    57  		// for post-receive
    58  		fmt.Sprintf(`#!/usr/bin/env %s
    59  # AUTO GENERATED BY GITEA, DO NOT MODIFY
    60  data=$(cat)
    61  exitcodes=""
    62  hookname=$(basename $0)
    63  GIT_DIR=${GIT_DIR:-$(dirname $0)/..}
    64  
    65  for hook in ${GIT_DIR}/hooks/${hookname}.d/*; do
    66    test -x "${hook}" && test -f "${hook}" || continue
    67    echo "${data}" | "${hook}"
    68    exitcodes="${exitcodes} $?"
    69  done
    70  
    71  for i in ${exitcodes}; do
    72    [ ${i} -eq 0 ] || exit ${i}
    73  done
    74  `, setting.ScriptType),
    75  	}
    76  
    77  	giteaHookTpls = []string{
    78  		// for pre-receive
    79  		fmt.Sprintf(`#!/usr/bin/env %s
    80  # AUTO GENERATED BY GITEA, DO NOT MODIFY
    81  %s hook --config=%s pre-receive
    82  `, setting.ScriptType, util.ShellEscape(setting.AppPath), util.ShellEscape(setting.CustomConf)),
    83  
    84  		// for update
    85  		fmt.Sprintf(`#!/usr/bin/env %s
    86  # AUTO GENERATED BY GITEA, DO NOT MODIFY
    87  %s hook --config=%s update $1 $2 $3
    88  `, setting.ScriptType, util.ShellEscape(setting.AppPath), util.ShellEscape(setting.CustomConf)),
    89  
    90  		// for post-receive
    91  		fmt.Sprintf(`#!/usr/bin/env %s
    92  # AUTO GENERATED BY GITEA, DO NOT MODIFY
    93  %s hook --config=%s post-receive
    94  `, setting.ScriptType, util.ShellEscape(setting.AppPath), util.ShellEscape(setting.CustomConf)),
    95  	}
    96  
    97  	if git.SupportProcReceive {
    98  		hookNames = append(hookNames, "proc-receive")
    99  		hookTpls = append(hookTpls,
   100  			fmt.Sprintf(`#!/usr/bin/env %s
   101  # AUTO GENERATED BY GITEA, DO NOT MODIFY
   102  %s hook --config=%s proc-receive
   103  `, setting.ScriptType, util.ShellEscape(setting.AppPath), util.ShellEscape(setting.CustomConf)))
   104  		giteaHookTpls = append(giteaHookTpls, "")
   105  	}
   106  
   107  	return hookNames, hookTpls, giteaHookTpls
   108  }
   109  
   110  // CreateDelegateHooks creates all the hooks scripts for the repo
   111  func CreateDelegateHooks(repoPath string) error {
   112  	return createDelegateHooks(repoPath)
   113  }
   114  
   115  // createDelegateHooks creates all the hooks scripts for the repo
   116  func createDelegateHooks(repoPath string) (err error) {
   117  	hookNames, hookTpls, giteaHookTpls := getHookTemplates()
   118  	hookDir := filepath.Join(repoPath, "hooks")
   119  
   120  	for i, hookName := range hookNames {
   121  		oldHookPath := filepath.Join(hookDir, hookName)
   122  		newHookPath := filepath.Join(hookDir, hookName+".d", "gitea")
   123  
   124  		if err := os.MkdirAll(filepath.Join(hookDir, hookName+".d"), os.ModePerm); err != nil {
   125  			return fmt.Errorf("create hooks dir '%s': %w", filepath.Join(hookDir, hookName+".d"), err)
   126  		}
   127  
   128  		// WARNING: This will override all old server-side hooks
   129  		if err = util.Remove(oldHookPath); err != nil && !os.IsNotExist(err) {
   130  			return fmt.Errorf("unable to pre-remove old hook file '%s' prior to rewriting: %w ", oldHookPath, err)
   131  		}
   132  		if err = os.WriteFile(oldHookPath, []byte(hookTpls[i]), 0o777); err != nil {
   133  			return fmt.Errorf("write old hook file '%s': %w", oldHookPath, err)
   134  		}
   135  
   136  		if err = ensureExecutable(oldHookPath); err != nil {
   137  			return fmt.Errorf("Unable to set %s executable. Error %w", oldHookPath, err)
   138  		}
   139  
   140  		if err = util.Remove(newHookPath); err != nil && !os.IsNotExist(err) {
   141  			return fmt.Errorf("unable to pre-remove new hook file '%s' prior to rewriting: %w", newHookPath, err)
   142  		}
   143  		if err = os.WriteFile(newHookPath, []byte(giteaHookTpls[i]), 0o777); err != nil {
   144  			return fmt.Errorf("write new hook file '%s': %w", newHookPath, err)
   145  		}
   146  
   147  		if err = ensureExecutable(newHookPath); err != nil {
   148  			return fmt.Errorf("Unable to set %s executable. Error %w", oldHookPath, err)
   149  		}
   150  	}
   151  
   152  	return nil
   153  }
   154  
   155  func checkExecutable(filename string) bool {
   156  	// windows has no concept of a executable bit
   157  	if runtime.GOOS == "windows" {
   158  		return true
   159  	}
   160  	fileInfo, err := os.Stat(filename)
   161  	if err != nil {
   162  		return false
   163  	}
   164  	return (fileInfo.Mode() & 0o100) > 0
   165  }
   166  
   167  func ensureExecutable(filename string) error {
   168  	fileInfo, err := os.Stat(filename)
   169  	if err != nil {
   170  		return err
   171  	}
   172  	if (fileInfo.Mode() & 0o100) > 0 {
   173  		return nil
   174  	}
   175  	mode := fileInfo.Mode() | 0o100
   176  	return os.Chmod(filename, mode)
   177  }
   178  
   179  // CheckDelegateHooks checks the hooks scripts for the repo
   180  func CheckDelegateHooks(repoPath string) ([]string, error) {
   181  	hookNames, hookTpls, giteaHookTpls := getHookTemplates()
   182  
   183  	hookDir := filepath.Join(repoPath, "hooks")
   184  	results := make([]string, 0, 10)
   185  
   186  	for i, hookName := range hookNames {
   187  		oldHookPath := filepath.Join(hookDir, hookName)
   188  		newHookPath := filepath.Join(hookDir, hookName+".d", "gitea")
   189  
   190  		cont := false
   191  		isExist, err := util.IsExist(oldHookPath)
   192  		if err != nil {
   193  			results = append(results, fmt.Sprintf("unable to check if %s exists. Error: %v", oldHookPath, err))
   194  		}
   195  		if err == nil && !isExist {
   196  			results = append(results, fmt.Sprintf("old hook file %s does not exist", oldHookPath))
   197  			cont = true
   198  		}
   199  		isExist, err = util.IsExist(oldHookPath + ".d")
   200  		if err != nil {
   201  			results = append(results, fmt.Sprintf("unable to check if %s exists. Error: %v", oldHookPath+".d", err))
   202  		}
   203  		if err == nil && !isExist {
   204  			results = append(results, fmt.Sprintf("hooks directory %s does not exist", oldHookPath+".d"))
   205  			cont = true
   206  		}
   207  		isExist, err = util.IsExist(newHookPath)
   208  		if err != nil {
   209  			results = append(results, fmt.Sprintf("unable to check if %s exists. Error: %v", newHookPath, err))
   210  		}
   211  		if err == nil && !isExist {
   212  			results = append(results, fmt.Sprintf("new hook file %s does not exist", newHookPath))
   213  			cont = true
   214  		}
   215  		if cont {
   216  			continue
   217  		}
   218  		contents, err := os.ReadFile(oldHookPath)
   219  		if err != nil {
   220  			return results, err
   221  		}
   222  		if string(contents) != hookTpls[i] {
   223  			results = append(results, fmt.Sprintf("old hook file %s is out of date", oldHookPath))
   224  		}
   225  		if !checkExecutable(oldHookPath) {
   226  			results = append(results, fmt.Sprintf("old hook file %s is not executable", oldHookPath))
   227  		}
   228  		contents, err = os.ReadFile(newHookPath)
   229  		if err != nil {
   230  			return results, err
   231  		}
   232  		if string(contents) != giteaHookTpls[i] {
   233  			results = append(results, fmt.Sprintf("new hook file %s is out of date", newHookPath))
   234  		}
   235  		if !checkExecutable(newHookPath) {
   236  			results = append(results, fmt.Sprintf("new hook file %s is not executable", newHookPath))
   237  		}
   238  	}
   239  	return results, nil
   240  }