git push中的non-fast-forward問題
[| 2012/04/16 13:50]
今天在回滾一個git操作記錄時(使用了git reset --hard)遇到了問題,在push回伺服器時提示:
error: failed to push some refs to '[email protected]:code/comlogsvr-proxy'
To prevent you from losing history, non-fast-forward updates were rejected
Merge the remote changes (e.g. 'git pull') before pushing again. See the
'Note about fast-forwards' section of 'git push --help' for details.
看了一下文件:
原文:
NOTE ABOUT FAST-FORWARDS
When an update changes a branch (or more in general, a ref) that used to point at commit A to point at another commit B, it is called a fast-forward update if and only if B is a descendant of A.
In a fast-forward update from A to B, the set of commits that the original commit A built on top of is a subset of the commits the new commit B builds on top of. Hence, it does not lose any
history.
In contrast, a non-fast-forward update will lose history. For example, suppose you and somebody else started at the same commit X, and you built a history leading to commit B while the other
person built a history leading to commit A. The history looks like this:
B
/
---X---A
Further suppose that the other person already pushed changes leading to A back to the original repository you two obtained the original commit X.
The push done by the other person updated the branch that used to point at commit X to point at commit A. It is a fast-forward.
But if you try to push, you will attempt to update the branch (that now points at A) with commit B. This does not fast-forward. If you did so, the changes introduced by commit A will be lost,
because everybody will now start building on top of B.
The command by default does not allow an update that is not a fast-forward to prevent such loss of history.
If you do not want to lose your work (history from X to B) nor the work by the other person (history from X to A), you would need to first fetch the history from the repository, create a history
that contains changes done by both parties, and push the result back.
You can perform "git pull", resolve potential conflicts, and "git push" the result. A "git pull" will create a merge commit C between commits A and B.
B---C
/ /
---X---A
Updating A with the resulting merge commit will fast-forward and your push will be accepted.
Alternatively, you can rebase your change between X and B on top of A, with "git pull --rebase", and push the result back. The rebase will create a new commit D that builds the change between X
and B on top of A.
B D
/ /
---X---A
Again, updating A with this commit will fast-forward and your push will be accepted.
There is another common situation where you may encounter non-fast-forward rejection when you try to push, and it is possible even when you are pushing into a repository nobody else pushes into.
After you push commit A yourself (in the first picture in this section), replace it with "git commit --amend" to produce commit B, and you try to push it out, because forgot that you have pushed
A out already. In such a case, and only if you are certain that nobody in the meantime fetched your earlier commit A (and started building on top of it), you can run "git push --force" to
overwrite it. In other words, "git push --force" is a method reserved for a case where you do mean to lose history.
我的翻譯版(不是嚴格依照原文,按自己理解的意思複述):
關於 FAST-FORWARDS
當修改一個branch(或ref)時,在a是b的直接基線或祖先基線的情況下,將HEAD指標從a移動為b叫做fast-forwards
在這種情況下,因為不會丟失任何歷史資料,所以叫做fast-forward
但是,對於non-fast-forward就會丟失歷史資料,設想你和另一個人同時以x為基線開發,你開發了一個叫b的commit,而那個人開發了一個叫a的commit:
B
/
---X---A
如果那個人已經將a提交到了服務端,現在服務端的head指向了a,如果你試圖去提交b,那麼服務端會試圖將head從a移動到b上,如此一來,就會丟失a的修改內容,這就是non-fast-forward。
所以預設情況下不允許這種提交,以防止資料丟失
如果你不想丟失你的和別人的工作成果,那麼需要先從服務端獲取其他人的修改,生成一個包含你的和其他人修改的commit,然後將其提交到伺服器。
你可以先執行git pull,然後解決合併衝突,然後git push。git pull會基於a和b生成一個c記錄,同時包含兩者的修改內容
B---C
/ /
---X---A
這樣提交c請求就變成了fast-forward請求,從而被允許。
同樣的,你可以在a基礎上新增從x到b的這些請求,使用git pull --rebase並push結果到伺服器,這樣會生成一個commit:d,在a的基礎上添加了從x到b的修改
B D
/ /
---X---A
這也是一個fast-forward請求,是被允許的。
(這一段不是特別理解。。像是廢話,因為跟上圖第一種情況看起來是一回事)還有一種情況,即使沒有其他人向版本庫推送過資料,你也可能遇到non-fast-forward的情況:
當你推送a到服務端後,又使用了git commit --amend 修改a為b,然後可能忘記已經推送過a,於是試圖去推送b到版本庫。這樣的話,當你確認沒有人fetch過a的話,可以使用git push --force去覆蓋這個記錄。
也就是說,當你確認你確實需要丟失歷史資料時,可以使用git push --force來強制推送
在gitolite中,對於每個版本庫的授權就有“RW+”欄位,其中的“+”許可權,就是強制推送的許可權,由於可能會導致歷史提交丟失,所以是比W更高階的許可權,需要單獨授予。