在對 git 有了基本理解和知道常規操做以後,如何對 git 的使用有進一步的理解?
一切皆 commit 或許是個不錯的理解思路。html
本文將從『一切皆 commit 』的角度,經過 git 中常見的名詞,如 commit, branch, tag, HEAD 和動詞,如 cherry-pick, rebase, reset, revert, stash 來理解 git。經過這些理解,指望可以更好地處理使用 git 中遇到的問題。git
好比:shell
配合希沃白板課件食用,效果更佳:
【希沃白板5】課件分享 : 《Git 進階 - 從使用角度深刻理解Git》
https://r302.cc/ke8XdO?platform=enpc&channel=copylink
點擊連接直接預覽課件fetch
在 git 中有工做區,暫存區和代碼倉庫三個概念,那爲何要有暫存區呢?爲了保證提交的原子性,在 git 的應用層面上,提交(commit,名詞)是 git 主要命令的操做的最小單位了。翻譯
關於此,能夠查看這篇知乎貼:爲何要先 git add 才能 git commit ? - Ivony的回答 - 知乎指針
本文中的內容不多涉及工做區和暫存區的操做,有了 commit 是 git 操做的基本單位這個概念,接下來將從『一切皆 commit』來理解 git。code
如上圖,其實比較好理解,咱們知道 commit 有一個 commit id,另外仍是 branch(分支),tag(標籤),HEAD(當前分支頭結點)這些概念。他們都是指向某個提交的引用(或者理解爲指針)。orm
如此,便理解了,branch,tag,HEAD 這些,本質上都是指向某個提交的引用,即:一切都是 commit 。htm
有一個引用,須要單獨說明,就是 origin/branch ,一般稱之爲遠程分支,那這個遠程分支指向哪裏呢?
如何在 『一切皆commit』 這句咒語下理解遠程倉庫?對象
以 master 分支爲例,origin/master 指向的,就是當前遠端 master 分支最新的那個提交。等等,其實這句話有點小問題,應該是最後一次更新本地倉庫時,遠端 master 分支最新的那個提交。那何時會更新遠程倉庫?在執行 pull push fetch 時更新。
你或許據說過 git pull = git fetch + git merge 的說法。
當執行 git fetch
命令時,只更新 origin/master 分支(包括全部其它的 origin 遠端分支),但並不會影響本地的任何分支。
那要更新本地的 master 分支怎麼辦? git merge origin/master
,將遠端的分支合併到本地分支,即完成了對本地 master 分支的更新。因此,實際上,git pull = git fetch + git merge 。
(@master)git pull = git fetch & git merge origin/master
案例
你在 f/table 分支開發功能,如今須要合併最新dev,能夠怎麼作?
剛學 git 時,可能會這麼作:
(@f/table) git checkout dev (@dev) git pull (@dev) git checkout f/table (@f/table) git merge dev
實際上,不須要切到 dev 分支,先更新 dev,則合併。如下命令便可:
(@f/table) git fetch (@f/table) git merge origin/dev
小結:origin/branch 是指向此分支雲端最新提交的引用(最新=最後一次更新),在執行 fetch pull push 指令時自動更新。
可使用 git show 命令查看一個提交的詳細信息,
由於 commitId/HEAD/branch/tag/origin-branch 這些都是指向一個提交,因此 show 命令後面寫任意一個均可以。
另外,還可使用其餘參數控制顯示內容,這裏不展開。
git show commitId/HEAD/branch/tag/origin-branch --format=short
cherry-pick 比較好理解,就是將一個指定提交的修改摘取過來,舉例:
如圖,6 提交是增長一個有用的 helper 類(間接說明,一個 commit 最好功能獨立),但你不想將整個分支合併過來,就可使用 cherry-pick 命令。使用任何一個指向 6 提交的引用均可以。
須要說明的是,cherry-pick 過來的提交,只是內容與以前的提交同樣,他們是兩個不一樣的提交。
案例
作了兩個提交的修改,而後刪掉分支了,過會發現剛纔兩個提交有價值,怎麼找回來?
Step1 使用 git reflog
查看以前的提交歷史,找到須要找回的提交ID。
Step2 使用 cherry-pick 命令將須要的提交摘取出來便可。
如何丟失的提交比較多,除了能夠批量 cherry-pick 以外,根據實際狀況,能夠直接在那些提交的最新提交上,新建一個分支,那些提交在此以前的全部提交,都在新的分支上了。
新建分支(03620f1 指提交號/commit id):
git branch newbranch 03620f1 git checkout -b newbranch 03620f1
若是用一句話理解 rebase 的話,就是:rebase = 一連串自動的 cherry-pick 。
關於 rebase ,須要回答三個問題:
如上圖,假設 dev 上的提交是 1-2-3-4-5,f/table 分支上的提交是 1-2-3-6-7。如今咱們須要合併 dev,一般,會使用 (@f/table)git merge dev
的方式合併。這裏,咱們使用 rebase 來合併 dev 。
首先,rebase 會找到 dev 和 f/table 共同的父提交,即 3 提交。而後以 dev 最新的提交爲基礎,把 f/table 分支上新的提交(這裏就是 6 和 7),逐個 cherry-pick 過來。造成新的 f/table 分支。
注意,整個過程當中,對 dev 分支不會有任何影響,由於你是在 f/table 上進行的操做。全部,rebase 的中文翻譯,變基,就能夠理解爲:變基:用 cherry-pick 的方式,給 f/table 上的新提交,換一個基,將基從以前的 3 換到了 dev 所指的提交 5 上。
當使用 merge 時,提交歷史如右側所示,使用 rebase 的提交歷史以下側所示。
提交歷史更清晰,當分支很是多時,回溯提交與查找問題更容易。
使用 rebase 會修改提交歷史,上面的例子中,6和7提交將不在 f/table 分支上存在,取而代之的是8和9分支,在協做分支上,若是6和7已經存在於遠端倉庫(即別人可能已經基於此有了新的修改),再將6和7移除,將帶來諸多衝突與合併的麻煩。(這是,你 push 時,也須要強推,在協做分支上強推,是很危險的行爲。)
因此:rebase只對本地未推送的commit上或本身的分支上進行。
使用 rebase 的收益:更簡潔清晰易回溯的提交歷史。
使用 rebase 的代價:逐個 cherry-pick ,若是有衝突,須要逐個解衝突,使合併變複雜。
以合併 dev 分支爲例,當工做分支已經作了大量修改(有不少提交,預期有許多衝突),或者以前 merge 過 dev。則建議使用 merge 的方式合併 dev。
rebase 小結:
rebase : 一連串的 cherry-pick。(移花接木)
reset,重置,將當前分支的狀態(這裏指工做區,暫存區,代碼倉庫)重置到指定的狀態。reset 的語法以下圖,第一個參數是重置方式,後面是一個指向提交的引用(能夠是提交ID,分支,tag,HEAD~1等等)。
與 rebase 同樣,reset 只對當前分支和工做區,暫存區的數據有影響,對參數中指定的引用沒有影響。即 (@f/table)git reset --hard dev
這句命令,影響的是 f/table 分支,對 dev 沒有任何影響。
具體來看:
從參數名能夠猜到,這個重置方式比較「強硬」,實際上就是,將當前分支,重置到與指定引用同樣的狀態,丟棄在這以後的提交,以及工做區和暫存區的提交。
未追蹤的文件是不受影響的,PS:git clean 命令會清除掉未追蹤的文件。
案例1
(@f/table)git reset --hard f/table~2 的含義?
當前在 f/table 分支,將其重置到 f/table~2 ,結果就是:丟棄掉 f/table 最新的兩個提交。
案例2
將當前分支重置到遠端最新 dev 的狀態,怎麼作?
(@f/table)git fetch
(@f/table)git reset --hard origin/dev
注意,這裏須要先 fetch 一下遠程倉庫,更新 origin/dev 分支。
理解了 --hard 的含義,--soft 和 --mixed 就很好理解了,這兩個參數,不會丟棄任何內容。
--soft 會將指定提交以後的提交內容,都放到 暫存區,同理,--mixed 會將指定提交以後的提交內容,以及暫存區中的內容,放到工做區。
因此,git reset --mixed HEAD
(能夠簡寫爲 git reset
),實現的效果就是:將暫存區中的內容,回退到工做區。
git reset --hard HEAD
(能夠簡寫爲 git reset --hard
),實現的效果就是:將工做區和暫存區中的所有內容。
案例1 將圖中的 2 3 4合併爲一個提交
案例2 移除誤合併
reset 用於修改錯誤,一般會修改提交歷史,
這在團隊協做分支上是危險且不容許的(如不少倉庫的 master 分支)。
這時可使用 revert 命令。
revert 很好理解,就是新建一個提交,用於撤銷以前的修改。
有個問題,revert 一個 merge 提交會怎麼樣?
如圖,若是執行 (@f/table)git revert 6
會獲得相似這樣的提示:
這時,使用 -m 參數能夠指定保留那邊的提交,可選內容只有 1 和 2 (對於一般的兩兩合併的狀況而言),
1 指代當前分支的那些提交,若是不是很肯定,可使用 git show 命令查看那個合併提交,在前的那個父節點爲 1 。
留兩個思考題:
1 如何在一切皆 commit 的語境下理解 git commit --amend
2 如何在一切皆 commit 的語境下理解 git stash
原文連接: http://www.javashuo.com/article/p-zzlopvae-kq.html
END