Git 中那些容易混淆和忽略的指令都作了些神馬

整理下git中不是很清晰的一些指令的背後作了什麼,有說的不對的地方,歡迎指正。html

git merge & git rebase

這節緣起於最近給公司的優秀的開源組件庫提pr,在開發說明文檔中看到,推薦在提交pr前建議用git rebase 清理下commit再提pr~。恩,平時合併改動的時候git merge是老朋友,經常使用到,介個git rebase有據說,可是用的很少。特此瞭解下二者的區別,簡單記錄下吧。git

首先git merge 和 git rebase 均可以合併兩個分支的commit。緩存

git merge

舉個栗子:spa

git checkout feature
git merge master
複製代碼

feature上除了合併了master分支上改動的commit外會多一個merge的commit說明此次merge,此外不會生成新的commit3d

git rebase

舉個栗子:指針

git checkout feature
git rebase master
複製代碼

feature上的新的commit會所有從新以master最新的commit爲base,效果就是feature上的新的commit就像是在最新的master上checkout出來的分支上後續添加的commit,這些commit是新生成的而且不會有那個專門說明合併的commit。【有些人會以爲這種commit污染了commit的記錄~,可是也有人以爲這個merge commit能夠幫忙理解到每次合併操做的發生的時間和合並的分支,這些信息是有用的。恩,我都好~code

引用很漂亮的圖來解釋以下【圖來自《Merging vs. Rebasing》】:cdn

git

git

git

劃重點,其實須要注意的點在於:htm

  • git merge 除了merge commit外,合併到feature上的commit不是生成的新的commit。
  • git rebase 則是線性的將feature的commit接到master的最新的commit後面,以新生成的commit的形式。

這有個問題,舉個栗子:blog

就是git rebase的feature分支若是是一個多人開發的分支,那麼rebase的是你本地的分支,合做的小夥伴的分支和你的這個分支進行同步的話,會有一些內容重複的可是commit的hash值不同的多餘的commit,整個commit就冗餘不乾淨了~

所以比較好的使用git rebase的場景是單人開發的分支,或者使用一個臨時的分支進行git rebase 而後使用git merge到master分支上,merge master的時候是fast forward的,而且不會有master到feature的merge commit。

此外

  • git rebase 的交互模式,即:git rebase -i 能夠進行更細粒度的rebase過程當中的commit的挑選和合並,也很方便。
  • 你也能夠rebase分支自己,好比3個commit以前爲base進行rebase,這樣至關於整理最近的3個commit的效果。

在知道了這些重要的點後,就能夠自行判斷什麼時候應該使用git merge 仍是 git rebase。git rebase還有不少用法,這裏不詳細說明,要用時再瞭解就行了。


git checkout & git reset & git revert

【本節插圖來自Resetting, Checking Out & Reverting

這三個指令均可以作到撤銷改動的做用,可是背後的行爲是不同的,結果也有所不一樣,其中git checkout確定是你們最熟悉的。這裏分別按順序介紹下三個指令的做用以及背後作了什麼。

git的本地的三個區域

git

分別稱爲:

  • 工做區
  • 暫存區
  • 版本區

這三個區分別管理着git項目中的改動的不一樣階段的狀態。

git checkout

git checkout是工做中常見的一個操做。

commit操做

在對commit操做的時候,簡單來講就是將HEAD(HEAD個人理解是一個指向當前活躍狀態或者說當前活躍commit的一個指針,表示的是如今作出的版本狀態)移動到對應的commit,當你checkout一個分支的時候,則是將HEAD指針移至這個分支的最新的一個commit。git checkout並不會影響分支上的commit,而只是切到對應的commit的版本狀態,在切換以前,須要保存當前的改動而且commit,由於你一旦切走了,雖然沒有改動commit的歷史,以後也能夠切回來,可是checkout走了以後,你的HEAD就不指向當前分支最新的狀態了,這時候須要將改動保存,以後最爲一個commit版原本進行管理。用圖來表示以下:

git

只是改變了HEAD的指向,並無改動到commit的歷史,這個時候工做區和暫存區的狀態保持一致爲checkout到的這個版本的狀態。

也就是說,git checkout對commit操做的時候的做用爲查看歷史版本,固然你也能夠在這時候進行改動而且commit,這樣操做的結果就是,在checkout到的commit的基礎上多了一個沒有歸屬任何分支的commit。當你這時checkout回其餘分支的時候,git會提醒你給這個剛纔在'detached HEAD' state時新增的commit的那個改動的分叉建立一個分支,這樣方便以後切到這個狀態,而不是一個沒有branch歸屬的commit改動。

文件操做

git checkout對文件進行操做的時候,則是將工做區的指定的文件或者目錄的狀態切換成指定的版本的狀態,對暫存區和版本區沒有影響。若是你git checkout一個文件的時候默認是HEAD,產生的效果就是放棄工做區當前的改動,這個也是小夥伴們使用git checkout比較多的操做之一。

git revert

git revert只能操做commit,不能對文件進行操做,git revert的撤銷背後的原理,就是用一次新的反向的commit將工做區、暫存區、版本區的狀態所有回退到指定的版本。也就是你的commit歷史會多一個commit,這個commit的操做就是指定的那些撤銷改動。git revert不會對歷史的commit進行改動,所以經常使用與公共分支的回滾。保留了commit歷史,同時也完成了版本回滾,而且其餘小夥伴在同步此次的回滾的時候只是至關於fast forward了一個commit版本。用圖來表示以下:

git

git reset

git reset這個操做能夠對文件也能夠對commit進行操做,git reset須要慎用,由於git reset是會改動到commit的歷史的。

文件操做

git reset指定文件的時候會將緩存區同步到你指定的那個提交。git reset默認reset到HEAD,因此能夠用來移除暫存區的指定文件。文件層面只支持--mixed參數,做用就是unstaged對應的暫存區中的文件。

commit操做

git reset的撤銷操做是「真 · 撤銷」操做,git reset 將一個分支的末端指向另外一個提交。這能夠用來移除當前分支的一些提交。被移除的commit在下次 git 執行垃圾回收的時候會被刪除。換句話說,若是你想完全的扔掉提交,你能夠這麼作。用圖來表示以下:

git

git reset會改寫當前分支的commit的歷史,因此最好不要在公共分支上進行這個操做。git reset 操做有三個選項來指定這個操做的影響範圍或者說做用域:

  • --soft – 緩存區和工做目錄都不會被改變
  • --mixed – 默認選項。緩存區和你指定的提交同步,但工做目錄不受影響
  • --hard – 緩存區和工做目錄都同步到你指定的提交

《git reset soft,hard,mixed之區別深解》中的解釋,我以爲挺清晰的,參考總結以下:

  • --soft參數告訴Git重置HEAD到另一個commit,但也到此爲止。全部的在original HEAD和你重置到的那個commit之間的全部變動集都放在暫存區中。

  • --hard參數將會blow out everything.它將重置HEAD返回到另一個commit,重置暫存區以便反映版本區的變化,而且重置工做區也使得其徹底匹配起來。這是一個比較危險的動做,具備破壞性,數據所以可能會丟失(makes everything matching the commit you have reset to.)。若是真是發生了數據丟失又但願找回來,那麼只有使用:git reflog命令了。

  • --mixed是reset的默認參數,也就是當你不指定任何參數時的參數。它將重置HEAD到另一個commit,而且重置暫存區以便和版本區相匹配,可是也到此爲止。工做區不會被更改,全部該branch上從original HEAD(commit)到你重置到的那個commit之間的全部變動將做爲local modifications保存在工做區中,(被標示爲local modification or untracked via git status),可是並未staged的狀態,你能夠從新檢視而後再作修改和commit。

總結表格

【圖表來自參考資料文章】

git


fast forward & non fast forward

在合併分支的時候git merge是老朋友,通常的小夥伴都是直接git merge 巴拉巴拉吧就解決了,這種狀況下默認使用的是fast forward模式進行分支的合併。另外咱們能夠在git merge的時候加上--no-ff參數來切換成non fast forward模式進行分支的合併。這兩種合併方式的結果不一樣,適用於不一樣的場景。

fast forward

fast forward簡單來講,就是將分支改動的commit按照時間順序依次併入到(舉個栗子)master分支上,合併的分支和master分支是一個扁平的關係。這裏有個問題就是,commit可能和master上新增的commit穿插到一塊兒,而且,在branch tree上,沒辦法直觀的看到一個分支的全部改動都有哪一些,由於這些改動被插入到master的commit歷史中了。

non fast forward

non fast forward簡單來講,合併分支的時候必定會多產生一個merge commit來講明此次合併的操做。而且在branch tree上被合併的分支和master分支的關係不是扁平的,是能夠清晰的看到這個合併的操做合併了哪些commit。此外,non fast forward的合併的回滾也比較方便,只要revert到對應的這個merge commit就行了。不會有fast forwar的模式下回滾的時候影響的commit穿插在其餘正常改動的commit中間,這時候,回滾就很難辦,可能會回滾掉正常的commit,在公共開發的分支上進行rebase整理commit歷史也很差(緣由在git rebase中已經說明了),整個處理起來就比較麻煩。

用圖來解釋以下:

git


參考資料:

相關文章
相關標籤/搜索