在 git push 上硬重置

Posted

技术标签:

【中文标题】在 git push 上硬重置【英文标题】:reset hard on git push 【发布时间】:2011-07-28 17:28:04 【问题描述】:

我有一个接收后挂钩脚本位于我正在推送的远程仓库上,它执行git reset --hard

类似这样的:

$ git push opal
Counting objects: 74, done.
Delta compression using up to 2 threads.
Compressing objects: 100% (45/45), done.
Writing objects: 100% (53/53), 16.68 KiB, done.
Total 53 (delta 20), reused 0 (delta 0)
remote: warning: updating the current branch
remote: HEAD is now at 88f1e35 tweak lavalamp styles

我在这里不明白的是 - 遥控器说头部现在在 XXX 但是当我登录服务器时 - 远程工作副本根本没有更新!

有什么想法吗?

【问题讨论】:

当你遇到问题时,你真的应该发布你的钩子脚本。 克里斯,我的接收后脚本只包含那一行。无需发帖。 【参考方案1】:

问题在于 Git 命令在为钩子脚本创建的环境与您的正常环境中的行为方式不同。

首先,钩子脚本运行时将当前工作目录设置为 Git 目录本身(即非裸存储库的 .git/ 目录)。其次,钩子脚本使用 GIT_DIR 环境变量集运行并指向 Git 存储库(同样,非裸存储库的 .git/ 目录)。

通常,如果您尝试从.git/ 目录运行git reset --hard,它将因以下消息而死:

fatal: This operation must be run in a work tree

但是当设置 GIT_DIR 时,Git 命令假定当前目录是工作树。由于钩子运行时的当前目录是.git/ 目录,因此您的git reset --hard 实际上是将您的工作树文件直接“签出”到.git/ 而不是其父目录(即您现在拥有版本化内容的副本在您的.git/ 目录中)。

希望您的存储库中的版本控制内容都没有与pathnames that Git uses in Git repositories themselves 一致的路径名。如果它们确实一致,那么您的git reset --hard 将覆盖您存储库的一些内部结构,您可能希望从其他存储库重新克隆它。如果你确信版本控制的内容没有与 Git 的内部路径名冲突,那么你可以用这个来清理它:

# make a backup of your repository first!
(cd .git && GIT_DIR=$PWD git ls-files -cz | xargs -0 rm)

这只会删除当前跟踪的文件(它会留下已被删除但曾经在中断的钩子处于活动状态时推送的提示提交中被跟踪的文件)。


一种解决方案是在调用 Git 命令之前将当前工作目录更改为正常工作树并取消设置 GIT_DIR 和 GIT_WORK_TREE。

⋮
test "$PWD%/.git" != "$PWD" && cd .. 
unset GIT_DIR GIT_WORK_TREE
# you can now safely use Git commands
⋮

另一种解决方案是显式重置 GIT_DIR,在那里设置 GIT_WORK_TREE 和 chdir。 Git 常见问题解答“Why won't I see changes in the remote repo after "git push"?” 建议使用post-update script 来执行此操作。链接脚本也更安全,因为如果在执行硬重置之前索引或工作树脏了,它会进行存储。

【讨论】:

+1 啊,太可怕了。更糟糕的是,我之前没有意识到 $PWD 和 $GIT_DIR 在非裸存储库中运行时对于不同的钩子是不一致的。 (例如,在post-commit 中,它们分别设置为工作树和.git。) @Mark:嗯,我没有过多考虑“本地”钩子。看起来可能只是设置这个有问题的环境的“远程”挂钩(接收和更新变体)。其他钩子主要针对无论如何都需要工作树的活动,并且它们似乎是在需要工作树的 Git 命令的合理配置下运行的(这是有道理的,因为这些钩子通常是从需要工作树的命令中调用的) . 对 - 我刚刚写了一篇博文,描述了每个钩子的 GIT 环境变量和当前目录:longair.net/blog/2011/04/09/missing-git-hooks-documentation【参考方案2】:

简而言之,使用钩子单线:

git --git-dir=. --work-tree=$PWD/.. reset --hard

更准确地说,编辑服务器上的文件.git/hooks/post-receive

#!/bin/sh
git --git-dir=. --work-tree=$PWD/.. reset --hard

将其设置为可执行:

chmod +x .git/hooks/post-receive

当从客户端推送到这个 repo 时,它应该这样说:

HEAD is now at abcd123 comment

【讨论】:

甚至比改变环境更好,并且在提出问题之前在 git 中可用。【参考方案3】:

那么脚本可能没有运行。它不会在愚蠢的 http 服务器上运行。 它将通过 ssh 运行。我不确定智能 http 服务器。

如果不是这样,您应该检查钩子上的“执行”权限(chmod +x .git/hooks/post-receive)。当您使用它时,通常检查所有权和权限。

如果看起来没问题,只需在脚本的第一行包含一个日志语句(例如date "%T $0 executed" >> /tmp/debug_hook.log)并检查日志文件以查看是否有任何更新。

此外,推送可能实际上做任何事情(一切都是最新的)。在这种情况下,不调用钩子是有道理的

如果所有这些都没有给出提示,请发布 .git/config,因为它驻留在服务器上(或至少部分)。 git log -1 HEAD 是否在服务器上给出了预期的结果?您的钩子脚本是否包含任何可能覆盖 GIT_DIR、GIT_WORK_TREE 或 GIT_INDEX_FILE 的内容?

【讨论】:

HEAD is now at … 消息只可能来自git checkoutgit reset。两者都不是正常推送的一部分,因此几乎可以肯定挂钩脚本正在运行。 您是否进行了其他检查?尤其是 HEAD 检查?阅读另一篇文章,您必须在“GIT_DIR”检查中获得积极的结果......

以上是关于在 git push 上硬重置的主要内容,如果未能解决你的问题,请参考以下文章

仅在 git push 后重新验证 opcache

如何撤消“git重置”?

在 Git 中重置所有暂存文件的最简单方法是啥?

git重置密码和用户名

接收失败:连接已重置 git

在Git中重置后恢复磁头