合并不会发生在 push 上,它们发生在 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 pushed 这个 new 结果,那么远程 repo 没有 either 您的本地提交,即您标记的那些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 并查看@ 中的内容987654346@那里。 X 拥有的任何你在 origin/develop 中没有的东西,fetch 带来并添加到 origin/develop。现在您与X 同步。 X 没有你的东西,但你有他们的。
如果您随后采取额外的步骤来执行merge(包括pull 所暗示的步骤),git 将在必要时进行合并提交......但这一切都在 你的 repo,在分支的顶端(在这种情况下仍然是develop)。除非并且直到您将此合并提交推送到 X(或 X 上的某个人从您那里提取您的提交,但我们现在先忽略它 :-)),X 不会拥有它。
无论如何,只要远程(X 此处)没有您的合并提交,您就是黄金。因为 他们 没有它,所以其他人也没有。你可以在你的develop 分支中创建一个rebase,将你的提交(C*)放在origin/develop 之上。这将摆脱合并提交 (M),然后您可以将简单的快进推送到 origin/develop。
如果X 确实 有你的合并提交——即,如果你在pulled 之后推动并获得了合并——那么你(在某种程度上)被卡住了,因为可能是其他人可以访问X,现在正在使用您的合并提交。可以回滚 X 上的 repo,类似于您在自己的 repo 中使用 git reset 和 git rebase 等执行此操作的方式,但这通常不是一个好主意。
现在,假设您
已经实际上推送到了另一个 repo(在机器
X 上),但是您绝对确定没有其他人看到您的更改,并且您肯定会重置它们,而不是还原它们(还原等于“坦白”并将搞砸记录抛在脑后,这可以让其他人轻松从中恢复,但也让他们看到您的错误:-))。
1这是诀窍:你首先需要让机器 X 的 repo 说“分支的尖端 devel 是提交 C7”,其中 C7 在你之前的图表中,只是重新编号以便我可以命名每个提交都不同:
--------------------------- C*--- M
--- C4 --- C5 --- C6 --- C7 ---- /
那么,你怎么能这样做呢?好吧,一种方法是登录 X,2cd 进入 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(这还有其他好处:在特别是,一旦 push -f 成功完成,您的 git repo 将知道 origin/develop 已被回绕。)只需创建一个指向正确提交的本地分支提示: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 的 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 个月,因为提交不再可以从分支提示到达。但请注意,在强制将“丢失的工作”从共享存储库中推出后,让每个人都在他们的存储库中搜索“丢失的”提交是一种无趣且疯狂的争夺。