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