合并不会发生在推送时,而是发生在git merge
(嗯,并且pull
但这实际上只是获取+合并,因此,合并发生在合并时)。
您似乎更有可能做了这样的事情:
<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
means fetch
(获取现在的所有新提交origin/develop
), then merge
(带上你当地的develop
并将您的提交与刚刚获取的新提交合并)。
如果你还没有git push
ed this new结果,那么远程仓库没有either您的本地提交,您已标记的提交C*
and M
。在这种情况下,你的状态很好! (您可以通过运行来检查git fetch
再次确保您本地的存储库origin/develop
与遥控器中的匹配,然后执行git log origin/develop
看看里面有什么。)
记住这里有两个完全独立的 git 存储库可能会有所帮助:你的,包含你的东西;和你打电话的人origin
这里。 (我们称那台机器为X
.) 如果您要登录X
,它有自己独立的提交历史和分支名称等等。在那里,您可以将目录更改为存储库,运行git log -1 develop
,看看树枝的尖端有什么。
现在如果您注销X
回到你自己的机器上,你可以运行git log -1 origin/develop
。如果这和你看到的一样X
, then get fetch
没有什么可更新的,因为什么git fetch
实际上(但更有效),登录到X
看看里面有什么develop
那里。任何东西X
有你没有的origin/develop
, fetch
带来并添加到origin/develop
。现在您与X
. X
没有你的东西,但你有他们的东西。
如果您采取额外的步骤进行merge
(包括由pull
),如果必须的话,git 会进行合并提交……但这一切都在your回购协议,在分支的尖端(仍然develop
在这种情况下)。除非并且直到您将此合并提交推送到X
(或某人在X
从您那里提取您的提交,但现在让我们忽略它:-) ),X
不会有它。
无论如何,只要远程(X
这里)没有你的合并提交,你是黄金。自从they自己没有,别人也没有。你可以做一个rebase
你的develop
分支来放置你的提交(C*
) 在之上origin/develop
。这将摆脱合并提交(M
),然后您可以将简单的快进推至origin/develop
.
If X
does进行合并提交——即,如果您在之后推送pull
ed 并得到合并 - 然后你就陷入困境(在某种程度上),因为大概其他人可以访问X
现在正在使用您的合并提交。可以回滚仓库X
,类似于您在自己的存储库中执行此操作的方式git reset
and git rebase
等等,但这通常是一个坏主意。
Now, suppose you
have in fact pushed to the other repo (on machine
X
), but you're absolutely sure nobody else has seen your changes yet, and you're definitely going to reset them, rather than reverting them (reverting amounts to "fessing up" and leaving the screwup-record behind, which lets everyone else recover from it easily, but also lets them see your error :-) ).
1
诀窍是:您首先需要获取机器 X 的存储库来说明“分支的提示”devel
是提交 C7”,其中 C7 位于您之前自己的图表中,只是重新编号,以便我可以以不同的方式命名每个提交:
--------------------------- C*--- M
--- C4 --- C5 --- C6 --- C7 ---- /
So, how can you do that? Well, one way is to log in on X
,2 cd
into the repo (even if it's --bare
), and use git update-ref
there. Let's say the SHA1 for C7 is actually 50db850
(as shown by "git log"). Then you could do this:
localhost$ ssh X
X$ cd /path/to/repo.git
X$ git update-ref refs/heads/develop 50db850
But if you can't log in to X
, or even just don't want to, you can do the same with git push -f
.3 (This has other advantages: in particular, your git repo will know that origin/develop
has been rewound, once the push -f
completes successfully.) Just make a local branch-tip pointing to the right commit: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 就会回到您想要的状态,您可以继续操作,就好像您从未推送过您不喜欢的合并一样。
Note that when you do the push -f
, if anyone else has made new commits on top of M
, those will also become invisible (technically they're still in there, along with your merge commit, but they are "lost" in the lost+found
sense of git fsck --lost-found
, and after a few months they'll really go away forever).5
再次和很重要:这种“共享存储库的回滚”对于该共享存储库的其他用户来说是一个很大的痛苦,因此在执行此操作之前请务必确保它没问题。
1This sort of trivial merge doesn't even need reverting. There's nothing fundamentally wrong with leaving the merge in there. If you're dealing with a more serious mistaken merge, though, there's one other disadvantage to reverting a merge, besides the record of your "oops": it makes "redoing" the changes later a bit harder, in that a later "merge on purpose" will see the earlier merge and think: OK, I don't need to re-merge those changes. You then have to "revert the revert" instead.
我认为正确的教训是:look (git log
)在你推之前确保你要推动的就是你想要推动的。
2Or, easier and simpler: git ls-remote
. This uses the fetch protocol code to see what the remote has. But it hides the theme I'm going for here, which is: the remote is a repo just like yours!
3The upcoming git version 2 releases have new "safety features" that will be useable with git push -f
. But they're not out yet, so that doesn't help you. I'll re-edit this later when they are, to note them. Until then, be really careful here: you're racing against anyone else trying to push new stuff to the shared repository, at this point.
4You can even do this by raw SHA-1 ID. The branch name is easier to type correctly several times in a row though.
5The retaining and expiration is via git's "reflogs". The shared server may not be logging all ref updates; if not, only private repos that already had the new commits will retain them. The shortest default expiration is 30 days, i.e., about one month, for commits no longer reachable from the branch tip. But note, it's a no-fun, crazy scramble to make everyone search through their repositories for "lost" commits after force-pushes "lose work" off a shared repository.