mirror of
https://codeberg.org/forgejo/forgejo.git
synced 2026-05-13 06:20:24 +00:00
This PR is replacing repository based hooks hooks with centralised files, this way the files don't need to be copied into every repository, only one line of config need to be added in the repository. Closes: #3523 Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/10397 Reviewed-by: Gusted <gusted@noreply.codeberg.org>
188 lines
5.3 KiB
Go
188 lines
5.3 KiB
Go
// Copyright 2020 The Gitea Authors. All rights reserved.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package git
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
|
|
"forgejo.org/modules/setting"
|
|
"forgejo.org/modules/util"
|
|
)
|
|
|
|
func getHookTemplates() (hookNames, hookTpls, giteaHookTpls []string) {
|
|
hookNames = []string{"pre-receive", "update", "post-receive"}
|
|
hookTpls = []string{
|
|
// for pre-receive
|
|
fmt.Sprintf(`#!/usr/bin/env %s
|
|
# AUTO GENERATED BY GITEA, DO NOT MODIFY
|
|
data=$(cat)
|
|
exitcodes=""
|
|
hookname=$(basename $0)
|
|
|
|
for hook in $(dirname $0)/${hookname}.d/*; do
|
|
test -x "${hook}" && test -f "${hook}" || continue
|
|
echo "${data}" | "${hook}"
|
|
exitcodes="${exitcodes} $?"
|
|
done
|
|
|
|
# Custom hooks
|
|
custom_hooks_dir="./hooks/${hookname}.d"
|
|
if [ -d "${custom_hooks_dir}" ]; then
|
|
for hook in ${custom_hooks_dir}/*; do
|
|
if [ $(basename "${hook}") != "gitea" ]; then
|
|
test -x "${hook}" && test -f "${hook}" || continue
|
|
echo "${data}" | "${hook}"
|
|
exitcodes="${exitcodes} $?"
|
|
fi
|
|
done
|
|
fi
|
|
|
|
for i in ${exitcodes}; do
|
|
[ ${i} -eq 0 ] || exit ${i}
|
|
done
|
|
`, setting.ScriptType),
|
|
|
|
// for update
|
|
fmt.Sprintf(`#!/usr/bin/env %s
|
|
# AUTO GENERATED BY GITEA, DO NOT MODIFY
|
|
exitcodes=""
|
|
hookname=$(basename $0)
|
|
|
|
for hook in $(dirname $0)/${hookname}.d/*; do
|
|
test -x "${hook}" && test -f "${hook}" || continue
|
|
"${hook}" $1 $2 $3
|
|
exitcodes="${exitcodes} $?"
|
|
done
|
|
|
|
# Custom hooks
|
|
custom_hooks_dir="./hooks/${hookname}.d"
|
|
if [ -d "${custom_hooks_dir}" ]; then
|
|
for hook in ${custom_hooks_dir}/*; do
|
|
if [ $(basename "${hook}") != "gitea" ]; then
|
|
test -x "${hook}" && test -f "${hook}" || continue
|
|
"${hook}" $1 $2 $3
|
|
exitcodes="${exitcodes} $?"
|
|
fi
|
|
done
|
|
fi
|
|
|
|
for i in ${exitcodes}; do
|
|
[ ${i} -eq 0 ] || exit ${i}
|
|
done
|
|
`, setting.ScriptType),
|
|
|
|
// for post-receive
|
|
fmt.Sprintf(`#!/usr/bin/env %s
|
|
# AUTO GENERATED BY GITEA, DO NOT MODIFY
|
|
data=$(cat)
|
|
exitcodes=""
|
|
hookname=$(basename $0)
|
|
|
|
for hook in $(dirname $0)/${hookname}.d/*; do
|
|
test -x "${hook}" && test -f "${hook}" || continue
|
|
echo "${data}" | "${hook}"
|
|
exitcodes="${exitcodes} $?"
|
|
done
|
|
|
|
# Custom hooks
|
|
custom_hooks_dir="./hooks/${hookname}.d"
|
|
if [ -d "${custom_hooks_dir}" ]; then
|
|
for hook in ${custom_hooks_dir}/*; do
|
|
if [ $(basename "${hook}") != "gitea" ]; then
|
|
test -x "${hook}" && test -f "${hook}" || continue
|
|
echo "${data}" | "${hook}"
|
|
exitcodes="${exitcodes} $?"
|
|
fi
|
|
done
|
|
fi
|
|
for i in ${exitcodes}; do
|
|
[ ${i} -eq 0 ] || exit ${i}
|
|
done
|
|
`, setting.ScriptType),
|
|
}
|
|
|
|
giteaHookTpls = []string{
|
|
// for pre-receive
|
|
fmt.Sprintf(`#!/usr/bin/env %s
|
|
# AUTO GENERATED BY GITEA, DO NOT MODIFY
|
|
%s hook --config=%s pre-receive
|
|
`, setting.ScriptType, util.ShellEscape(setting.AppPath), util.ShellEscape(setting.CustomConf)),
|
|
|
|
// for update
|
|
fmt.Sprintf(`#!/usr/bin/env %s
|
|
# AUTO GENERATED BY GITEA, DO NOT MODIFY
|
|
%s hook --config=%s update $1 $2 $3
|
|
`, setting.ScriptType, util.ShellEscape(setting.AppPath), util.ShellEscape(setting.CustomConf)),
|
|
|
|
// for post-receive
|
|
fmt.Sprintf(`#!/usr/bin/env %s
|
|
# AUTO GENERATED BY GITEA, DO NOT MODIFY
|
|
%s hook --config=%s post-receive
|
|
`, setting.ScriptType, util.ShellEscape(setting.AppPath), util.ShellEscape(setting.CustomConf)),
|
|
}
|
|
|
|
// although only new git (>=2.29) supports proc-receive, it's still good to create its hook, in case the user upgrades git
|
|
hookNames = append(hookNames, "proc-receive")
|
|
hookTpls = append(hookTpls,
|
|
fmt.Sprintf(`#!/usr/bin/env %s
|
|
# AUTO GENERATED BY GITEA, DO NOT MODIFY
|
|
%s hook --config=%s proc-receive
|
|
`, setting.ScriptType, util.ShellEscape(setting.AppPath), util.ShellEscape(setting.CustomConf)))
|
|
giteaHookTpls = append(giteaHookTpls, "")
|
|
|
|
return hookNames, hookTpls, giteaHookTpls
|
|
}
|
|
|
|
func InitDelegateHooks(path string) (err error) {
|
|
hookNames, hookTpls, giteaHookTpls := getHookTemplates()
|
|
hookDir := filepath.Join(path, "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': %w", filepath.Join(hookDir, hookName+".d"), err)
|
|
}
|
|
|
|
// WARNING: This will override all old server-side hooks
|
|
if err = util.Remove(oldHookPath); err != nil && !os.IsNotExist(err) {
|
|
return fmt.Errorf("unable to pre-remove old hook file '%s' prior to rewriting: %w ", oldHookPath, err)
|
|
}
|
|
if err = os.WriteFile(oldHookPath, []byte(hookTpls[i]), 0o777); err != nil {
|
|
return fmt.Errorf("write old hook file '%s': %w", oldHookPath, err)
|
|
}
|
|
|
|
if err = ensureExecutable(oldHookPath); err != nil {
|
|
return fmt.Errorf("Unable to set %s executable. Error %w", oldHookPath, err)
|
|
}
|
|
|
|
if err = util.Remove(newHookPath); err != nil && !os.IsNotExist(err) {
|
|
return fmt.Errorf("unable to pre-remove new hook file '%s' prior to rewriting: %w", newHookPath, err)
|
|
}
|
|
if err = os.WriteFile(newHookPath, []byte(giteaHookTpls[i]), 0o777); err != nil {
|
|
return fmt.Errorf("write new hook file '%s': %w", newHookPath, err)
|
|
}
|
|
|
|
if err = ensureExecutable(newHookPath); err != nil {
|
|
return fmt.Errorf("Unable to set %s executable. Error %w", oldHookPath, err)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func ensureExecutable(filename string) error {
|
|
fileInfo, err := os.Stat(filename)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if (fileInfo.Mode() & 0o100) > 0 {
|
|
return nil
|
|
}
|
|
mode := fileInfo.Mode() | 0o100
|
|
return os.Chmod(filename, mode)
|
|
}
|