Git钩子能否自动将文件添加到提交中?

101
我想在Git中使用预提交或后提交钩子来添加一个自动生成的文件到同一次提交中,具体取决于该次提交中修改的文件。我该如何操作?
我已经尝试过将其作为预提交钩子使用,但没有成功:
#!/bin/sh
files=`git diff --cached --name-status`
re="<files of importance>"
if [[ $files =~ $re ]]
then
  echo "Creating files"
  exec bundle exec create_my_files
  exec git add my_files
  exec git commit --amend -C HEAD
fi

这成功地将它们添加到仓库中,但并没有将它们添加到提交中。我还尝试了使用最后两行执行代码在提交后钩子中与预提交检查一起使用,但是也没有用。


4
你需要从这段代码中删除 exec (参见 man sh)。在 exec 之后,任何 shell 命令都无法被执行,因为 exec 覆盖了当前进程,即用于解释命令的 shell。 - Martin Jambon
11个回答

74

由于在预提交中也不能使用git add,因此我采用了Mark的想法,使用.commit文件将进程分成预提交和后提交两个部分。

以下是一些易于理解的代码:

在预提交中:

  • 创建一个名为.commit的文件或类似的东西。(确保将其添加到.gitignore中)
#!/bin/sh 
echo 
touch .commit 
exit

在提交后:

 

如果.commit文件存在,你就知道一个提交已经发生了,但是post-commit尚未运行。因此,你可以在这里进行代码生成。此外,测试.commit文件是否存在:

 
     
  • 添加文件
  •  
  • commit --amend -C HEAD --no-verify(避免循环)
  •  
  • 删除.commit文件
  •  
#!/bin/sh
echo
if [ -e .commit ]
    then
    rm .commit
    git add yourfile
    git commit --amend -C HEAD --no-verify
fi
exit

希望这能让对Bash知识了解不多的人更容易理解Mark的想法。


5
“-a” 不应该改成“-e” 来测试 “.commit” 是否存在吗? - Albert Netymk
4
@AlbertNetymk - 实际上差别很小;-a 主要选项最初是作为 KornShell 提案引入并被纳入 POSIX 标准,如今只为了向后兼容而支持。后来加入了 -e 选项,以避免将 -a 主要选项与 -a 二进制运算符混淆。 - Eliran Malka
1
@AlbertNetymk,好的,这里是 unix.com 上 POSIX test 命令手册页。请在页面中搜索术语 “An early proposal used the KornShell -a primary” 。 - Eliran Malka
3
@jcollum:显然,.commit文件可以防止无限循环。具体来说,--no-verify不能阻止post-commit钩子运行,因此当.commit文件存在时,post-commit会运行两次:一次是在.commit文件存在时运行的,然后运行git commit --amend …,这会再次触发post-commit钩子,但这次.commit文件已经不存在了,这就结束了循环。 - Gabriella Gonzalez
2
这个方案对我有效,但关键的修改是我不得不将 git add 移动到 pre-commit 钩子而不是 post-commit 钩子。我在 macOS 上使用 git 版本 2.25.1。 - mhucka
显示剩余3条评论

40

您可以使用预提交钩子来完成您想要的操作。我们对Heroku部署执行类似的操作(将CoffeeScript编译为JavaScript)。您的脚本不起作用的原因是因为您错误地使用了exec命令。

来自man页面

exec内置函数用于使用新命令替换当前正在运行的shell进程图像。成功完成后,exec永远不会返回。无法在管道内使用exec。

只有您的第一个exec命令正在运行。之后,您的脚本基本上已终止。

尝试使用以下内容(作为预提交钩子):

#!/bin/sh
files=`git diff --cached --name-status`
re="<files of importance>"
if [[ $files =~ $re ]]
then
  echo "Creating files"
  bundle exec create_my_files
  git add my_files
fi

1
最近的git(我使用1.7.9)出现了问题,pre-commit中的git add会将文件添加到未来的提交中,而不是你刚刚参与的那个。 - Rubycut
4
在git 1.7.10版本中我测试通过。文件没有被添加到提交消息中,但它们已经提交了。看起来在pre-commit之前生成提交消息的'git status'存在问题。这对我来说似乎是个bug,但我怀疑有些原因是故意这样做的。你可以在pre-commit的末尾添加一行类似于“git status; echo -n 点击回车继续...; read”的命令。可能有更好的解决方法,但对我而言,这是一个快速修复的办法。 - ben
2
我使用的是Git 1.8.3.4版本,无论在pre-commit钩子中添加了什么内容,在下一次提交之前似乎都没有被暂存。 - jbasko
4
实际上,我可以确认这在我现在使用的 git 2.7.4 版本中有效。当您编辑提交消息时,它看起来并不是这样,但实际上当您提交时,您会看到在 pre-commit hook 中添加的文件出现在提交中。 - Yi Jiang
1
2.11.0 版本也不能工作。已经添加更改但未提交。 - superarts.org
显示剩余5条评论

17
#!/bin/sh
#
#  .git/hooks/pre-commit
#

git add file.xyz

这对我来说完全有效。

它将成为当前提交的一部分。

git 版本 1.7.12.4 (Apple Git-37)


2
这对我也起作用了,只是我需要使用 cd $(git rev-parse --show-toplevel) 更改我的脚本的工作目录。 - IanS
3
我也试过了,如果只用 git commit 命令提交代码,新增的文件不会出现在自动生成的提交信息中,但它们仍会被提交。 - laurent
这似乎与这个相矛盾。实际上这就是我的问题所在。添加操作已完成,但文件尚未提交。 - undefined

13

您可以使用预提交脚本和提交后脚本的组合。

在 pre-commit 中:

  • 创建一个名为.commit 的文件。(确保将其添加到.gitignore)

在 post-commit 中:

如果存在.commit文件,则表示已经进行了一次提交,但尚未执行过 post-commit。 因此,您可以在此处生成代码。 此外,请测试.commit是否存在:

  • 添加文件
  • commit --amend -C HEAD --no-verify (避免循环)
  • 删除.commit文件

这大致是我用来存储元数据文件.metadata的过程。

如果有人知道更好的方法,请告诉我,但现在似乎可以工作。


12
您可以使用update-index命令: git update-index --add my_files

我已经更新了问题并提供了信息。没有错误。文件已创建。Git状态显示它们已添加但未提交。 - Ian Terrell
可能还有另一种选项可以满足您的需求,即 update-index:http://ftp.sunet.se/pub/Linux/kernel.org/software/scm/git/docs/git-update-index.html - rfunduk
我一定会把那作为一个选项考虑。谢谢! - Ian Terrell
6
我不知道@IanTerrell的情况,但我还是卡在这里。我尝试了git addgit update-index --add两种方法。无论哪种方法,文件都会被添加到仓库中(所以它们将在下一次提交中出现),但不会添加到当前提交中。 - kojiro
rfunduk的回答在git 2.6.2上完美地运行,即git update-index --add my_files。 - erichlf
如果索引中有另一个文件不想添加到提交中,这种方法也行不通。 - Iulian Onofrei

3

你可以写一个post-commit脚本来生成你的文件,然后让执行(类似于)git add my_files; git commit --amend


post-commit 在提交完成后运行 -- 此时您无法修改它 afaics。 - jcollum
git commit --amend 修改了之前的提交记录(正如您所说,这刚刚发生过)。 - rfunduk
运行提交会不会导致提交+后提交的无限循环? - undefined

1

我有同样的需求,这种方法对我来说非常有效:

#!/bin/sh
files='git diff --cached --name-only'
re="<files of importance>"
if [[ $files =~ $re ]]
then
   echo "Creating files"
   create_my_files && git add my_files
fi

在这里,“create_my_files”应该是可执行的,例如,如果它是一个Python文件,您可以将其作为“python create_my_files && git add my_files”执行

而且确实不需要预提交再次提交(那会创建一个无限恶心的循环:p)


在 Git 2.6.4 中,pre-commit 钩子中的 git add 对我来说无法按预期工作。 - Flimm

0

是的,你可以使用git hooks自动将生成的文件添加到提交中!但这需要一个棘手的脚本。

在这里,你可以找到解决问题的方法。它会在每次提交时更新文件版本,添加新的修改文件,并根据需要修正提交。它完全可行: https://github.com/evandrocoan/.versioning

然后,你只需将文件“updateVersion.sh”中的“Version File Replacement”算法替换为你的算法。也许你需要改变一些东西,比如删除分支限制,因为在那里,脚本只在你处于“develop”分支时运行。

此外,它只会更改指定的文件,如果已经被暂存。如果文件没有被暂存,那么它将仅执行普通的提交操作。更准确地说,它会在每个步骤上打印出它正在做什么。

我将解释这个技巧。它非常棘手。在prepare-commit-msg-hook中,它检测所需的文件是否正在被暂存和提交。之后,它创建一个标志文件,并停止prepare-commit-msg-hook。 稍后,在post-commit-hook中,它检查标志文件是否存在。如果是,则修正提交中的文件。

注意,这会创建一个无限循环,因为它会再次调用prepare-commit-msg-hook(因为我们正在修改)。但由于标志文件的存在,这不会发生。当prepare-commit-msg-hook运行并找到标志文件时,它“知道”正在发生什么。然后只需删除标志文件,而不再创建它。通过这样做,它将阻止post-commit-hook再次修改提交,从而允许提交最终完成。


0
如果文件是自动生成的,并且它们可以在任何地方生成(这暗示了您希望在Git预提交钩子中构建它们),那么您不应该首先将它们放在源代码控制下。您只应该控制源文件--生成的文件应该作为构建脚本的一部分生成。
将生成的文件放在源代码控制下的唯一原因是当它需要独特/特权资源来生成(例如许可程序)或者它需要大量时间来生成时。 补充 来自http://git-scm.com/docs/githooks: pre-commit 这个钩子由 git commit 调用,可以通过 --no-verify 选项绕过。它不需要任何参数,在获取建议的提交日志消息和进行提交之前被调用。从此脚本退出时返回非零状态会导致 git commit 中止。
默认的 pre-commit 钩子在启用时,会捕获带有尾随空格的行的引入,并在发现这样的行时中止提交。
所有的 git commit 钩子都使用环境变量 GIT_EDITOR=: 调用,如果该命令不会打开编辑器来修改提交消息。 pre-commit 钩子的目的是在进行提交之前对工作区和提交内容进行一次通过或失败的检查。试图更改提交内容是行不通的。
我的建议是在你的构建脚本中增加两个步骤:(1) 一个步骤来构建所有需要生成的过时文件(并将它们添加到工作区),(2) 一个步骤来检查所有生成的文件是否是最新版本,并返回非零状态码。你的Git pre-commit钩子应该运行第二个步骤。你的开发人员应该接受必要的培训并运行第一个步骤。

10
没错,但这并没有回答问题。他可能有将生成的文件放入源代码控制下的非常好的理由,这不是我们需要决定的 :) - rfunduk
1
它们无法在任何地方生成:它们正在从源代码控制部署到只读文件系统。 - Ian Terrell
1
就是这样! :) 你可以尝试将生成步骤放入部署脚本中,但这可能不切实际。 - rfunduk
1
部署是通过 git push 自动化完成的(这是在 Heroku 上的 Rails 应用程序),所以将其放在那里并不是非常实际。 pre-commit 真正适合它,因为我可以测试是否有任何依赖文件已更改,只有在它们更改时才重新构建生成的文件。 - Ian Terrell
1
@Ian,这似乎更适合你的构建脚本,在提交之前处理,而不是尝试使用 Git 自动化生成。如果需要在 pre-commit 步骤中加入任何内容,应该检查文件是否为最新版本,并在不同步时失败提交。 - Craig Trader
1
看起来你对 pre-commit 是正确的... 不幸的是,对于 Ian 的开发人员来说,他的项目中可能没有涉及到“构建脚本” -- 除非你把测试算进去 :) - rfunduk

0
假设你有一个预提交钩子,它执行以下操作:
#!/bin/bash

echo "--- Dataset Pre-Commit Hook ---"

DATASET_REPO_PATH="$(pwd)"

python3 -m my_script_that_generates_or_modifies_a_file
git -C $DATASET_REPO_PATH add file

echo "--- Done ---"
exit

只有在已经有要提交的内容时,它才会起作用。这些内容不一定是同一个文件。如果没有要提交的内容,git add git add file命令会起作用,但不会被提交。
以下是一个有效的示例:
1. 你修改了一个文件,并将其添加(例如,gitignore文件)。
$> git status
On branch master

No commits yet

Changes to be committed:
  (use "git rm --cached <file>..." to unstage)
        new file:   .gitignore

运行提交。首先,将运行pre-commit钩子并创建file。然后进行提交。
--- Dataset Pre-Commit Hook ---
--- Done ---
[master (root-commit) 2ba2f7c] First commit
 3 files changed, 34 insertions(+)
 create mode 100644 .gitignore
 create mode 100644 file

你可以看到消息说file已经提交。而git status现在输出:
On branch master
nothing to commit, working tree clean

不起作用的示例

  1. 你没有任何要添加的内容。当然你可以运行git add,但它不会有任何作用。运行git status会得到以下结果:
On branch master
Your branch is up to date with 'origin/master'.

nothing to commit, working tree clean

运行提交。钩子将创建文件。但提交消息说他什么都没做。
--- Dataset Pre-Commit Hook ---
--- Done ---
On branch master
Your branch is up to date with 'origin/master'.

nothing to commit, working tree clean

验证状态 git status
On branch master
Your branch is up to date with 'origin/master'.

Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
        modified:   file

你可以看到文件确实被修改了,并且被添加了,但是还没有提交。你需要再次运行git commit来使其生效。
我尝试将其作为post-commit钩子添加,但是没有成功。如果有人知道如何解决这个问题,我很愿意知道。

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