很早就想些一篇關(guān)于git的文章了,這玩意兒實在好用,但是內(nèi)容又比較多,這里我講解最基本使用技巧,這個足以應(yīng)對99%以上的場景,剩下那些真的要用到就去看官網(wǎng)手冊。
Git是目前世界上最先進的分布式版本控制系統(tǒng)(沒有之一),它的誕生也是個很有趣的故事。大家都知道Git是Linus大神寫的,據(jù)說剛開始的時候,linux內(nèi)核源碼使用BitKeeper這個商業(yè)版本控制系統(tǒng),BitKeeper授權(quán)Linux社區(qū)免費使用,但是某一天開發(fā)Samba的Andrew這個家伙試圖破解BitKeeper協(xié)議,東窗事發(fā)。于是BitKeeper公司一怒之下收回了免費使用權(quán)。Linus大神是不可能去道歉的,于是他就花了2個星期用C語言寫了Git,一個月內(nèi),Linux源碼就由Git管理了,無敵是多么寂寞 →_→
相比較像svn這樣的集中式版本管理,分布式版本管理優(yōu)勢在哪里呢?這里先說兩個,后面再說另外幾個殺手級優(yōu)點。
首先,分布式所有客戶機都有一個完整拷貝,所以不用擔心服務(wù)器掛點。另外分布式不需要聯(lián)網(wǎng)就可以工作,沒有中央服務(wù)器。
默認的yum源中都是舊的1.8版本,使用下面方法安裝最新的git2版本:
1 | sudo yum remove git |
如果在windows上面,就去官網(wǎng)下載安裝文件,點擊安裝即可。
安裝完成,還要簡單配置下全局設(shè)置:
1 | git config --global user.name "Your Name" |
1 | mkdir gitdemo && cd gitdemo |
當前目錄下多了一個.git的目錄,這個目錄是Git來跟蹤管理版本庫的,沒事千萬不要手動修改這個目錄里面的文件,不然改亂了,就把Git倉庫給破壞了。
如果你有一些文件不需要版本跟蹤就寫到這里面,比如:
1 | build/ |
接受通配符,具體規(guī)則請參考gitignore說明
先編寫一個readme.txt文件,內(nèi)容如下:
1 | hello git |
第一步,使用git add
命令添加read.txt到git:
1 | git add readme.txt |
執(zhí)行上面的命令,沒有任何顯示,這就對了
第二步,使用git commit
命令提交到git倉庫:
1 | git commit -m "readme file" |
輸出:
1 | [master (root-commit) 50f5fdc] readme file |
剛剛提交完后,繼續(xù)編輯readme.txt,內(nèi)容如下:
1 | hello git |
現(xiàn)在,運行git status
命令看看結(jié)果:
1 | [root@controller161 gitdemo]# git status |
git status
命令可以讓我們時刻掌握倉庫當前的狀態(tài),上面的命令告訴我們,readme.txt被修改過了,但還沒有準備提交的修改。
雖然Git告訴我們readme.txt被修改了,但如果能看看具體修改了什么內(nèi)容,自然是很好的,這時候使用git diff
命令:
1 | [root@controller161 gitdemo]# git diff readme.txt |
知道了對readme.txt作了什么修改后,再把它提交到倉庫就放心多了,步驟還是先add,再commit:
1 | git add readme.txt |
執(zhí)行add之后,我們先不提交,看看狀態(tài):
1 | [root@controller161 gitdemo]# git status |
git status
告訴我們,將要被提交的修改包括readme.txt,下一步,就可以放心地提交了:
1 | [root@controller161 gitdemo]# git commit -m "modify readme" |
提交后,我們再用git status命令看看倉庫的當前狀態(tài):
1 | [root@controller161 gitdemo]# git status |
沒有需要提交的修改,而且,工作目錄是干凈(working directory clean)的
現(xiàn)在再修改readme.txt文件如下:
1 | hello git |
然后再提交:
1 | [root@controller161 gitdemo]# git add readme.txt |
像這樣,你不斷對文件進行修改,然后不斷提交修改到版本庫里,實際上這個commit操作就相對于一個快照。以后你誤改誤刪了文件是可以回退的。
我們可以通過git log
命令查看提交歷史:
1 | [root@controller161 gitdemo]# git log --pretty=oneline |
第一列是commit的一個id號(版本號),是SHA1計算出來的一個非常大的數(shù)字,用十六進制表示。
或者你想通過 ASCII 藝術(shù)的樹形結(jié)構(gòu)來展示所有的分支, 每個分支都標示了他的名字和標簽:
1 | git log --graph --oneline --decorate --all |
看看哪些文件改變了:
1 | git log --name-status |
假如你想回退到modify readme
那個版本,可以通過git reset
命令:
1 | [root@controller161 gitdemo]# git reset --hard 1d57c05ee44 |
reset后面指定版本號,你可以只取前面幾位,只要能區(qū)分就行。我們再來看readme.txt:
1 | [root@controller161 gitdemo]# cat readme.txt |
確實回退到那個提交版本了。
現(xiàn)在,你回退到了某個版本,關(guān)掉了電腦,第二天早上就后悔了,想恢復(fù)到新版本怎么辦?找不到新版本的commit id怎么辦?
在Git中,總是有后悔藥可以吃的?;赝吮仨氈付╟ommit id,而Git提供了一個命令git reflog用來記錄你的每一次命令:
1 | [root@controller161 gitdemo]# git reflog |
在使用Git作為版本控制的時候,我們可能會由于各種各樣的原因提交了許多臨時的 commit,而這些 commit 拼接起來才是完整的任務(wù)。那么我們?yōu)榱吮苊馓嗟?commit 而造成版本控制的混亂,通常我們推薦將這些 commit 合并成一個
首先假設(shè)我們有如下幾個 commit:
1 | [root@controller161 gitdemo]# git log --pretty=oneline --abbrev-commit |
我想將最近3個(ad6fa66、3c18f63、10c9f30)合并為1個提交歷史,怎樣做呢?
1 | git rebase -i 796e584 |
注意這個796e584是指的合并提交的前一個,不參與合并。出現(xiàn)下面的編輯界面:
1 | pick 3c18f63 modify README again |
很奇怪的是第一條commit老不顯示,應(yīng)該是解決沖突的commit根本沒辦法合并原因。
可以看到其中分為兩個部分,上方未注釋的部分是填寫要執(zhí)行的指令,而下方注釋的部分則是指令的提示說明。指令部分中由前方的命令名稱、commit hash 和 commit message 組成。
當前我們只要知道 pick 和 squash 這兩個命令即可。
我們將 10c9f30 這個 commit 前方的命令改成 squash 或 s,然后輸入:wq以保存并退出。
Git會壓縮提交歷史,如果有沖突,需要修改,修改的時候要注意,保留最新的歷史,不然我們的修改就丟棄了。修改以后要記得敲下面的命令:
1 | git add . |
如果你想放棄這次壓縮的話,執(zhí)行以下命令:
1 | git rebase --abort |
如果沒有沖突,或者沖突已經(jīng)解決,則會出現(xiàn)如下的編輯窗口,出現(xiàn)下面的界面:
1 | # This is a combination of 2 commits. |
然后修改成下面commit說明的:
1 | modify README again , modify readme by xiao ming |
輸入wq保存并退出, 再次輸入git log查看 commit 歷史信息,你會發(fā)現(xiàn)這3個 commit 已經(jīng)合并了。
1 | [root@controller161 gitdemo]# git log --pretty=oneline --abbrev-commit |
很奇怪,之前這個ad6fa66 ok , I resolve confict
也被合并了,好神秘哦。有明白為什么的同學(xué)告我下。
rebase還有一種用法,就是當兩個分支產(chǎn)生分叉,比如master和dev,最后你想讓dev分支歷史看起來像沒有經(jīng)過任何合并一樣,你也許可以用 git rebase:
1 | $ git checkout dev |
這些命令會把你的dev分支里的每個提交(commit)取消掉,并且把它們臨時保存為補丁(patch)(這些補丁放到”.git/rebase”目錄中),然后把dev分支更新到最新的master分支,最后把保存的這些補丁應(yīng)用到master分支上。
當dev分支更新之后,它會指向這些新創(chuàng)建的提交(commit),而那些老的提交會被丟棄。如果運行垃圾收集命令(pruning garbage collection), 這些被丟棄的提交就會刪除
同樣和合并提交一樣,在rebase的過程中,也許會出現(xiàn)沖突(conflict),在這種情況,Git會停止rebase并會讓你去解決沖突;在解決完沖突后,用git add
命令去更新這些內(nèi)容的索引(index), 然后,你無需執(zhí)行git commit
,只要執(zhí)行:
1 | $ git rebase --continue |
這樣git會繼續(xù)應(yīng)用(apply)余下的補丁,同樣,在任何時候,你可以用–abort參數(shù)來終止rebase的行動,并且dev分支會回到rebase開始前的狀態(tài):
1 | $ git rebase --abort |
在git里面有三個很重要的概念:工作區(qū)、暫存區(qū)、提交歷史。
就是你在電腦里能看到的目錄,比如我的gitdemo文件夾就是一個工作區(qū)
一般存放在 “.git目錄下” 下的index文件(.git/index)中,所以我們把暫存區(qū)有時也叫作索引(index)。實際上指向暫存區(qū)的指針名就是index。
隱藏目錄.git
其實是Git的版本庫,也叫分支的提交歷史。Git為我們自動創(chuàng)建的第一個分支master,以及指向master的一個指針叫HEAD。請注意暫存區(qū)也是放在這個隱藏目錄里面的。
把文件往Git版本庫里添加的時候,是分兩步執(zhí)行的:
git add
把文件添加進去,實際上就是把文件修改添加到暫存區(qū);git commit
提交更改,實際上就是把暫存區(qū)的所有內(nèi)容提交到當前分支。再次修改readme.txt:
1 | hello git |
另外再添加一個文件LICENSE,內(nèi)容如下:
1 | MIT |
先用git status
查看一下狀態(tài):
1 | [root@controller161 gitdemo]# git status |
Git非常清楚地告訴我們,readme.txt被修改了,而LICENSE還從來沒有被添加過,所以它的狀態(tài)是Untracked
現(xiàn)在,使用兩次命令git add
或者git add --all
,把readme.txt和LICENSE都添加后,用git status
再查看一下:
1 | [root@controller161 gitdemo]# git add --all |
現(xiàn)在,暫存區(qū)的狀態(tài)就變成這樣了:
所以,git add
命令實際上就是把要提交的所有修改放到暫存區(qū)(Stage),然后,執(zhí)行git commit
就可以一次性把暫存區(qū)的所有修改提交到分支:
1 | [root@controller161 gitdemo]# git commit -m "show stage work" |
一旦提交后,如果你又沒有對工作區(qū)做任何修改,那么工作區(qū)就是“干凈”的:
1 | [root@controller161 gitdemo]# git status |
現(xiàn)在版本庫變成了這樣,暫存區(qū)就沒有任何內(nèi)容了:
很多時候需要用diff命令來比較文件差異,總結(jié)一下:
git diff readme.txt
-> 工作區(qū) 和 暫存區(qū)比較git diff --cache readme.txt
-> 暫存區(qū) 和 版本庫比較git diff HEAD -- readme.txt
-> 版本庫 和 工作區(qū)比較如果git diff
后面不加文件名readme.txt,表示要顯示所有文件的差異。
有時候你也會犯傻修改了不該改的東西,這樣時候可以通過git checkout
命令撤銷修改。
命令git checkout -- readme.txt
意思就是,把readme.txt文件在工作區(qū)的修改全部撤銷,這里有兩種情況:
總之,就是讓這個文件回到最近一次git commit或git add時的狀態(tài)。
git checkout -- file
命令中的--
很重要,沒有--
,就變成了“切換到另一個分支”的命令,我們在后面的分支管理中會再次遇到git checkout
命令。
還有一種情況是,你想將暫存區(qū)的修改撤銷掉:
1 | git reset HEAD readme.txt |
git reset
命令既可以回退版本,也可以把暫存區(qū)的修改撤銷掉。當我們用HEAD時,表示最新的版本。
注意:這里git reset
并沒有不會對工作區(qū)產(chǎn)生任何影響,只是撤銷了暫存區(qū)的修改。
還記得如何丟棄工作區(qū)的修改嗎?
1 | git checkout -- readme.txt |
整個世界終于清靜了
總結(jié)一下撤銷修改場景:
git checkout -- file
git reset HEAD file
,就回到了場景1,第二步按場景1操作。git reset --mixed 版本庫ID
1 | git reset --soft/mixed/hard <commit_id> |
重要的事說三遍,最后有必要再次總結(jié)幾個重要的指令:
git reset HEAD
命令時,暫存區(qū)的目錄樹會被重寫,被 master 分支指向的目錄樹所替換,但是工作區(qū)不受影響。git rm --cached <file>
命令時,會直接從暫存區(qū)刪除文件,工作區(qū)則不做出改變。git rm -f <file>
命令時,會直接把暫存區(qū)和工作區(qū)全部刪了。git checkout .
或者 git checkout -- <file>
命令時,會用暫存區(qū)全部或指定的文件替換工作區(qū)的文件。這個操作很危險,會清除工作區(qū)中未添加到暫存區(qū)的改動。git checkout HEAD .
或者 git checkout HEAD <file>
命令時,會用 HEAD 指向的 master 分支中的全部或者部分文件替換暫存區(qū)和工作區(qū)中的文件。這個命令也是極具危險性的,因為不但會清除工作區(qū)中未提交的改動,也會清除暫存區(qū)中未提交的改動。git reset
有三個選項,--hard、--mixed、--soft
。
注意這三個選項對文件層面的git reset
毫無作用,因為緩存區(qū)中的文件一定會變化,而工作目錄中的文件一定不變。
1 | //僅僅只是撤銷已提交的版本庫,不會修改暫存區(qū)和工作區(qū) |
注意這個版本庫ID應(yīng)該不是你剛剛提交的版本庫ID,而是剛剛提交版本庫的上一個版本庫。
聯(lián)系客服