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