今天完成一個功能開發(fā),提交代碼的時候,突然提示如下錯誤:
To C:/Users/Alpha/AppData/Local/Temp/d20170730-15308-3dbr6w/.git
! [rejected] master -> master (non-fast-forward)
error: failed to push some refs to 'C:/Users/Alpha/AppData/Local/Temp/d20170730-15308-3dbr6w/.git'
hint: Updates were rejected because the tip of your current branch is behind
hint: its remote counterpart. Integrate the remote changes (e.g.
hint: 'git pull ...') before pushing again.
hint: See the 'Note about fast-forwards' in 'git push --help' for details.
意思就是本次提交被遠程倉庫拒絕了,因為當前分支無法與遠程倉庫對應起來。遠程倉庫對應分支默認有個指針指向最新提交到倉庫的 commit ,而所有的本地倉庫的分支都可以看做是從這個 commit 分散開來的。也就是本地分支的最后一次 push 到倉庫的 commit 一定與倉庫對應分支的最新一次 commit 是相同的,否則就無法對接。也就是會出現(xiàn)上面的錯誤提示。如果是正常 push 到倉庫,正確的完成 commit 更新,那么這次更新就是一個 fast-forward
更新,而如果不理會錯誤警告用本地更新強制覆蓋倉庫,就是一次 no-fast-forward
更新,很明顯,no-fast-forward
更新會導致記錄丟失。
那么這種問題是如何發(fā)生的呢?比如有兩個人都是從倉庫的 master 分支克隆到本地,然后分別開發(fā)。master 本身有一個指針 HEAD 指向最后一次 commit 記錄 commit-0 。A 先完成一個功能,并 push 到倉庫,這次 commit 記為 commit-A,這也就是一次 fast-forward
更新,此時倉庫的 master 分支的 HEAD 指針就指向了 commit-A。接下來 B 也完成了一個功能,要向倉庫 push commit-B,如果沒有做額外操作,肯定會出現(xiàn)上面的錯誤。
知道錯誤是如何發(fā)生的,就可以避免了。既然倉庫有了更新,那么就要先把倉庫的更新拉取到本地。這里有兩種方式可以拉?。阂皇侵苯邮褂?git pull
命令,該命令會在拉取的同時會直接與本地對應分支進行合并,如果確信倉庫的更新與本地不會發(fā)生沖突,那么可以直接使用。但是很可能 A 與 B 都對同一些文件做出了修改,那么必然導致沖突。不過既然知道會沖突也只能老老實實解決沖突了,不管是 fetch 先解決沖突在合并還是 pull 先合并再解決沖突,這個過程少不了的,除非你確定倉庫的更新是沒用的可以直接拋棄,就可以執(zhí)行 git push -f
強制覆蓋到倉庫,這會導致倉庫中某些記錄丟失。
我們借助于 githug 28 關來模擬看看:
$ git push -u origin master
To C:/Users/Alpha/AppData/Local/Temp/d20170731-15124-1ywoym1/.git
! [rejected] master -> master (non-fast-forward)
error: failed to push some refs to 'C:/Users/Alpha/AppData/Local/Temp/d20170731-15124-1ywoym1/.git'
hint: Updates were rejected because the tip of your current branch is behind
hint: its remote counterpart. Integrate the remote changes (e.g.
hint: 'git pull ...') before pushing again.
hint: See the 'Note about fast-forwards' in 'git push --help' for details.
$ git log origin/master --oneline
68ad000 Fourth commit
$ git log --oneline
b977ec3 Third commit
6d15890 Second commit
5266aa2 First commit
$ git push -f -u origin master
Counting objects: 7, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (5/5), done.
Writing objects: 100% (7/7), 546 bytes | 0 bytes/s, done.
Total 7 (delta 2), reused 0 (delta 0)
To C:/Users/Alpha/AppData/Local/Temp/d20170731-15124-1ywoym1/.git
+ 68ad000...b977ec3 master -> master (forced update)
Branch master set up to track remote branch master from origin.
$ git log origin/master --oneline
b977ec3 Third commit
6d15890 Second commit
5266aa2 First commit
可以看到,強制覆蓋 push 后,倉庫的 Fourth commit
已經(jīng)不見了。
但如果不想丟掉 commit-A 的同時又不想與 commit-A 合并,B 想繼續(xù)接著本地倉庫工作,可以使用 git rebase origin/master
,表示將本地所有 commit 排在倉庫 的 commit 記錄之后。然后向倉庫的 push 就會被接受。同樣借助于 githug 28 關,而且,這才是 28 關正確的過關方式:
詳細過關過程如下:
$ git push -u origin master
To C:/Users/Alpha/AppData/Local/Temp/d20170731-14980-yb0fll/.git
! [rejected] master -> master (non-fast-forward)
error: failed to push some refs to 'C:/Users/Alpha/AppData/Local/Temp/d20170731-14980-yb0fll/.git'
hint: Updates were rejected because the tip of your current branch is behind
hint: its remote counterpart. Integrate the remote changes (e.g.
hint: 'git pull ...') before pushing again.
hint: See the 'Note about fast-forwards' in 'git push --help' for details.
$ git log origin/master --oneline
015383a Fourth commit
$ git log --oneline
38aa398 Third commit
1c03f48 Second commit
ad32a6d First commit
$ git rebase origin/master
First, rewinding head to replay your work on top of it...
Applying: First commit
Applying: Second commit
Applying: Third commit
$ git log --oneline
fbf0528 Third commit
03d1240 Second commit
9828360 First commit
015383a Fourth commit
$ git push -u origin master
Counting objects: 6, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (6/6), done.
Writing objects: 100% (6/6), 607 bytes | 0 bytes/s, done.
Total 6 (delta 2), reused 0 (delta 0)
To C:/Users/Alpha/AppData/Local/Temp/d20170731-14980-yb0fll/.git
015383a..fbf0528 master -> master
Branch master set up to track remote branch master from origin.
但其實還有一種比較常見的出現(xiàn) no-fast-forward
這種錯誤的情境,是在你向一個只有你自己可訪問的倉庫 push 的時候發(fā)生的。當你已經(jīng)將一次 commit-A push 到倉庫后,然后因為某些原因又使用了 git commit --amend 修改了 commit-A ,這個時候 commit-A 就變成了 commit-B,而此時本地倉庫就沒有關系 commit-A 的記錄了,這個時候再次向倉庫 push ,很明顯,commit-B 無法與倉庫的 commit-A 進行對接,所以出現(xiàn)了 no-fast-forward
錯誤。這種情況下其實也很好解決,如果你確定 commit-A 已經(jīng)完全無用并且沒有人將 commit-A 拉取到本地進行進一步開發(fā)之后,你就使用 git push -f
來覆蓋倉庫記錄。之后,你就會永遠丟失 commit-A 記錄了。
而對比發(fā)現(xiàn),我之所以會遇到本文開頭的錯誤,就是因為之前使用了 git commit --amend
命令修改了已經(jīng) push 到倉庫的 commit 的注釋導致的。因此,一旦已經(jīng) push 到倉庫,想要做出修改,就只能通過一次新的 commit 來完成對某次已經(jīng) push 到倉庫的 commit 記錄的修改了,可以參考 githug 52 關 revert。
參考:
[1]https://git-scm.com/docs/git-push/2.10.0#Note about fast-forwards
[2]極客學院 githug 通關攻略
本文最早發(fā)布于 alphagao.com 。