這一集要講的是:還沒 push 前可以做的壞事,也就是 reset 跟 rebase。
相較於 SVN 這種 commit 就是送到遠端伺服器,Git 的 commit 其實東西都還是在本地端,所以只要你還沒 push 出去分享給別人,你的 commit logs 是可以修改的!! 這種功能非常的 powerful,可以讓你 undo 和 rewrite commit history。如果你用 Git 只會 git commit 然後接著馬上 git push,那你就沒有學到精隨啊!~
使用告誡:如果你已經 push 出去了,請千萬不要做 rewrite history 的動作,會天下大亂啊。正確善用 undo changes/rewrite history 的功能,我們可以讓東西準備好弄的整整齊齊才 push 出去給別人 pull。
讓我們來學幾招吧:
amend
git commit -C HEAD -a –amend 快速修正前一次 commit 的錯誤,只要修 typo 之後打這行,就會替換掉前一次的 commit
reset
git reset 砍掉 commit 重來,但是修改的程式還是留在 working tree。例如:
git reset HEAD^ 就會回到前一版本(一個^表示是前一版),並把其中的 changes 繼續留在 working tree 中。適合發現前一次 commit 有問題或是想要修改 commit log,可以修改後再重新 commit。
git reset 如果加上 –soft 參數則會把 changes 直接加到 staging area。
加上 –hard 參數表示不留 staging area 也不留 working tree(完全刪除任何修改記錄),例如:
git reset –hard HEAD^ 則會完全抹掉前一次的 commit。
另個常用的情境是要把修改的檔案從 staging area 移走,指令就是 git reset HEAD filename (但還是留在working tree)。BTW,要回復 working tree 中修改的檔案成本來的樣子,指令是 git checkout filename (重新拿出本來的檔案)
順道一提,如果東西已經 push 出來了,要怎麼回復? 這時候就得用 revert 了。
git revert 會用一個新的 commit 來回復所有的變更(適合已經push出去給別人的情境)。加上 -n 可以不先 commit,這樣可以多 revert 幾次後再一次 commit。
指令會有差別都是因為 remote repo.<-> local repo.<->staging area<->working tree 分了四層移來移去的關係,你只要想清楚這幾層就融會貫通了 (請參考第一集有解釋 staging area 和 working tree)。
rebase
我在 使用 rebase 避免無謂的 merge 一文中有解釋過 rebase 的其中一個用途。而 rebase 的真正潛力是,我們可以從指定的版號之後,重新隨你意 commit 一次來重建 history,超威的。首先輸入 git rebase -i 版號 就會可以跳出 editor 可以編輯,我們可以
a. 變更 commit 順序
b. 將多個 commit 合併 squash
c. 將一個 commit 打散 (edit 會停著讓你可以 git reset HEAD^ 打散重新 commit,完成後 git rebase –continue )
另一種 rebase 用法是不需要打 -i ,直接指定另一個 branch 或 tag,這樣就會重新 commit 另一個 branch 的東西,然後才 commit 自己的 (也就是 使用 rebase 避免無謂的 merge 的用法)。
git rebase 若有 conflict 就會停下來, 跟 merge 一樣處理完 add,然後 git rebase –continue 就會繼續 commit (也可以 –skip 或 –abort 放棄啦)
rebase 有個 onto 參數用法,使用的情境是:假設你有三個有 dependency 的 branch 分別叫做 master/contact/search,後來發現 search branch 只有 depend on matser,於是你可以輸入 git rebase –onto master contact search 這樣就會讓 search branch 從 master 的地方開始重新 commit。
再次提醒,rebase 千萬只能適合東西還沒 push 的情境,或是你自己的 local 專用私人 branch。rebase 一個已經 push 出去的 repository,然後你又把修改的 history push 出去,是會造成超級大災難的。
在學習 rebase 的過程中,很容易拿來跟 merge 比較一下。我發現一個有趣的不同點:如果有檔案在要被 merged 的 branch 中被刪除,如果用 rebase 檔案最後會不存在,但是用 merge 的話檔案最後還在 XDXD
另外,在 rebase branch 之後,如果再做 merge,就會發現因為 master 直接就是被 merge 的祖先,所以線圖直接變成一條線,而有這種 parent 關係的 merge 就叫做 fast-forward。換句話說,因為沒有發生任何 merge commit,也不會發生 conflict,Git 內部單純只是變更 reference 參照,所以謂之 fast-forward。
好心提醒,因為開 local branch 是如此便宜無害,所以要做 rebase 時建議您可以先開一個 local branch 來實驗 rebase。老實說,rebase 還挺危險的 XDXD
最後,rebase 我認為算是 Git 初學者最難理解的功能吧,但是如果不知道什麼是 rebase,就不能說是懂 Git 啊。