合并不会在推送时发生,它们会发生在git merge
(嗯,pull
但这实际上只是 fetch + merge,因此,合并发生在合并时)。
你似乎更有可能做了这样的事情:
<get copy from origin/develop>
<wait around for remote's origin/develop to acquire new commits>
git checkout develop # get onto (local) develop branch
<edit>
git commit -m message # create some new commit(s)
git push origin develop # attempt to push -- but this fails!
git pull
这是创建合并提交的最后一步(M
上面),因为pull
意味着fetch
(获取所有现在位于的新提交origin/develop
),然后merge
(获取您的本地develop
并将您的提交与刚刚获取的新提交合并)。
如果你还没有git push
编辑这个新结果,那么远程仓库没有你的本地提交,你标记的那些C*
和M
. 在这种情况下,你的状态很好!(您可以通过git fetch
再次运行来检查以确保您的本地存储库origin/develop
与远程存储库中的匹配,然后git log origin/develop
查看其中的内容。)
记住这里有两个完全独立的 git 存储库可能会有所帮助:你的,你的东西;和你在origin
这里打电话的那个。(我们称那台机器X
为 。)如果您要登录到X
,它有自己独立的提交历史和分支名称等等。在那里,您将目录更改为 repo,运行git log -1 develop
,然后查看该分支的顶端有什么。
现在,如果您注销X
并回到您自己的机器上,您可以运行git log -1 origin/develop
. 如果这与您在 上看到的相同X
,则get fetch
无需更新,因为git fetch
实际上(但更有效),登录X
并查看develop
其中的内容。任何X
你没有的东西origin/develop
,都会fetch
带来并添加到origin/develop
。现在您与X
. X
没有你的东西,但你有他们的。
如果你然后采取额外的步骤做 a merge
(包括由 暗示的那个pull
),git 将,如果它必须,做一个合并提交......但所有这些都在你的repo 中,在分支的顶端(仍然develop
在这个案例)。除非并且直到您将此合并提交推送到X
(或有人X
从您那里拉出您的提交,但我们现在暂时忽略它:-)),X
否则不会拥有它。
无论如何,只要远程(X
此处)没有您的合并提交,您就是黄金。因为他们没有,其他人也没有。你可以做一个rebase
你的develop
分支,把你的 commit( C*
) 放在origin/develop
. 这将摆脱合并提交(M
),然后您可以将简单的快进推送到origin/develop
.
如果X
确实有您的合并提交(即,如果您在pull
编辑后推送并获得了该合并),那么您(在某种程度上)被卡住了,因为可能其他人可以访问X
并且现在正在使用您的合并提交。可以回滚 repo on X
,类似于您可以在自己的 repo 中使用 etc 执行此操作的方式git reset
,git rebase
但这通常是一个坏主意。
现在,假设您实际上
已经X
推送到另一个 repo(在 machine 上),但是您绝对确定没有其他人看到您的更改,并且您肯定会重置它们,而不是还原它们(还原相当于“坦白”并留下糟糕的记录,这让其他人可以轻松地从中恢复,但也让他们看到你的错误:-))。
1
这是诀窍:您首先需要让机器 X 的存储库说“分支的尖端devel
是提交 C7”,其中 C7 在您之前的图表中,只是重新编号,以便我可以以不同的方式命名每个提交:
--------------------------- C*--- M
--- C4 --- C5 --- C6 --- C7 ---- /
那么,你怎么能这样做呢?好吧,一种方法是登录X
, 2 cd
进入 repo(即使它是--bare
),然后git update-ref
在那里使用。假设 C7 的 SHA1 实际上是50db850
(如“git log”所示)。然后你可以这样做:
localhost$ ssh X
X$ cd /path/to/repo.git
X$ git update-ref refs/heads/develop 50db850
但是,如果您无法登录X
,或者只是不想登录,您可以使用git push -f
. 3 (这还有其他优点:特别是,一旦成功完成,您的 git repo 就会知道origin/develop
已经倒带push -f
。)只需创建一个指向正确提交的本地分支提示:4
localhost$ git branch resetter 50db850
localhost$ git log resetter # make sure it looks right
...
localhost$ git push -f origin resetter:develop
Total 0 (delta 0), reused 0 (delta 0)
To ssh://[redacted]
+ 21011c9...50db850 resetter -> develop (forced update)
localhost$ git branch -d resetter # we don't need it anymore
完成此操作后,机器 X 将恢复到您想要的状态,并且您可以继续进行,就好像您从未推送过您不喜欢的合并一样。
请注意,当您执行 时push -f
,如果其他人在 之上进行了新提交M
,这些也lost+found
将变得不可见(从技术上讲,它们与您的合并提交一起仍然存在,但它们在git fsck --lost-found
, 和几个月后,它们真的会永远消失)。5
同样非常重要:这种“共享存储库的回滚”对于共享存储库的其他用户来说是一个很大的痛苦,所以在你这样做之前一定要确定它是好的。
1这种微不足道的合并甚至不需要还原。将合并留在那里没有根本性的问题。但是,如果您正在处理更严重的错误合并,那么除了“oops”的记录之外,恢复合并还有另一个缺点:它使稍后“重做”更改变得更加困难,因为稍后的“合并故意”将看到较早的合并并认为:好的,我不需要重新合并那些更改。然后,您必须改为“还原还原”。
我认为正确的外卖教训是:在你推动之前看看(git log
),以确保你要推动的是你打算推动的东西。
2或者,更简单、更简单:git ls-remote
. 这使用获取协议代码来查看遥控器有什么。但它隐藏了我要在这里讨论的主题,即:遥控器和你的一样是一个 repo!
3即将发布的 git 版本 2 具有新的“安全功能”,可用于git push -f
. 但他们还没有出来,所以这对你没有帮助。稍后我会重新编辑它,以记录它们。在此之前,请务必小心:此时,您正在与任何试图将新内容推送到共享存储库的人竞争。
4您甚至可以通过原始 SHA-1 ID 执行此操作。不过,分支名称更容易连续多次正确键入。
5保留和过期是通过 git 的“reflogs”。共享服务器可能没有记录所有 ref 更新;如果没有,只有已经拥有新提交的私有仓库才会保留它们。最短的默认到期时间是 30 天,即大约 1 个月,因为提交不再可以从分支提示到达。但请注意,在强制将“丢失的工作”从共享存储库中推出后,让每个人都在他们的存储库中搜索“丢失的”提交是一种无趣、疯狂的争夺。