GIT是非常優秀的版本控制工具,但是苦於git那晦澀難懂的man pages,還有衆多的命令選項和怪異的用法,git有點難學。這篇文章分享我學習過程中收藏的一些好圖,並圍繞這些圖講講我對git的理解,希望對大家有所幫助。
GIT工作流程
瞭解git,首先要弄清楚對象在被git管理過程中所處的4個階段,分別是:工作目錄、index(又稱爲暫存區)、本地倉庫和遠程倉庫。從時間先後來講,工作目錄的內容是你當前看到的,也是最新的;index區標記了你當前工作目錄中,哪些內容是被git管理的;而本地倉庫保存了對象被提交過的各個版本,比起工作目錄和暫存區的內容,它要更舊一些;遠程倉庫是本地倉庫的異地備份,遠程倉庫的內容可能被分佈在多個地點的處於協作關係的本地倉庫修改,因此它可能與本地倉庫同步,也可能不同步,但是它的內容是最舊的。任何對象都是在工作目錄中誕生和被修改;任何修改都是從進入index區纔開始被版本控制;只有把修改提交到本地倉庫,該修改才能在倉庫中留下痕跡;而要與協作者分享本地的修改,可以把它們push到遠程倉庫來共享。圖最上方的add、commit、push等,展示了git倉庫的產生過程。反過來,我們可以從遠程歷史倉庫中獲得本地倉庫的最後一個版本,clone到本地,從本地檢出對象的各個版本到index暫存區或工作目錄中,從而實現任何對象或整個倉庫的任意階段狀態的”回滾”。當正向和反向都能自由切換後,git就強大到無所不能了。
一開始接觸這些概念可能比較繞,其實在git入門階段,可以先拋開遠程倉庫不看,只瞭解管理本地倉庫的”3棵樹”就夠了。如下圖:
常用GIT命令
在開始之前,我們需要把下面的圖看懂:
- HEAD,頭,它始終指向當前所處分支的最新的提交點。你所處的分支變化了,或者產生了新的提交點,HEAD就會跟着改變。
- working directory,它是你的工作目錄,也是當前你看到的東西。你的工作目錄是與版本、分支相關的。
- stage的東西雖然看不見,但是執行git status就會看到哪些對象的修改將在下一次commit的時候被放進本地倉庫。這些東西稱爲stage。
commit
commit把暫存區的內容存入到本地倉庫,並使得當前分支的HEAD向後移動一個提交點。如果對最後一次commit不滿意,可以使用git commit --amend
來進行撤銷,修改之後再提交。如圖所示的,ed489被4ca87取代,但是git log裏看不到ed489的影子,這也正是amend的本意:原地修改,讓上一次提交不露痕跡。
checkout
checkout用來檢出並切換分支。checkout成功後,HEAD會指向被檢出分支的最後一次提交點。對應的,工作目錄、暫存區也都會與當前的分支進行匹配。下圖是執行git checkout maint
後的結果:
reset
reset命令把當前分支指向另一個位置,並且相應的變動工作目錄和索引。如下圖,執行git reset HEAD~3
後,當前分支相當於回滾了3個提交點,由ed489回到了b325c:
reset有3種常用的模式:
–soft,只改變提交點,暫存區和工作目錄的內容都不改變
–mixed,改變提交點,同時改變暫存區的內容。這是默認的回滾方式
–hard,暫存區、工作目錄的內容都會被修改到與提交點完全一致的狀態
diff
我們在commit、merge、rebase、打patch之前,通常都需要看看這次提交都幹了些什麼,於是diff命令就派上用場了:
來比較下上圖中5種不同的diff方式:
- 比較不同的提交點之間的異同,用
git diff 提交點1 提交點2
- 比較當前分支與其他分支的異同,用
git diff 其他分支名稱
- 在當前分支內部進行比較,比較最新提交點與當前工作目錄,用
git diff HEAD
- 在當前分支內部進行比較,比較最新提交點與暫存區的內容,用
git diff --cached
- 在當前分支內部進行比較,比較暫存區與當前工作目錄,用
git diff
看起來有點複雜?是的,記不住的時候就看看這些圖吧。
merge
merge命令把不同的分支合併起來。如下圖,HEAD處於master分支的ed489提交點上,other分支處於33104提交點上,項目負責人看了下覺得other分支的代碼寫的不錯,於是想把代碼合併到master分支,因此直接執行git merge other
,如果沒有發生衝突,other就成功合併到master分支了。
rebase
rebase又稱爲衍合,是合併的另外一種選擇。merge把兩個分支合併到一起進行提交,無論從它們公共的父節點開始(如上圖,other分支與master分支公共的父節點b325c),被合併的分支(other分支)發生過多少次提交,合併都只會在當前的分支上產生一次提交日誌,如上圖的f8bc5。所以merge產生的提交日誌不是線性的,萬一某天需要回滾,就只能把merge整體回滾。而rebase可以理解爲verbosely merge,完全重演下圖分支topic的演化過程到master分支上。如下圖:
在開始階段,我們處於topic分支上,執行git rebase master
,那麼169a6和2c33a上發生的事情都在master分支上重演一遍,分別對應於master分支上的e57cf和f7e63,最後checkout切換回到topic分支。這一點與merge是一樣的,合併前後所處的分支並沒有改變。git rebase master
,通俗的解釋就是topic分支想站在master的肩膀上繼續下去。
cherry-pick
cherry-pick命令複製一個提交點所做的工作,把它完整的應用到當前分支的某個提交點上。rebase可以認爲是自動化的線性的cherry-pick。
例如執行git cherry-pick 2c33a
:
正反過程對比
理解了上面最晦澀的幾個命令,我們來從正反兩個方向對比下版本在本地的3個階段之間是如何轉化的。如下圖(history就是本地倉庫):
如果覺得從本地工作目錄到本地歷史庫每次都要經過index暫存區過渡不方便,可以採用圖形右邊的方式,把兩步合併爲一步。
Some Tips
如何管理空文件夾
git本身不會對空文件夾進行版本控制,如果希望項目被clone後自帶一些空目錄,那麼可以:
- 在要被管理的空目錄下創建.gitignore文件。
- 在.gitignore文件內寫入如下代碼。第一行忽略所有文件。第二行除了.gitignore文件不被忽略。
*
!.gitignore
用git實現遠程備份
git本地倉庫的初始化採用git init .
即可。如果需要搭建異地備份,除了可以使用開源的gitlab等來搭建服務器外,還可以git --bare init .
來初始化遠服務器倉庫,然後把本地倉庫推送到服務器倉庫。這樣可以輕鬆實現代碼或文檔的異地備份,即使把代碼備份到本地也是可以的(這或許可以避免rm -rf *的悲劇哦):git remote add origin git://127.0.0.1/abc.git
然後建立本地到本地的ssh信任關係,enjoy yourself!
無密碼登陸git服務器
調試技巧:ssh -v [email protected]
GIT與知識管理
日常工作中,我習慣用git來管理github和本地的代碼;也用git來管理各種配置文件,例如.vimrc,.bashrc等,以及各種技術文檔。我的博客用vim+markdown來編寫,也用git來做版本控制,很方便。感謝linus大神爲我們寫了這麼好的工具!
歡迎批評指正!
注:本文的圖片絕大部分來自於visual-git-guide,這也同時是一篇非常好的git教程。