Git笔记(一)——[commit, checkout]
其實一直覺得自己是會用Git的,畢竟咱也是用Github的人啊!可是三月份找工作時候的一次面試顛覆了我的看法:
Q: 用過Git嗎?平常怎么用的?
A: 用過的,一般就是add,commit,push嘛
Q: branch用的多嗎?git rebase這命令使用過嗎?
A: 一般都是自己的項目用的,就一個人,沒涉及到這么復雜的使用
Q: 那換個話題吧。。。
這么一搞才發現,其實我只會皮毛啊。。。正好現在新公司是用Git的,我也趁機惡補一下!本文其實就是對圖解Git這篇文章的一個實踐。
三個目錄
談到Git,最先需要明確的就是這三個概念:
- Working Directory:工作目錄,這個可以簡單的理解為你在文件系統里真實看到的文件
- Stage(Index):暫存“目錄”,用git add命令添加的文件就到了這里,即將被commit的文件
- Repository:項目“目錄”,用git commit提交的文件就到了這里
后兩個“目錄”之所以加上引號,是因為其實它們并不是真實存在于文件系統中的目錄,是個抽象的概念!為了方便后面的表述,我們就動手建立一個Git工程,并添加一個test.txt的文件。
| 1234 | mkdir GitNotescd GitNotesgit inittouch test.txt |
為了更直觀的觀看,后續將采用SourceTree這個軟件來講解。將這個目錄添加到SourceTree中可以看到:
從圖1中我們可以直觀的看到三個目錄,其中區域1即為Repository目錄,默認顯示的是所有commit的log記錄,由于我們還沒有進行commit,所以此時看到的就是ncommitted changes。區域2的標題為Staged files,就是Stage目錄,目前我們沒有git add,所以這里是空的。區域3就是Working目錄,與文件系統對應的,我們新建的文件test.txt就在這里,可以看到前面有個問號,表示Not Tracked,就是說這個文件從來沒有被add過。
有了這些基本概念,我們來做第一次commit吧,看看文件是怎么在這三個目錄下轉移的吧。運行git add test.txt,觀察SourceTree的面板,發現文件test.txt已經來到了區域2,如圖2。運行git commit -m "add test file",文件已經到達了區域1。點擊區域1的commit message,即可看到文件。標簽master表示我們當前處于master分支,最新的commit為“add test file”,這個commit可以用cde6c09來唯一的標識。
commit - 我會好幾種姿勢呢
前面的第一次提交用的是比較常規的姿勢,其實提交代碼還有好多種姿勢哦!我們對test.txt文件做一下簡單的修改,加一行“test 1”,然后運行git commit -am "commit 1",通過加-a參數,和先運行git add .再commit的效果是一樣的,也就是文件直接從Working目錄到了Repo里。換個姿勢再來一次,給文件加一行“test 2”,然后運行git commit test.txt -m "commit 2",這次文件也是從Working直接到Repo去了。我們還可以再換個姿勢,給文件再添加一行“test 2.5”,運行git commit --amend -am "commit 2, 2.5",觀察SourceTree,發現提交最后的一次提交記錄被修改了,并且包含了最近的兩次更改,如圖3所示。
總結一下commit的幾種姿勢:
關于commit還有幾點想說的:
- git commit -a和git commit file這兩個命令對Untracked的文件是無效了,也就是說只對add過的文件的更改才有效。比如我們新建一個文件touch test1.txt,然后運行git commit -am "add test1"或git commit test1.txt -m "add test1"都是無效的,如圖4所示。
- 盡量不要使用-m標簽,-m標簽只適用于單行的提交信息,而提交信息最好越詳細越好,方便別人,更方便自己。舉個例子,給test.txt添加一行“test 3”,運行git add test.txt,再運行git commit,這時會打開Git中默認的編輯器(一般是vim),推薦像圖5這樣添加commit信息。其中第一行是簡短的信息,第三行是詳細的解釋,標準就是第一行一目了然,第三行越詳細越好。這樣做的另一個好處是,Github默認是支持這種書寫方式的,在Github的pull request里,默認顯示第一行,第三行被折疊,非常方便。并且如果你的pull request只包含這一個commit的話,Github會默認將第一行作為標題,第三行作為內容,如圖6。
- 盡量也不要使用git add .或git commit -a,這兩條命令都會將當前所有的更改進行Stage或commit,這表面看來沒什么大問題,其實是很危險的,有的時候會將未注意的更改錯誤的提交。Git的最佳實踐還是強調小步提交,也就是說提交頻繁一點,每次提交包含的更改少一點,這樣不僅方便跟蹤,更能避免多人合作時產生沖突。使用SourceTree這樣的工具可以做到行級別的提交,也就是說一個文件我修改了好多行,可以把這些更改放到不同的commit里去。比如,修改test.txt添加兩行“test 4”和“test 5”,然后打開SourceTree觀察,如圖7所示,點擊一行后,右上角的“Stage lines”圖標就會出現,這個圖標的作用就是將這一行更改進行add。用這種方法,我們可以把這兩行作兩次提交。最終結果如圖8所示。
checkout - 上得了廳堂,下得了廚房
喲喲切克鬧,煎餅果子來一套!說到了吃,git checkout可以說是身兼多職——上得了廳堂,下得了廚房。一個是分支相關的操作,另一個是可以恢復文件到之前的某個狀態。
平常最常用的功能就是創建和切換分支了。運行git checkout -b branch1新建一個名為“branch1”的分支并切換過去。給test.txt再添加一行“test 5.5”,做一次commit,git commit test.txt -m "commit 5.5"。這時的狀態如下圖:
可以看到當前的分支上有個小對號,當前所處的commit前面的圓點是白色的。此時運行git checkout master就會切回master分支。“master”這個位置不僅可以放分支的名稱,還可以放commit的hash。比如git checkout cc59b55(commit 2,2.5所對應的hash),這個時候Git會給出提示信息,如下圖:
此時Git處在Detached HEAD狀態,從SourceTree里也可以看出有一個HEAD的tag指向對應的commit。HEAD可以理解為時一個指針,指向當前所在的分支當前的commit。其實這個時候Git處在一個“游離的匿名分支”上,Git提示說你可以做修改,做提交,但一旦你checkout到別的地方,這些提交將無法再引用到,如果你想保存,必須在此基礎上創建一個新的分支。什么意思呢?我們跟著提示一步一步做。首先給test.txt文件(此時文件只有三行,最后一行是test 2.5)再添加一行“test aaa”并提交git commit test.txt -m "commit aaa"。這時,看一下SourceTree的狀態圖:
按照Git給的提示,此時我們checkout到別的地方去:git checkout master切回master分支,再去SourceTree看一眼,我擦淚,剛才的修改丟了!!如下圖:
不聽“提示”言,吃虧在眼前,難道真的像提示說的那樣,剛才的修改再也找不到了嗎?非也,只要記得commit的hash,我們是可以切回去,從圖11中找到commit aaa所對應的hash,運行git checkout 6382c7d,再看看SourceTree,OK,都回來了!“游離的匿名分支”的取名就來源于此,這些commit目前不屬于任何分支,不能通過切分支的方式找到他們,只能記住hash才能切回來。為了保存這些提交,我們按照提示新建一個分支:git checkout -b branch2。這樣以后就可以通過git checkout branch2切到該分支找到這些提交了。
除了分支相關的操作,checkout的另一個作用就是恢復文件了。既然說到恢復,那肯定有source(從哪里恢復)和target(恢復到哪里去),個人感覺一般checkout的target就是指你的工作目錄,而source自然是其他兩個目錄了。也就是說,可以從暫存目錄往工作目錄里恢復文件,也可以從Repo里的各個commit記錄里往工作目錄恢復文件。為了方便講解,我們先切回master分支:git checkout master,然后給test.txt加一行“test temp”,這個時候可以在SourceTree里看到存在Uncommited changes,運行git checkout -- test.txt,可以看到剛才的更改被取消了,也就是說我們從Repo的最新commit里將test.txt恢復到了我們的工作目錄里。這里的--符號主要是為了避免歧義,其實這里我們不要--,直接運行git checkout test.txt也是可以的,但試想這么一種情況:我們的文件名稱與分支名稱一樣,比如有一個叫做“master”的文件,如果不加--則Git會認為你想切換分支,所以必須使用--來告訴Git你想恢復文件而不是切換分支,多才多藝的人就是這樣鬧心啊!我們重復同樣的操作,給test.txt添加一行“test temp”并做add操作:git add test.txt,這樣文件就到了暫存目錄。這時候運行git checkout test.txt是沒有作用的,因為你的工作目錄和暫存目錄是一樣,繼續更改test.txt,添加一行”test temp1”并保存,這里運行git checkout test.txt,則會發現新加的這一行temp1沒有了,也就是暫存目錄被恢復到工作目錄了。實踐了“Repo最新commit => 工作目錄”和“暫存目錄 => 工作目錄”,下面試試歷史commit作為source吧。為了方便,將當前暫存區的更改(“test temp”)進行提交:git commit -m "commit temp",此時的commit記錄如下圖:
這里我們試著將某個歷史commit的test.txt恢復回來,比如將commit 3狀態下對應的文件恢復,則找到對應的commit hash,運行git checkout 9fc9896 test.txt,會發現當前工作目錄最后一行已經是“test 3”了,與commit 3時的文件狀態一致,并且這個更改(最新的commit與commit “test 3”的差別)已經自動被add到暫存區域,如下圖:
運行git reset HEAD test.txt(git add的反操作,后面會講)和git checkout test.txt(最新的commit恢復到工作目錄)來使test.txt恢復到“test temp”的狀態。除了hash,還可以使用一些“快捷方式”來引用各個commit,比如剛才的操作用git checkout HEAD~3 test.txt也是一樣的,其中HEAD~3表示比當前的commit早3個commit(爺爺的爸爸。。。)。說到快捷方式,其實分支名稱也可以理解成一個快捷方式,代表所在分支的最新commit。比如運行git checkout branch2 test.txt,同樣可以看到branch2的最新commit的狀態被恢復到工作目錄并添加到了暫存區。
總結一下checkout的幾個功能:
- 暫存區有內容且暫存區內容與工作目錄不同,則恢復暫存區的狀態到工作目錄
- 暫存區無內容,則恢復HEAD(最新的commit)的狀態到工作目錄
P.S. 本來準備把所有命令寫在一篇里,不過感覺篇幅太長了也不好,這篇就到這里吧,剩下的下篇再寫。
from:?http://pinkyjie.com/2014/08/02/git-notes-part-1/
總結
以上是生活随笔為你收集整理的Git笔记(一)——[commit, checkout]的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Git笔记(二)——[diff, res
- 下一篇: 图解Git