pre-receive hook无法读取提交的文件以推入远程master

时间:2014-06-23 08:45:50

标签: git bash github sed

我在我的服务器中的预接收文件中尝试了以下内容,但我在本地git存储库上的预提交工作没有任何问题

#!/bin/bash 
git rev-parse -q --verify HEAD && sed -n -e '/^<<\+ /q1' -n -e '/^>>\+ /q1' $(git diff --name-only HEAD^ HEAD $(git write-tree)) || { echo "Git conflict (<<<<) or (>>>>) exists in one of the committed files."; exit 1; }

但是会收到remote: sed: can't read /App/abc/TestFile.java:No such file or directory

这样的错误

请帮我解决一下?

3 个答案:

答案 0 :(得分:2)

这有点乱。

让我们先从你的预提交钩子中取出这部分:

git diff --name-only HEAD^ HEAD $(git write-tree)

内部git write-tree将索引写入树并返回其哈希值。为了举例,我们说它是01234567

然后使用三个commit-or-tree-ish参数运行git diff(所有三个参数都可以解析为树标识符,这是git在这里关心的):

HEAD^ HEAD 01234567

这在git diff中调用了一个未记录的行为:它产生了一个“组合差异”。组合diff的输入被认为是几个父项(除第一个参数之外的所有参数)和一个子(第一个参数),因此这将处理您刚刚编写的树以及存储库中的HEAD提交,如两个父提交,HEAD^作为子提交。

git diff documentation注意到组合差异“只列出了从所有父母修改过的文件”。在这种情况下,同样,两个“父”是建议的新提交树(来自git write-tree)和HEAD提交(当前位于当前分支的尖端)。其中的两个HEAD^(当前分支的尖端的第一个父级)不同,git将显示差异。这不是你想要的! (由于您还指定了--name-only,git将只显示文件名,而不是实际差异。)

然后,您可以使用这些名称并在这些文件中查找git的“冲突标记”(冲突区域周围的<<<>>>标记)。这部分没有错(但仍然有点破坏),但此时事情已经错了,因为你可能正在查看错误的文件。

例如,考虑提交HEAD^缺少文件f2,提交HEAD添加文件f2,当前索引修改文件f3的情况但它有一个git冲突:

$ mkdir /tmp/repo; cd /tmp/repo; git init
Initialized empty Git repository in /tmp/repo/.git/
$ echo ordinary file > f1; git add f1
$ echo another ordinary file > f3
$ git add f1 f3; git commit -m initial
[master (root-commit) f181096] initial
 2 files changed, 2 insertions(+)
 create mode 100644 f1
 create mode 100644 f3
$ echo new file f2 > f2; git add f2
$ git commit -m 'add f2'
[master c06f8d1] add f2
 1 file changed, 1 insertion(+)
 create mode 100644 f2
$ (echo '<<< conflict'; echo '==='; echo '>>> end conflict') > f3
$ git add f3 # but we never resolved our (fake) conflict
$ git diff --name-only HEAD^ HEAD $(git write-tree)
f2

存在问题:合并后的差异没有看f3因为它没有在“父母”和“孩子”中被修改(当然这些“父母”/“孩子”关系无论如何都是荒谬的) 。没有--name-only,我们会看到组合差异输出:

$ git diff HEAD^ HEAD $(git write-tree)
diff --cc f2
index 9d57e62,9d57e62..0000000
deleted file mode 100644,100644
--- a/f2
+++ /dev/null
@@@ -1,1 -1,1 +1,0 @@@
--new file f2

如果要检查建议的新提交树是否有一些带有冲突标记的文件,则需要检查建议的“blob”,而不是当前工作树。 (这是因为您可以git add一个文件,然后进一步修改它;或者git add -p以交互方式选择要添加的部分和部分以推迟添加。因此,索引的内容可能与工作目录不匹配。)有很多方法可以做到这一点;请参阅this question及其对一种方法及其下方的答案(使用带有修订版和路径的git show)。您现在拥有的代码适用于一些案例,但绝对不是全部。


有了这个,我看到Ikke has already answered the other issue,这是一个裸存储库 - git push操作的常用目标,以及运行预接收挂钩的地方 - 没有工作树,因此您无法查看该工作树中的文件。预接收挂钩通常更难写,因为您必须处理许多情况:

  • 多次提交
  • 不是分支(标签)的引用
  • 非提交的对象(带注释的标签)
  • 分支创建和删除以及更新

当建议更新分支(形式为refs/heads/name的引用)时,预接收挂钩将获得其当前的SHA-1和建议的新SHA-1。然后,如果允许更新,则可以使用git rev-list查找将在分支上(或不再在分支上)的对象序列。对于每个这样的对象,如果它是一个提交,你将检查附加到该提交的树,以查看该树中的所有blob(文件)是否通过了检查。

请注意,pre-receiveupdate挂钩与其他git“pre”挂钩非常不同:在这两种情况下,建议的新提交和/或带注释的标签实际上已经存在于存储库中(尽管如果你的钩子拒绝它们可能会再次被剥离),你通常应该通过object-ID(SHA-1)来引用这些提议的git对象。 (可以使用提交树;实际上,在许多情况下,必须执行此操作。)这里的要点是,预先提交挂钩的正确性几乎可以保证是错误的。预接收挂钩,反之亦然。

这个过程的大致概述可能是:

NULL_SHA1=0000000000000000000000000000000000000000

check_revs()
{
    local range branch rev rtype path

    range=$1
    branch=$2
    git rev-list $range |
    while read rev; do
        rtype=$(git cat-file -t $rev)
        case $rtype in
        commit) ;;
        *) continue;; # skip annotated tags
        esac
        git diff --name-only ${rev}^ $rev |
        while read path; do
            if git show ${rev}:$path | grep forbidden-item; then
                echo "error: branch ${branch}: ${rev}:$path contains forbidden-item" 1>&2
                exit 1
            fi
        done
    done
}

check_branch()
{
    local old new branch

    old=$1
    new=$2
    branch=$3

    if [ $old = $NULL_SHA1 -o $new = $NULL_SHA1 ]; then
        # branch will be created or deleted, not updated
        # do whatever is appropriate here
    else
        # branch will be updated, if we allow it
        check_revs $old..$new $branch
    fi
}

while read oldsha newsha fullref; do
case "$fullref" in
refs/heads/*) check_branch $oldsha $newsha ${fullref#refs/heads/};;
# add cases for refs/tags/* if desired, etc
*) ;;
esac

exit 0 # if we got this far it must be OK to do it

(请注意,这是完全未经测试的。我希望它在“已删除文件”的情况下有一个错误,在新版本中没有git show。此外,检查不一定是个好主意。对于<<<等等。如果有一个文本文件说明git冲突的样子会怎样?你可以选择基于文件名的检查类型,但即使这样,一些文件可能合法地包含看起来是什么,但实际上并不是git冲突标记。如果你选择这样做,请确保在允许的情况下允许它绕过它。)

答案 1 :(得分:1)

问题是sed尝试从文件系统读取文件,但裸存储库没有工作树。

git write-tree创建一个新的树对象,它不会检出文件,因此它不适合你想要的。

此外,您只检查上次提交的差异,而有人可以推送许多提交。 pre-receive挂钩提供有关在stdin上推送的提交的信息(请参阅格式的手册页)。

你必须迭代每一行并使用它来创建一个你可以输入sed的差异

答案 2 :(得分:0)

git diff "$old_commit"..."$latest_commit"其中old_commit和latest_commit是预先接收的默认输入。它显示了最古老的&amp;之间的差异。最新的提交。