Git:如何确保向非裸仓库推送的安全性

3

我需要一些来自git专家的指导,关于如何使对非裸库的推送操作更加安全。基本上,我有一个计划,希望能得到一些关于这个计划是否合理的建议 :)

通常情况下,在git中向非裸库进行推送时,它的工作副本和索引不会被更新。正如我所发现的那样,如果你忘记手动更新它们,这可能会导致严重的问题!

在我们的团队中,我们有一些“中央”仓库供人们克隆并推回,但是许多人也希望能够克隆他们的克隆,并根据需要以真正的分布式方式进行推/拉操作。为了让这更加安全,我想确保每个通过“克隆”或“init”创建的仓库都自动安装了一个post-receive hook,该hook将在推送操作后更新工作目录和索引,以使其与新的HEAD同步。

我发现,我可以通过创建一个模板目录,其中包含hooks子目录中的我的post-receive hook,然后要求我们团队中的每个人执行以下操作来实现这一点:

git config --global init.templatedir /path/to/template/dir

目前我的post-receive钩子看起来像这样:

export GIT_WORK_TREE=..
git checkout -f HEAD

这个看起来是按照预期工作的,但是我对checkout命令还有些不确定。为了将工作目录和索引与HEAD状态同步,"git checkout -f HEAD" 和 "git reset --hard HEAD" 两者是否等效?

我问这个问题是因为虽然我知道 "git reset --hard HEAD" 可以实现我的需求,但在我的测试中,在post-receive hook中使用它会显著减慢推送操作(似乎会重新检出所有文件,无论工作目录中的文件是干净的还是脏的)。"git checkout -f HEAD" 看起来可以更快地完成同样的事情(让我得到一个干净的工作目录和与HEAD同步的索引),但是考虑到checkout命令在进行未提交的工作目录更改时可能会进行即时合并,我有些紧张。这个命令是否确实会在所有情况下(包括例如文件删除、重命名等)给我一个与HEAD状态完全匹配的工作目录和索引?

非常感谢任何建议!

2个回答

5
我可能误解了问题。您真的想要设置这样一个环境,让任何人都可以在开发人员有未提交更改的情况下推送到他的工作目录吗?这种情况无论如何都不能被视为“安全”。
大多数人的做法是拥有自己的正常工作目录(私有),并将其裸克隆到同一磁盘上的公共存储库中,并分享该存储库。这使用硬链接,因此几乎不会使用额外的空间。您将更改推送到同事的公共裸存储库中,当他准备好接收更改时,他会从裸存储库中拉取到他的工作目录中。或者,您将更改推送到您自己的公共裸存储库中,当您的同事准备好时,他从那里拉取。推送到非裸存储库很难设置,这是有原因的。

抱歉,我觉得我没有很好地表达这个允许推送到非裸仓库的疯狂计划背后的动机。我们至少有两个使用案例,似乎这样做是有意义的:
  1. 我们有一个“稳定”分支和一个“不稳定”分支,但出于各种原因,我们希望它们在不同的存储库中。人们从稳定和不稳定中克隆并推送,但通常我们还需要从不稳定合并到稳定,或者从稳定中拉取错误修复到不稳定。如果稳定和不稳定是非裸的,则可以直接完成此操作,但如果它们是裸的,则更加麻烦。
- David
由于没有人直接在稳定版或不稳定版上工作,因此通过挂钩将工作目录与HEAD保持同步似乎是安全的。
  1. 由于各种原因,我们的集成测试只能在服务器上运行。人们希望能够在服务器上克隆稳定/不稳定版本,然后在本地机器上创建其服务器端克隆。典型的工作流程包括从中央存储库拉取到他们的服务器端克隆,然后再次拉取到他们的本地克隆,进行工作,然后将其推送到服务器端克隆以运行集成测试。
- David
如果测试通过,就会对中央仓库进行另一次推送。如果中央仓库和本地克隆之间存在“中介”服务器端仓库,它将无法从中央仓库拉取更新,如果它是裸仓库的话。由于所有工作都在本地克隆上完成,因此似乎可以将服务器端仓库设为非裸仓库,并使其工作目录与 HEAD 保持同步。抱歉评论有点长--我很感激您对这两种用例的看法! - David
1
@David,对于用例1,通常不应直接在中央存储库中进行合并,而应在开发人员的本地目录中进行合并,然后推送到中央存储库。否则,您将出现中央存储库无法使用的时间段。 - Karl Bielefeldt
用例2是规则的例外,因为您正在“部署”到集成测试服务器。该存储库中不应有未提交的更改,因此检出或重置方法都可以正常工作,而无需进行合并。 - Karl Bielefeldt
哎呀,关于用例1的观点非常好--我没有考虑到!至于用例2,我将仔细考虑是否应该在每个存储库中自动安装post-receive hook以预期这种用例,还是根据需要手动安装--忘记手动添加hook的后果可能很严重,但不适当地重置工作树的后果也是如此。感谢您抽出时间回复,卡尔! - David

4
我建议使用post-update hook,这是Git FAQ中所述的”为什么在“git push”之后我看不到远程repo中的更改?”。它会在执行硬重置之前将已暂存和未暂存的更改(对已跟踪文件)隐藏起来。它比普通的硬重置更安全,但正如FAQ所说,它仍然无法涵盖可能出现的所有情况(例如,具有预先存在冲突的索引无法隐藏;它不会像git checkout那样自动合并未修改文件的更改等)。
然而,如果可能的话,你应该避免在第一时间推送到任何已检出的分支。
只要不推送到已检出的分支(毕竟涉及的配置变量是“receive.denyCurrentBranch”,而不是“receive.denyNonBare”),向非裸库推送是可以的。
上面链接的FAQ条目的最后一段链接到(正如Mark Longair在下面的评论中提到的那样)另一个条目,该条目概述了向非裸库推送的方法。该条目的动机是两个非裸库之间的非对称网络连接,但该技术可应用于任何需要/想要向非裸库推送的情况。
这个FAQ例子演示了如何向远程跟踪分支(在refs/remotes/下)推送。只有refs/heads/下的引用才能被检出而不会分离HEAD(不使用git symbolic-ref),所以推送至refs/heads/之外的任何地方都应该安全,避免“推送到已检出的分支”。
由于您正在中心化环境中工作,您可能能够为此类推送制定目标策略。例如:

When you need to push commits to someone else’s non-bare repository, push them to refs/remotes/from/<your-username>/<branch>. To avoid conflicts with normal remote-tracking branches, no one should ever define a remote named from. Branches pushed like this will show up in git branch -a (or -r) and, accordingly, can be referenced without the refs/remotes/ prefix. However, the from pseudo-remote will not show up in git remote because there is no remote.from.url configuration variable.

Example:

alice$ remote add betty bettys-machine:path/to/some/non-bare/repository
alice$ git push betty master:refs/remotes/from/alice/bug/123/master

betty$ git log --reverse -p origin/master..from/alice/bug/123/master

谢谢你指出那个脚本!我没有考虑过像那样隐藏现有的更改。你知道“checkout -f”是否和“reset --hard”一样有效吗? - David
@David:git checkout -fgit reset --hard是等价的:两者都会将索引更新为与HEAD完全匹配,并将工作树更新为(新)索引相匹配。请注意,由于git checkout有几种操作模式,因此对于人们来说,立即理解git reset --hard可能比推理出此checkout命令将执行的操作更容易(没有路径参数意味着它将“切换分支”,因为没有提交或分支参数,它默认为HEAD(这意味着没有有效的“分支切换”),并将更新索引和工作树以匹配该分支)。 - Chris Johnsen
2
那个常见问题解答还链接到另一种推送到裸仓库的方法,如果我必须要推送到非裸仓库,这就是我总是会做的事情。 - Mark Longair
感谢您给我提供有关将内容推送到/refs/remotes/分支而不是master的FAQ条目的指针 - 这是一个有趣的选项!然而,我在这里主要关注的用例是:1.不直接在repo中进行任何工作(即,工作目录永远不会是脏的)2.需要推送repo 3. repo还需要能够执行pulls / merges。在我的回复中,我给了Karl一些示例(单独的中央稳定/不稳定repo,“中间”repo用于集成测试) - 我很感激您对这些情况的看法! :) - David
@David:我觉得你的第1点和第3点是相互矛盾的。每次合并(或拉取)时,都存在处理冲突的风险。需要解决冲突意味着您将拥有一个脏的工作树和索引。正如Karl在他的答案评论中所指出的那样,这种合并工作通常在普通开发存储库中完成,然后推送到您的中央存储库中,而不是直接在中央存储库中尝试完成。 - Chris Johnsen

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