将Git钩子放入存储库

312

.git/hooks 放进项目库(例如使用符号链接)是否被认为是一种不好的做法?如果是,那么向不同的Git用户提供相同的hooks的最佳方法是什么?


“项目存储库”是什么意思(或者可能是“项目的存储库”(所有格))?它是指特定的IDE吗?还是其他什么东西? - Peter Mortensen
1
@PeterMortensen 这个问题中所指的项目是指作为VCS根的现有项目,可以克隆并开始使用。 - shabunc
在这个上下文中使用的术语可能源自GitLab - Peter Mortensen
1
@PeterMortensen GitLab在2014年推出,而问题是在2011年8月提出的,因此术语先于GitLab的存在。 - shabunc
2
这将是一个很好的功能,如果git能够添加的话。在克隆一个仓库时,如果它包含一个.git-suggested目录,提示用户是否希望将其内容复制到.git/{hooks,config,info,(任何安全的目录)}中。然而,对建议的钩子进行平滑处理可能会有些棘手。 - undefined
10个回答

167

现在你可以通过以下步骤将一个版本控制下的目录设为Git Hooks目录,例如: MY_REPO_DIR/.githooks 即可。

git config --local core.hooksPath .githooks/

虽然它仍然不能直接执行,但是如果您在自己的README(或其他地方)添加一条注释,每个开发者都需要付出最少的努力。


12
我在 https://www.viget.com/articles/two-ways-to-share-git-hooks-with-your-team/ 上发现的一个技巧是从你的 Makefile/CMake 配置/任何其他选项中设置选项。 - Julius Bullinger
2
作为一条注释,这仅适用于Git 2.9.0以上的版本。在发布文章时,这还不是一个“事情”。 - NationWidePants
这个选项不能包含在.gitattributes文件中,我猜测是吗? - Stefan

166
我基本上同意Scy的观点,还有一些建议,足够值得单独回答。
首先,你应该编写一个脚本来创建适当的符号链接,特别是如果这些钩子是用于执行策略或创建有用的通知。如果用户只需键入bin/create-hook-symlinks而不是自己手动创建符号链接,他们更有可能使用这些钩子。
其次,直接创建符号链接会阻止用户添加自己的个人钩子。例如,我很喜欢样本的pre-commit钩子,它可以确保我没有任何空白错误。一个很好的解决方法是在你的代码库中放置一个钩子包装脚本,并将所有的钩子都创建符号链接到它。
然后,包装脚本可以检查$0(假设它是一个Bash脚本;否则可以使用类似argv[0]的等效方式)来确定它被调用为哪个钩子,然后在你的代码库中调用相应的钩子,以及相应用户的钩子(需要重命名),并将所有参数传递给每个钩子。以下是一个快速示例:
#!/bin/bash
if [ -x $0.local ]; then
    $0.local "$@" || exit $?
fi
if [ -x tracked_hooks/$(basename $0) ]; then
    tracked_hooks/$(basename $0) "$@" || exit $?
fi

安装脚本将把所有现有的钩子移到一边(在它们的名称后面添加.local),并将所有已知的钩子名称链接到上述脚本:
#!/bin/bash
HOOK_NAMES="applypatch-msg pre-applypatch post-applypatch pre-commit prepare-commit-msg commit-msg post-commit pre-rebase post-checkout post-merge pre-receive update post-receive post-update pre-auto-gc"
HOOK_DIR=$(git rev-parse --show-toplevel)/.git/hooks

for hook in $HOOK_NAMES; do
    # If the hook already exists, is executable, and is not a symlink
    if [ ! -h $HOOK_DIR/$hook -a -x $HOOK_DIR/$hook ]; then
        mv $HOOK_DIR/$hook $HOOK_DIR/$hook.local
    fi
    # create the symlink, overwriting the file if it exists
    # probably the only way this would happen is if you're using an old version of git
    # -- back when the sample hooks were not executable, instead of being named ____.sample
    ln -s -f ../../bin/hooks-wrapper $HOOK_DIR/$hook
done

7
我在你的bin/create-hook-symlinks中添加了chmod +x .git/hooks/*来使其工作。 - guneysus
7
@guneysus,你不需要那个,因为这些挂钩应该已经可以执行(它们应该以这种方式检查),链接不需要任何特殊权限,只需要链接到的文件即可。 - Cascabel
15
获取钩子目录的更好方法是 HOOK_DIR=$(git rev-parse --show-toplevel)/.git/hooks - Arnold Daniels
2
我基于这个简单的系统,编写了一个用于管理项目中钩子的脚本:http://ell.io/tt$Paws.js/blob/Master/Scripts/install-git-hooks.sh - ELLIOTTCABLE
8
我只选了必要的内容,并将其放入仓库https://github.com/sjungwirth/githooks。 - Scott Jungwirth
显示剩余4条评论

137

不,将它们放入存储库是可以的。我甚至建议这样做(如果它们对其他人也有用)。用户必须明确启用它们(正如您所说,例如通过符号链接),这在一方面有些麻烦,但另一方面可以保护用户免受未经他们同意运行任意代码的风险。


19
如果这是公司政策问题,那么这段代码就不是“任意的”,它是必要的代码。这将被视为GIT的限制,因为没有另一个(预定义的)目录可以被跟踪,在常规挂钩一起执行。 - Tobias Hagenbeek
19
自动提供 Git 钩子存在安全问题,我很高兴 Git 没有直接这样做。为了执行团队/公司策略,请在服务器端使用钩子或让用户手动决定是否启用它们,正如 @scy 所描述的那样 :) - Mark K Cowan
5
如果开发人员按照您的建议(符号链接)进行操作,那么挂钩可能会被其他人更改,并且运行“未经他们同意的任意代码”,这就需要保护用户免受此类威胁。 - MiniGod
33
MiniGod: 当然。如果你足够谨慎,你可以复制这些钩子而不是使用符号链接,然后审核它们,最后再启用它们。然而,大多数(需要引用)Git仓库包含要在用户机器上运行的源代码,因此你很可能会运行经常变化和未经审核的代码。但是,你说得对。;) - scy

13

在项目中存储并在构建中安装

正如其他人在他们的答案中所述,如果你的钩子是特定于你的项目的,则将它们包含在由Git管理的项目本身中。我会更进一步地说,鉴于使用单个脚本或命令构建项目是一个好的实践,你的钩子应该在构建期间安装。

我写了一篇管理Git钩子的文章,如果你有兴趣了解更多。

Java和Maven

完全声明;我编写了下面描述的Maven插件。

如果你正在使用Maven处理Java项目的构建管理,则以下Maven插件可以处理从项目中的位置安装钩子。

https://github.com/rudikershaw/git-build-hook

将所有的Git钩子放在项目中的一个目录中,然后配置你的pom.xml以包含以下插件声明、目标和配置。
<build>
  <plugins>
    <plugin>
      <groupId>com.rudikershaw.gitbuildhook</groupId>
      <artifactId>git-build-hook-maven-plugin</artifactId>
      <configuration>
        <gitConfig>
          <!-- The location of the directory you are using to store the Git hooks in your project. -->
          <core.hooksPath>hooks-directory/</core.hooksPath>
        </gitConfig>
      </configuration>
      <executions>
        <execution>
          <goals>
            <!-- Sets git config specified under configuration > gitConfig. -->
            <goal>configure</goal>
          </goals>
        </execution>
      </executions>
    </plugin>
      <!-- ... etc ... -->
  </plugins>
</build>

当您运行项目构建时,插件将配置Git以从指定目录运行钩子。这将有效地为所有在项目上工作的人设置该目录中的钩子。
对于NPM,有一个名为Husky的依赖项,允许您安装包括用JavaScript编写的钩子。
// package.json
{
  "husky": {
    "hooks": {
      "pre-commit": "npm test",
      "pre-push": "npm test",
      "...": "..."
    }
  }
}

其他

此外,还有许多不同的钩子管理应用程序/插件,包括Python项目的pre-commit,Ruby项目的Overcommit,以及Ruby Node.js 项目的Lefthook


1
感谢您创建这个插件,它使得集成我的预提交文件变得非常容易。 - Michiel Bugher
Husky真的很棒。我甚至在一些PHP项目中使用了Husky来管理预提交钩子,这些钩子运行由Composer安装的工具,例如phpstan和phpcs,我对这个设置感到非常满意。据我所知,Composer没有类似的功能。 - Garrett W.

7
模板目录中,您可以使用以下机制之一来更新每个新创建的Git仓库的.git/hooks目录:

模板目录包含将复制到$GIT_DIR创建后的文件和目录。

模板目录将按顺序为以下内容之一:

  • 使用--template选项给的参数;

  • $GIT_TEMPLATE_DIR环境变量的内容;

  • init.templateDir配置变量;或者

  • 默认模板目录:/usr/share/git-core/templates


5
对于基于PHP Composer的PHP项目,您可以自动分发给工程师。以下是pre-commit和commit-msg钩子的示例。
创建一个hooks文件夹,然后在您的composer.json文件中:
 },
 "scripts": {
     "post-install-cmd": [
         "cp -r 'hooks/' '.git/hooks/'",
         "php -r \"copy('hooks/pre-commit', '.git/hooks/pre-commit');\"",
         "php -r \"copy('hooks/commit-msg', '.git/hooks/commit-msg');\"",
         "php -r \"chmod('.git/hooks/pre-commit', 0777);\"",
         "php -r \"chmod('.git/hooks/commit-msg', 0777);\"",
     ],

然后,随着每个人定期运行composer install,您甚至可以在项目继续进行时更新它们。


3

pre-commit npm软件包可以优雅地处理这个问题,允许您在package.json文件中指定预提交钩子。


1
这里有一个脚本add-git-hook.sh,您可以将其作为常规文件发送到存储库中,并且可以执行以将Git钩子附加到脚本文件中。调整要使用的钩子(pre-commit、post-commit、pre-push等)以及cat heredoc中钩子的定义。
#!/usr/bin/bash
# Adds the git-hook described below. Appends to the hook file
# if it already exists or creates the file if it does not.
# Note: CWD must be inside target repository

HOOK_DIR=$(git rev-parse --show-toplevel)/.git/hooks
HOOK_FILE="$HOOK_DIR"/post-commit

# Create script file if doesn't exist
if [ ! -e "$HOOK_FILE" ] ; then
        echo '#!/usr/bin/bash' >> "$HOOK_FILE"
        chmod 700 "$HOOK_FILE"
fi

# Append hook code into script
cat >> "$HOOK_FILE" <<EOF

########################################
# ... post-commit hook script here ... #
########################################

EOF

这个脚本可能需要可执行权限,或者用户可以直接运行它。我使用它在提交后自动在其他机器上进行git-pull。
我回答了一个更简单的问题,而不是被问到的问题,也不是OP所寻找的答案。我在下面的评论中对在存储库中发货挂钩脚本与在外部管理它们的用例和参数发表了意见。

我很感激你的努力,也相信这里有宝贵的信息,但是它并没有回答所提出的问题。 - shabunc
在我看来,如果钩子是特定于特定存储库或工作流程的组成部分,则它们应作为文件放置在存储库中。如果将它们放在其他地方,可能会带来更多问题而不是解决问题。您可以将通用钩子存储在自己的存储库或共享驱动器中,这可以使项目存储库非常干净,但代价是实用性大大降低。我同意其他用户的观点,即钩子必须易于添加。符号链接可能会在特定系统或文件结构上创建不必要的依赖关系。 - mathewguest
1
此外,符号链接会破坏用户添加自己钩子的能力。.git/hooks目录没有被跟踪,因此源代码应该从存储库开始,并进入钩子脚本,而不是相反。我认为反对意见可能是git钩子与工作流程或团队更相关,而不是项目,因此不属于存储库。根据您特定的用例,您是否更愿意在git存储库中污染较少相关的钩子,还是宁愿放弃将它们放在其他地方的复杂性? - mathewguest

1

看起来很多帖子都已经过时了,至少如果你在Python生态系统中使用pre-commit的话(我发现在较旧版本的git中更改git hook路径会失败,例如2.3)。有了一个.pre-commit-config.yaml文件,放在仓库根目录下的hooks目录中,最简单的解决方案是运行:

pre-commit install -f --config hooks/.pre-commit-config.yaml

5
.pre-commit-config.yaml文件从哪里来?这个问题需要更多的信息。 - kev

-2

您可以使用托管解决方案,例如pre-commit进行提交前钩子管理。

或者使用中央化的解决方案,例如Datree.io用于服务器端git-hooks。

它具有内置策略,例如:

  1. 检测并阻止秘密合并
  2. 强制执行正确的Git用户配置
  3. 强制执行Jira工单集成- 在拉取请求名称/提交消息中提及工单号码。

它不会替换所有钩子,但它可能会帮助您的开发人员处理最明显的问题,而无需在每个开发人员的计算机/仓库上安装钩子的配置问题。

免责声明:我是Datree的创始人之一。


6
我认为你正在制作一款有趣的产品,但我也认为这并没有回答问题,基本上只是自我宣传而已。 - shabunc
这个答案涉及到某个框架或包,而问题是关于Git Hooks的 - 两件不同的事情。 - Optimism

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接