来自https://stackoverflow.com/a/34535193/156458
当推送到远程存储库的签出分支时,通常会收到警告,并且 Git不允许您这样做。
但我明白git可以推送到远程存储库的当前分支,来自Version Control with Git,Loeliger,2ed,特别是粗体文本:
推送操作可以更新存储库状态,包括 HEAD 提交即可。也就是说,即使远程端的开发人员已经完成了 没有,分支参考和 HEAD可能会改变,变得不同步 带有签出的文件和索引。
正积极在存储库中工作的开发人员 异步推送发生不会看到推动。但随后 该开发人员的提交将发生在意外的HEAD上,创建一个 奇怪的历史。强制推送将失去对方的推送提交 开发商。该存储库中的开发人员也可能找到自己 无法将她的历史记录与上游存储库或 下游克隆因为它们不再像前面那样简单快速 他们应该是。她不会知道原因:存储库已经默默无闻 从她的下面变了出来。猫和狗将一起生活。 这会很糟糕。
因此,我们鼓励您只进入裸存储库。 这不是一个严格的规则,但它是一个很好的指南 平均开发者,被认为是最佳实践。有几个 您可能想要进入的实例和用例 开发存储库,但你应该完全理解它 影响。当您想要进入开发存储库时, 你可能想要遵循两种基本方法之一。
在第一个场景中,您确实希望拥有一个工作目录 在接收存储库中检出分支。你可能知道, 例如,没有其他开发人员会做活动 在那里发展,因此没有人可能是盲人 默默的变化被推入他的存储库。
在这种情况下,您可能希望在接收中启用挂钩 存储库到执行一些分支的结账,也许只是一个分支 推送到工作目录。验证 在具有自动化之前,接收存储库处于理智状态 checkout,钩子应该确保nonbare存储库的工作 目录不包含编辑或修改过的文件及其索引 当推送发生时,没有处于暂存但未提交状态的文件。 如果不满足这些条件,您将面临失去这些条件的风险 结帐或更改,因为结帐会覆盖它们。
另一种情况是,推入非公共存储库可以合理地工作 好。通过协议,推动更改的每个开发人员必须推送到未经检查的 out branch ,简称为接收分支。开发人员从不推动 预计将被检出的分支。这取决于某些开发人员 管理签出什么分支以及何时签出。也许那个人负责 处理接收分支并将它们合并到主分支之前 检查出来。
这是否意味着git可以推送到远程存储库的当前分支? (我的猜测是肯定的,但我不确定)
最后一段之前的段落(推荐使用通过push更新检查分支的钩子的段落)是否假设要推送的分支不是远程存储库中的当前分支? (我的想法是检查分支推送意味着分支推送到不是当前分支,但最后一段指出推送到未检出分支的“不同场景”,暗示我上一段是关于推送到已检出的分支,即当前分支
答案 0 :(得分:5)
从Git 2.3+开始,您可以配置 接收端到"有一个工作目录,并在接收存储库中检出分支。&#34 ;
具体来说:"push-to-checkout" (git 2.4, May 2015),对"push-to-deploy" (Git 2.3, February 2015)进行了改进。
参见" Deploy a project using Git push"举一个具体的例子。
您可以将更改直接推送到服务器上的存储库。如果未在服务器上进行本地修改,则将自动检出对服务器当前分支的任何更改。即时部署!
要使用此功能,您必须首先通过运行
在服务器上的Git存储库中启用它
$ git config receive.denyCurrentBranch updateInstead
现在有一个
push-to-checkout
挂钩,可以安装在服务器上,以准确定制用户推送到签出分支时发生的情况。
例如,默认情况下,如果服务器上的工作树发生任何更改,则此推送将失败。 push-to-checkout钩子可以尝试将任何服务器端编辑与新的分支内容合并,或者它可以无条件地用推送的分支内容的原始副本覆盖任何本地更改。
简而言之,您可以直接推送到已签出的分支机构 有关警告,请参阅commit 4d7a5ce:
仅更改工作树但不更改索引仍然是一个 改变为受保护;
工作树中未被跟踪的文件将被覆盖 通过推送部署需要加以保护;
使文件与现有文件完全相同的更改 推送仍然是一个需要保护的变化(即功能 清洁度要求比结账要严格。
答案 1 :(得分:3)
我认为查看此内容的方式来自"接收方",即考虑当您是普通人使用git时会发生什么,并且有人会进入您的存储库(与你通常做的一样,这是从他们那里取的)。 Git为此提供了很多机制,并且默认策略运行正常,即:"只是说不#34;。 :-)较新版本的git(2.3+)增加了一些提供安全性的策略,同时允许一些这些推送;你是否会打电话给他们"对"或者"完美"更多的是意见问题。 (如果某人开始编辑非裸露的部署"主机然后比如睡着了会发生什么,那么没有其他人可以推动它,因为它现在看起来很脏"对于git。)
首先请记住,可以将存储库设置为"裸",它告诉git不要查找工作树。 (你可以将非裸仓库转换为裸仓,反之亦然,但根据我的经验,大多数人都会在此过程中蠢蠢欲动。使用git clone --bare
最初设置裸克隆,并避免创建工作树,这意味着这里不存在混淆或错误的可能性。 1 )鉴于存储库没有工作树,没有人进入工作树并做任何工作, "推动正在进行中的工作"案件是不可能的。
考虑到这一点,让我们看看当我们是推送的接收者时会发生什么,并且我们有一个非裸的存储库,它有一个实际的工作树。我们还要记住两件事:
.git/index
),它的作用通常是“#34;放置下一个提交的位置”#34;和"缓存以加快git status
和类似的操作"。我们还有一个"当前"分支,存储在HEAD
文件中, 2 ,我们可以直接查看,或使用git symbolic-ref HEAD
读取(后者是正式批准的方法)。如果当前分支为br
,则HEAD
文件包含单行读取ref: refs/heads/br
,并git symbolic-ref HEAD
打印refs/heads/br
。
(如果我们处于"分离的HEAD"模式,HEAD
文件包含原始SHA-1,而不是ref: refs/heads/branch
。在这种情况下,接收一个推动不会搞砸正在进行的工作,所以我们可以放心地忽略这种情况。)
这是接收推送背后的基本机制:
作为接收者,我们首先接收要添加到我们的存储库的对象。 3 我们添加所有这些对象,即使我们最终拒绝一个或多个参考更新
现在我们得到一份提案列表,其一般形式如下:"请将refs/heads/X
设置为1234567...
","强制设置{{ 1}}到refs/heads/Y
"等等。这些对应于那些启动fedcba9...
的人使用的refspec。 (如果提供的SHA-1全为零,则他们的git要求我们删除这些引用。)
我们将这些参考更新请求中的每一个都考虑在内,部分是一次一个,部分是全部,应用下面列出的子规则。对于通过的更新,我们将提供的引用设置为提供的SHA-1并告诉其他git" ok,done&#34 ;;对于失败的更新,我们告诉其他git"不,拒绝"并提供更多的"原因"信息。 (我们还将stderr输出,有时是stdout输出,传递给另一个git。有一个小协议告诉他哪些是我们自己的答案,哪些只是传递输出,所以他的git知道我们接受了哪些更新。)
一旦我们完成所有工作,我们就会运行一个" post-receive hook"并将成功的更新传递给它(与预接收挂钩的形式相同,但取消了被拒绝的更新)。
现在让我们(轻描淡写)用于接受或拒绝个别更新和/或整体更新的规则。这些不一定是实际的内部顺序(我将忽略一些特殊情况,例如push
):
某些更新必须通过各种内置测试,最常见的是"快进"测试,有时候"永远不会改变这个"测试。究竟哪些refs以哪种方式进行测试取决于我们的git版本,我们的配置以及此更新的force标志。有关这些的详细信息,请参阅the git config
documentation,特别注意receive.shallowUpdates
和receive.denyDeletes
,并注意git用于将快进规则应用于标记更新(但不是标记删除),直到git 1.8.2,当标签改为"永不改变" (但除非设置receive.denyNonFastForwards
,否则仍允许删除。)
更新的整套被发送到receive.denyDeletes
挂钩(在其标准输入上,作为一系列行)。他们首先增加了一点信息:当前的SHA-1与每个参考相关联,或者如果我们没有那个参考则全0。如果该钩子退出非零,则更新的整个集合 - 整个推送 - 被拒绝。 (如果挂钩不存在,我们认为此测试已通过。)
每个单独的更新都发送到pre-receive
挂钩(作为参数)。如果该钩子退出非零,则拒绝此特定更新,但我们继续验证其他钩子。 (和以前一样,如果钩子不存在,则测试是自动传递。)
最后,我们拥有"非裸存储库"规则,这是你在这里关心的规则,我将分开到他们自己的部分。
(我看到VonC beat me to this,但我会继续详细说明。)
git 1.6.6,git 2.3和git 2.4中的新配置条目或值是:
update
:这个选项实际上是在git 1.6.2中引入的,但在git 1.6.6之前并没有做任何事情。在此之前,接收当前分支的推送删除,删除了当前分支的引用。当它这样做时,receive.denyDeleteCurrent
指向一个不存在的分支(为了修复它你必须使用管道工具或直接修改文件)。在git 1.6.6中,默认情况下已停止允许。 (我没有经过测试,看看"糟糕的HEAD"事情是否仍然发生。)
HEAD
:这也是在1.6.2期间进行的,并且在1.6.6中启用了(即默认"拒绝"操作生效)。然而,它增长了新的更新并且不断变化。 git 2.3中的价值。
请注意,这两个特定于"当前分支",即receive.denyCurrentBranch
引用的(单个)引用refs/heads/br
。同样,它们仅在未设置HEAD
时应用。在这种情况下,有一个工作树,它充满了以某种方式与core.bare
中归档的SHA-1相关的文件。还有(可能是 4 )索引文件,它可能有也可能没有refs/heads/br
- ed,add
- ed,并且如果你&#39则保持合并状态;在冲突合并的中间。
假设,通过rm
,您允许某人receive.denyCurrentBranch
更改您的存储库{ - 1}}存储的SHA-1。进一步假设您没有设置任何部署挂钩,并且没有使用新的(2.3+)功能。然后,在这种情况下,如果其他人改变了您的git push
,您自己的索引和工作树将保持完全不变。具体来说,让我们说refs/heads/br
曾经指向提交refs/heads/br
而其他人 - 例如鲍勃 - 刚刚成功推送并将其更改为br
。
如果您现在完成自己的编辑/合并/其他任务,2222222...
结果正常,并运行3333333...
,git将从您当前的索引进行新的提交 ,除了您的git add
和git commit
s"之外,其中包含来自提交2222222...
的所有内容。 Bob所做的事情在git add
中,在您的索引中不是。但是,git的新提交将使用git rm
作为其父级,同时使用从索引中获取的内容,它基于3333333...
。结果是你的提交会在添加所有更改时恢复所有Bob的更改:将新提交与3333333...
区分开来会显示你做了什么,而将新提交与其父项进行区分将显示你退出鲍勃的所有工作都在保持自己的工作。
如果做有一个可以进行部署的钩子,那么索引和/或工作树的内容将完全取决于该钩子的作用。例如,如果它执行2222222...
,则Bob更改的所有内容都将替换您在索引和工作树中放置的内容。
这些结果都不是任何人真正想要的。
新的2222222...
设置更接近人们有时想要的内容:在允许参考更新之前(Bob将git checkout -f
从updateInstead
更改为refs/heads/br
) ,git检查你的索引和工作树是否匹配提交2222222...
。如果他们这样做, 5 git允许Bob的推送和将该更新应用于您的索引和工作树,就好像您已经发现Bob的推动一样以某种方式完成3333333...
,或任何相当于使一切更新的东西。
这里还有一些潜在的危险。例如,假设您已开放2222222...
来处理它。您已经花了一段时间追查一些参考网址并在编辑器中输入,但没有在任何地方写入结果。与此同时,鲍勃已经修复了git checkout br
并且他运行了他的README
。你的git看到你的工作树是"干净"并且更新是"安全",因此它会更新您的README
。
根据您的编辑器的聪明程度,当您去写git push
时,您可能会覆盖Bob的更改,或者您的编辑可能会说"嘿,README
改变了,我会抓住新的"有人可能认为这是编辑的不良行为(而且我会购买那个论点),但它仍然是一个潜在的问题 - 而且它不仅限于编辑;您可能正在运行一些缓慢的计算过程来编写您保持源代码控制的文件,这可能会产生相同类型的问题。
Git并没有尝试决定如何处理这一切。 Git只为您提供配置选项(更多机制),并将最终的策略留给您。我说这里的git默认是正确的;发烧友README
模式不是默认模式,因为"右边"政策尚不清楚。
1 还有其他可能的错误,具体取决于您是否需要组写入模式和共享以进行简单的ssh推送。在以前的工作场所,我们最终制定了一个使用脚本配置可推送存储库的策略:您可以设置您想要在其中看到的私有存储库,然后您自己运行脚本或让管理员运行它,给它私有仓库的URL,通过克隆创建公共共享仓库。在那之后我们并不关心你用私人仓库做了什么,但这里的要点是我们要用README
克隆而不是必须有人 - 通常是我去修复所有破碎的位。 : - )
2 即使是裸存储库也有一个updateInstead
文件,因此有一个当前分支。它也有一个索引,但没有work-dir,索引通常是无关紧要的。 (某些部署脚本最终会使用裸仓库的索引,这会导致某些部署脚本出现错误,但完全是另一个问题。)当前分支略有相关性:它会影响某个分支机构否则' s --bare
会在克隆过程结束时检出它们,前提是它们没有指定特定的分支名称。
3 通常我们将这些作为"瘦包",也就是说,可以对我们已经拥有的对象进行增量压缩的包。为了实现这一目标,在接收对象之前需要一步"步骤,我们告诉发件人我们有什么SHA-1。您可以使用HEAD
在发件人上查看我们告诉发件人的内容。还有一些早期的协议协商步骤。这些涉及较低级别的细节,但不适用于上述过程。
4 您可以删除git clone
,git会在以后需要时重新构建它。我并不特别建议将其删除,但效果是丢失所有已存储的git ls-remote
和.git/index
以及任何合并信息(如果您正处于中间位置)合并。
5 并通过了其他测试(参见VonC's answer)。其中一些额外的测试并非在git add
模式的初始刺中,而且我认为这是很难找到的。
答案 2 :(得分:2)
是否意味着git可以推送到远程存储库的当前分支? (我的猜测是肯定的,但我不确定)
这取决于你的git版本 在版本2之前,你必须明确地告诉git要推送到哪个分支,但是从版本2.X它已被更改。
所有详情均为here:
Git v2.0发行说明
向后兼容性说明
当
git push [$there]
没有说什么推,我们使用了 传统"匹配"到目前为止的语义(所有的分支都被发送了 只要已经存在同名分支,就可以到远程控制台 在那边)。在Git 2.0中,默认现在是"简单"语义,
推动:
只有当前分支到同名的分支,并且只有 当前分支设置为与该远程集成时 分支,如果你正在推送到同一个遥控器;或
只有当前分支到具有相同名称的分支,如果您 正在推送到一个不是你常去的地方的遥控器。
您可以使用配置变量" push.default"改变 这个。如果你是一个想要继续使用的老人 "匹配"语义,您可以将变量设置为"匹配",for 例。阅读文档以了解其他可能性。
您可以为每个分支设置默认(获取)远程分支,分支将是"通话"到(pull/push
)。
设置在每个分支的.git/config
文件中定义
您也可以使用以下命令手动更改它:
git branch --set-upstream-to=upstream/foo