Git笔记(二)——[diff, reset]
書(shū)接上回,直入主題!如果你是接著上篇來(lái)的,那么先運(yùn)行g(shù)it reset HEAD test.txt和git checkout test.txt來(lái)放棄當(dāng)前的更改,使最新的commit回到“commit temp”,這個(gè)時(shí)候運(yùn)行g(shù)it status,會(huì)看到“nothing to commit, working directory clean”。這里,“nothing to commit”說(shuō)明暫存目錄是空的,“working directory clean”說(shuō)明你的工作目錄也沒(méi)有任何修改。
回到這種狀態(tài)是為了方便我們下面的講解,此時(shí)的SourceTree狀態(tài)為:
diff - 來(lái),叔叔給你檢查身體
好好的一個(gè)diff命令能讓我想到的就是這句猥瑣的經(jīng)典臺(tái)詞了,diff可以讓你比較項(xiàng)目中任意兩個(gè)狀態(tài)的差別。說(shuō)到比較,自然就又有source和target了,那么diff命令最直觀的用法其實(shí)就是git diff source target。這里的source和target與checkout中的類似,可以是“commit的hash”,“分支名”,“快捷方式”。比如,我們想比較圖13中前兩個(gè)commit,運(yùn)行g(shù)it diff ce81811 6382c7d即可,得到的結(jié)果如下圖:
可以看到,比較的結(jié)果其實(shí)是以target為基準(zhǔn)的,也就是說(shuō)target相比于source有了哪些變化,圖15的結(jié)果中commit “6382c7d”比“ce81811”少了三行,多了一行,分別用減號(hào)和加號(hào)來(lái)表示。同樣,使用git diff master branch2和git diff HEAD branch2得到的結(jié)果與上面是一致的,分支名和“HEAD”一類的都可以看做是commit的快捷方式。
這種source和target都給的情況是最容易理解的,復(fù)雜就復(fù)雜在如果我們省略一個(gè)參數(shù)會(huì)怎么樣呢?比如運(yùn)行g(shù)it diff branch2結(jié)果如下圖所示:
可以看到,圖16的結(jié)果與圖15是相反的。也就是說(shuō)git diff branch2與git diff branch2 HEAD的結(jié)果是一樣的,即如果只給一個(gè)參數(shù),則這個(gè)參數(shù)為source,target默認(rèn)為當(dāng)前所在分支的最新的commit?,F(xiàn)在就下這個(gè)結(jié)論對(duì)嗎?注意我們現(xiàn)在處在“暫存目錄為空”+“工作目錄clean”的狀況下,現(xiàn)在我們把工作目錄搞成dirty試試,給test.txt再加一行“test 6”并保存。這時(shí)再試試git diff branch2,結(jié)果如圖17所示:
可以看到新建的“test 6”也進(jìn)去了??梢詳喽?#xff0c;在工作目錄不clean的情況下,target默認(rèn)表示的是工作目錄。這個(gè)結(jié)論是否還是為時(shí)尚早呢,如果暫存目錄有東西會(huì)怎么樣?運(yùn)行g(shù)it add test.txt,然后繼續(xù)git diff branch2,發(fā)現(xiàn)結(jié)果與圖17是一致的,這還是不能說(shuō)明問(wèn)題,因?yàn)榇藭r(shí)工作目錄與暫存目錄是一致的(都到test 6)。那么我們?cè)偌右恍小皌est 7”,這時(shí)工作目錄為“test 7”,暫存目錄還是“test 6”,此時(shí)運(yùn)行g(shù)it diff branch2,發(fā)現(xiàn)“test 7”這一行也被加了進(jìn)去。這個(gè)時(shí)候我們基本可以斷定,target默認(rèn)顯示的確實(shí)是工作目錄。
前面看了省略一個(gè)參數(shù)的情況,那倆參數(shù)都省略會(huì)咋樣呢?運(yùn)行g(shù)it diff結(jié)果如下圖:
可以看到比較結(jié)果為只增加了“test 7”,所以這個(gè)時(shí)候的source是暫存目錄,而target還是工作目錄。為了驗(yàn)證這個(gè)推測(cè),我們運(yùn)行g(shù)it add test.txt,將“test 7”的修改也add到暫存目錄,這時(shí)運(yùn)行g(shù)it diff,返回結(jié)果為空,因?yàn)榇藭r(shí)暫存目錄和工作目錄是一致。我們做一次提交git commit -m "commit 6,7",這時(shí),暫存目錄為空,工作目錄clean,繼續(xù)運(yùn)行g(shù)it diff,還是空的。到這里我們可以斷定,如果兩個(gè)參數(shù)都省略,那么默認(rèn)source為暫存目錄,默認(rèn)target為工作目錄。
前面的情況涉及到“各個(gè)commit之間的比較”,“各個(gè)commit與工作目錄的比較”,“暫存目錄與工作目錄的比較”,那么只差一種情況了,我想比較“暫存目錄”和“各個(gè)commit”怎么整呢?為了實(shí)現(xiàn)這個(gè),我們先給暫存目錄來(lái)點(diǎn)東西:加一行“test 8”并保存,然后git add test.txt,然后在編輯test.txt加一行“test 9”,這么做的原因是讓暫存區(qū)有東西而且暫存區(qū)與工作目錄不同。這時(shí)運(yùn)行g(shù)it diff --cached branch2,可以發(fā)現(xiàn)結(jié)果為下圖:
從圖中可以發(fā)現(xiàn)“test 8”在而“test 9”不在,說(shuō)明此時(shí)的target已經(jīng)變成暫存區(qū)了。
總結(jié)一下diff的各種情況:
在繼續(xù)往下走之前,先將剛才的更改全部提交,運(yùn)行g(shù)it add test.txt和git commit -m "commit 8,9"。
reset - 有了我你隨便咋折騰都行
版本控制最大的好處就是可以方便的找到以前的版本并恢復(fù),所以從這個(gè)角度來(lái)說(shuō)reset命令的地位還是比較重要的,可以讓你無(wú)所顧忌的隨便蹂躪整個(gè)項(xiàng)目。說(shuō)到恢復(fù),也有source和target的概念,這里的source肯定就是各個(gè)commit(包含分支名和快捷方式),而target根據(jù)不同的參數(shù)可能是暫存目錄或工作目錄或兩者同時(shí)都是target。比如我們選定當(dāng)前commit的父commit作為source,運(yùn)行g(shù)it reset HEAD~ test.txt,提示有Unstaged change,此時(shí)SourceTree里的Uncommitted changes的狀態(tài)如下圖:
從圖20中可以發(fā)現(xiàn),暫存區(qū)域的文件狀態(tài)與父commit時(shí)一致,而改變是減掉了“test 8”和“test 9”兩行,說(shuō)明工作目錄并沒(méi)有發(fā)生變化(工作目錄含有這兩行)??梢钥吹竭@種情況下的target其實(shí)是暫存目錄,它并沒(méi)有改變工作目錄。說(shuō)到這里,把前面欠的課補(bǔ)上,還記得前面我們做git add的反操作時(shí)用了git reset HEAD test.txt,其實(shí)也是將HEAD狀態(tài)的文件恢復(fù)到了暫存區(qū),工作目錄保持不變,而那時(shí)最新commit的文件狀態(tài)和工作目錄是一致的,所以最終產(chǎn)生的效果就是“git add反操作”。其實(shí)這里的HEAD也可以省略,因?yàn)槟J(rèn)的source就是當(dāng)前所在分支的最新commit。更進(jìn)一步,文件名test.txt也可以省略,默認(rèn)會(huì)將Repo里的所有文件恢復(fù),因?yàn)榇藭r(shí)我們就只有這一個(gè)文件,所以效果是一樣的。
再介紹reset的其他參數(shù)之前,我們想把剛才的reset再給reset掉,很簡(jiǎn)單,只要再運(yùn)行一遍git reset即可,因?yàn)槲覀冃枰钠鋵?shí)是“git add的反操作”。然后加參數(shù)運(yùn)行reset,git reset --soft HEAD~,注意這里我們并不是省略文件名,而是一旦加了--soft就不能跟文件路徑,而是恢復(fù)整個(gè)項(xiàng)目的所有文件了,結(jié)果如下圖所示:
可以看到,這次reset直接改變了HEAD,原先的“commit 8,9”消失了,最新的commit變成了原先的HEAD~,但這次reset仍然沒(méi)有修改工作目錄,只是將“commit 8,9”的文件狀態(tài)add到了暫存區(qū)。既然有--soft參數(shù),那肯定會(huì)有--hard參數(shù),這次我們保持當(dāng)前的狀態(tài),直接運(yùn)行g(shù)it reset --hard ce81811(commit temp所對(duì)應(yīng)的hash),發(fā)現(xiàn)當(dāng)前最新的commit變成“commit temp”,并且暫存區(qū)域是空的,然后工作目錄也是clean的,說(shuō)明--hard參數(shù)不管運(yùn)行命令前處于什么狀態(tài),都直接將工作目錄恢復(fù)到“commit temp”的狀態(tài),清空暫存區(qū)域。這也比較符合--hard這個(gè)單詞強(qiáng)硬的意思。此時(shí)的SourceTree狀態(tài)圖為:
從圖中可以看出,“commit temp”之前的commit都已經(jīng)丟失了,整個(gè)項(xiàng)目被強(qiáng)制恢復(fù)到了“commit temp”所在的狀態(tài)。
總結(jié)一下reset的用法:
關(guān)于reset命令的其他補(bǔ)充:當(dāng)前HEAD已經(jīng)位于“commit temp”,是不是前面的commit都找不回來(lái)了?當(dāng)然不會(huì),reset過(guò)的操作也是可以被reset的。有兩種方法:
- 如果記得“commit 8,9”的hash(從圖20中可以看到),則直接git reset --hard 1a222c3,則項(xiàng)目直接強(qiáng)制恢復(fù)到“commit 8,9”所在的狀態(tài)。
- 如果不記得的話,運(yùn)行g(shù)it reflog,這個(gè)命令會(huì)輸出一個(gè)列表,包含HEAD發(fā)生的所有變化。如下圖:
在圖23中可以發(fā)現(xiàn)“commit 8,9”所對(duì)應(yīng)的條目為1a222c3 HEAD@{9}: commit: commit 8,9,第一項(xiàng)就是commit hash,第二項(xiàng)自然是快捷方式了。那么只要我們運(yùn)行g(shù)it reset --hard HEAD@{9}即可。注意,我的reflog輸出結(jié)果可能與你的不同,因?yàn)閷?xiě)教程的需要我可能做了很多額外的操作。
P.S. 顯然兩篇也不夠啊,發(fā)現(xiàn)主要是Retina屏的截圖太尼瑪大了。。。下篇再講剩下的吧!
from:?http://pinkyjie.com/2014/08/09/git-notes-part-2/
總結(jié)
以上是生活随笔為你收集整理的Git笔记(二)——[diff, reset]的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: Git笔记(三)——[cherry-pi
- 下一篇: Git笔记(一)——[commit, c