很早就想些一篇關(guān)于git的文章了,這玩意兒實(shí)在好用,但是內(nèi)容又比較多,
這里我講解最基本使用技巧,這個足以應(yīng)對99%以上的場景,剩下那些真的要用到就去看官網(wǎng)手冊。
Git是目前世界上最先進(jìn)的分布式版本控制系統(tǒng)(沒有之一),它的誕生也是個很有趣的故事。
大家都知道Git是Linus大神寫的,據(jù)說剛開始的時候,linux內(nèi)核源碼使用BitKeeper這個商業(yè)版本控制系統(tǒng),
BitKeeper授權(quán)Linux社區(qū)免費(fèi)使用,但是某一天開發(fā)Samba的Andrew這個家伙試圖破解BitKeeper協(xié)議,東窗事發(fā)。
于是BitKeeper公司一怒之下收回了免費(fèi)使用權(quán)。Linus大神是不可能去道歉的,于是他就花了2個星期用C語言寫了Git,
一個月內(nèi),Linux源碼就由Git管理了,無敵是多么寂寞 →_→
相比較像svn這樣的集中式版本管理,分布式版本管理優(yōu)勢在哪里呢?
這里先說兩個,后面再說另外幾個殺手級優(yōu)點(diǎn)。
首先,分布式所有客戶機(jī)都有一個完整拷貝,所以不用擔(dān)心服務(wù)器掛點(diǎn)。
另外分布式不需要聯(lián)網(wǎng)就可以工作,沒有中央服務(wù)器。
安裝git默認(rèn)的yum源中都是舊的1.8版本,使用下面方法安裝最新的git2版本:
1 2 3 4 sudo yum remove git sudo yum install epel-release sudo yum install https://centos7./ius-release.rpm sudo yum install git2u
如果在windows上面,就去官網(wǎng)下載安裝文件,點(diǎn)擊安裝即可。
安裝完成,還要簡單配置下全局設(shè)置:
1 2 3 4 5 6 git config --global user.name "Your Name" git config --global user.email "email@example.com" # 彩色的 git 輸出: git config --global color.ui true # 顯示歷史記錄時,每個提交的信息只顯示一行: git config --global format.pretty oneline
初始化 初始化倉庫:1 2 3 mkdir gitdemo && cd gitdemo git init # Initialized empty Git repository in /root/gitdemo/.git/
當(dāng)前目錄下多了一個.git的目錄,這個目錄是Git來跟蹤管理版本庫的,
沒事千萬不要手動修改這個目錄里面的文件,不然改亂了,就把Git倉庫給破壞了。
.gitignore文件如果你有一些文件不需要版本跟蹤就寫到這里面,比如:
接受通配符,具體規(guī)則請參考gitignore說明
添加文件到版本庫先編寫一個readme.txt文件,內(nèi)容如下:
1 2 hello git I like git very much
第一步,使用git add命令添加read.txt到git:
執(zhí)行上面的命令,沒有任何顯示,這就對了
第二步,使用git commit命令提交到git倉庫:
1 git commit -m "readme file"
輸出:
1 2 3 [master (root-commit) 50f5fdc] readme file 1 file changed, 2 insertions(+) create mode 100644 readme.txt
回退和撤銷剛剛提交完后,繼續(xù)編輯readme.txt,內(nèi)容如下:
1 2 3 hello git I like git very much add something
現(xiàn)在,運(yùn)行git status命令看看結(jié)果:
1 2 3 4 5 6 7 8 9 [root@controller161 gitdemo]# git status On branch master Changes not staged for commit: (use "git add <file>..." to update what will be committed) (use "git checkout -- <file>..." to discard changes in working directory) modified: readme.txt no changes added to commit (use "git add" and/or "git commit -a")
git status命令可以讓我們時刻掌握倉庫當(dāng)前的狀態(tài),上面的命令告訴我們,readme.txt被修改過了,但還沒有準(zhǔn)備提交的修改。
雖然Git告訴我們readme.txt被修改了,但如果能看看具體修改了什么內(nèi)容,自然是很好的,這時候使用git diff命令:
1 2 3 4 5 6 7 8 9 [root@controller161 gitdemo]# git diff readme.txt diff --git a/readme.txt b/readme.txt index 2236b09..4114c7f 100644 --- a/readme.txt +++ b/readme.txt @@ -1,2 +1,3 @@ hello git I like git very much +add something
知道了對readme.txt作了什么修改后,再把它提交到倉庫就放心多了,步驟還是先add,再commit:
執(zhí)行add之后,我們先不提交,看看狀態(tài):
1 2 3 4 5 6 [root@controller161 gitdemo]# git status On branch master Changes to be committed: (use "git reset HEAD <file>..." to unstage) modified: readme.txt
git status告訴我們,將要被提交的修改包括readme.txt,下一步,就可以放心地提交了:
1 2 3 [root@controller161 gitdemo]# git commit -m "modify readme" [master 1d57c05] modify readme 1 file changed, 1 insertion(+)
提交后,我們再用git status命令看看倉庫的當(dāng)前狀態(tài):
1 2 3 [root@controller161 gitdemo]# git status On branch master nothing to commit, working directory clean
沒有需要提交的修改,而且,工作目錄是干凈(working directory clean)的
版本回退現(xiàn)在再修改readme.txt文件如下:
1 2 3 4 hello git I like git very much add something 我又加了點(diǎn)東西在后面
然后再提交:
1 2 3 4 [root@controller161 gitdemo]# git add readme.txt [root@controller161 gitdemo]# git commit -m "添加中文行" [master 90507c6] 添加中文行 1 file changed, 1 insertion(+)
像這樣,你不斷對文件進(jìn)行修改,然后不斷提交修改到版本庫里,實(shí)際上這個commit操作就相對于一個快照。
以后你誤改誤刪了文件是可以回退的。
我們可以通過git log命令查看提交歷史:
1 2 3 4 [root@controller161 gitdemo]# git log --pretty=oneline 90507c6859f8af73a651f033de8ea901811cb4e8 添加中文行 1d57c05ee44d590f279050276884551e77d2ffb1 modify readme 50f5fdcea453fed2eee690b5e686994040ffe210 readme file
第一列是commit的一個id號(版本號),是SHA1計(jì)算出來的一個非常大的數(shù)字,用十六進(jìn)制表示。
或者你想通過 ASCII 藝術(shù)的樹形結(jié)構(gòu)來展示所有的分支, 每個分支都標(biāo)示了他的名字和標(biāo)簽:
1 git log --graph --oneline --decorate --all
看看哪些文件改變了:
假如你想回退到modify readme那個版本,可以通過git reset命令:
1 2 3 [root@controller161 gitdemo]# git reset --hard 1d57c05ee44 Unstaged changes after reset: M readme.txt
reset后面指定版本號,你可以只取前面幾位,只要能區(qū)分就行。
我們再來看readme.txt:
1 2 3 4 [root@controller161 gitdemo]# cat readme.txt hello git I like git very much add something
確實(shí)回退到那個提交版本了。
現(xiàn)在,你回退到了某個版本,關(guān)掉了電腦,第二天早上就后悔了,想恢復(fù)到新版本怎么辦?找不到新版本的commit id怎么辦?
在Git中,總是有后悔藥可以吃的?;赝吮仨氈付╟ommit id,而Git提供了一個命令git reflog用來記錄你的每一次命令:
1 2 3 4 5 [root@controller161 gitdemo]# git reflog 1d57c05 HEAD@{0}: reset: moving to 1d57c05ee44 90507c6 HEAD@{1}: commit: 添加中文行 1d57c05 HEAD@{2}: commit: modify readme 50f5fdc HEAD@{3}: commit (initial): readme file
利用rebase合并多個commit在使用Git作為版本控制的時候,我們可能會由于各種各樣的原因提交了許多臨時的 commit,
而這些 commit 拼接起來才是完整的任務(wù)。那么我們?yōu)榱吮苊馓嗟?commit 而造成版本控制的混亂,
通常我們推薦將這些 commit 合并成一個
首先假設(shè)我們有如下幾個 commit:
1 2 3 4 5 6 [root@controller161 gitdemo]# git log --pretty=oneline --abbrev-commit ad6fa66 ok , I resolve confict 3c18f63 modify README again 10c9f30 modify readme by xiao ming 796e584 dev branch modify readme by self dbf4ee5 Initial commit
我想將最近3個(ad6fa66、3c18f63、10c9f30)合并為1個提交歷史,怎樣做呢?
注意這個796e584是指的合并提交的前一個,不參與合并。出現(xiàn)下面的編輯界面:
1 2 3 4 5 6 7 8 9 10 11 12 13 pick 3c18f63 modify README again pick 10c9f30 modify readme by xiao ming # Rebase 796e584..ad6fa66 onto 796e584 (2 command(s)) # # Commands: # p, pick = use commit # r, reword = use commit, but edit the commit message # e, edit = use commit, but stop for amending # s, squash = use commit, but meld into previous commit # f, fixup = like "squash", but discard this commit's log message # x, exec = run command (the rest of the line) using shell # d, drop = remove commit
很奇怪的是第一條commit老不顯示,應(yīng)該是解決沖突的commit根本沒辦法合并原因。
可以看到其中分為兩個部分,上方未注釋的部分是填寫要執(zhí)行的指令,而下方注釋的部分則是指令的提示說明。
指令部分中由前方的命令名稱、commit hash 和 commit message 組成。
當(dāng)前我們只要知道 pick 和 squash 這兩個命令即可。
pick 的意思是要會執(zhí)行這個 commit
squash 的意思是這個 commit 會被合并到前一個commit
我們將 10c9f30 這個 commit 前方的命令改成 squash 或 s,然后輸入:wq以保存并退出。
Git會壓縮提交歷史,如果有沖突,需要修改,修改的時候要注意,保留最新的歷史,
不然我們的修改就丟棄了。修改以后要記得敲下面的命令:
1 2 git add . git rebase --continue
如果你想放棄這次壓縮的話,執(zhí)行以下命令:
如果沒有沖突,或者沖突已經(jīng)解決,則會出現(xiàn)如下的編輯窗口,出現(xiàn)下面的界面:
1 2 3 4 5 6 7 8 # This is a combination of 2 commits. # The first commit's message is: modify README again # This is the 2nd commit message: modify readme by xiao ming
然后修改成下面commit說明的:
1 modify README again , modify readme by xiao ming
輸入wq保存并退出, 再次輸入git log查看 commit 歷史信息,你會發(fā)現(xiàn)這3個 commit 已經(jīng)合并了。
1 2 3 4 [root@controller161 gitdemo]# git log --pretty=oneline --abbrev-commit 4d07b18 modify README again , modify readme by xiao ming 796e584 dev branch modify readme by self dbf4ee5 Initial commit
很奇怪,之前這個ad6fa66 ok , I resolve confict也被合并了,好神秘哦。有明白為什么的同學(xué)告我下。
兩個分支rebaserebase還有一種用法,就是當(dāng)兩個分支產(chǎn)生分叉,比如master和dev,
最后你想讓dev分支歷史看起來像沒有經(jīng)過任何合并一樣,你也許可以用 git rebase:
1 2 $ git checkout dev $ git rebase master
這些命令會把你的dev分支里的每個提交(commit)取消掉,
并且把它們臨時保存為補(bǔ)丁(patch)(這些補(bǔ)丁放到”.git/rebase”目錄中),
然后把dev分支更新到最新的master分支,最后把保存的這些補(bǔ)丁應(yīng)用到master分支上。
當(dāng)dev分支更新之后,它會指向這些新創(chuàng)建的提交(commit),而那些老的提交會被丟棄。
如果運(yùn)行垃圾收集命令(pruning garbage collection), 這些被丟棄的提交就會刪除
同樣和合并提交一樣,在rebase的過程中,也許會出現(xiàn)沖突(conflict),在這種情況,Git會停止rebase并會讓你去解決沖突;
在解決完沖突后,用git add命令去更新這些內(nèi)容的索引(index), 然后,你無需執(zhí)行git commit,只要執(zhí)行:
這樣git會繼續(xù)應(yīng)用(apply)余下的補(bǔ)丁,
同樣,在任何時候,你可以用–abort參數(shù)來終止rebase的行動,并且dev分支會回到rebase開始前的狀態(tài):
工作區(qū)、暫存區(qū)、提交歷史在git里面有三個很重要的概念:工作區(qū)、暫存區(qū)、提交歷史。
工作區(qū)(Working Directory)就是你在電腦里能看到的目錄,比如我的gitdemo文件夾就是一個工作區(qū)
暫存區(qū)(Stage)一般存放在 “.git目錄下” 下的index文件(.git/index)中,所以我們把暫存區(qū)有時也叫作索引(index)。
實(shí)際上指向暫存區(qū)的指針名就是index。
提交歷史(Commit History)隱藏目錄.git其實(shí)是Git的版本庫,也叫分支的提交歷史。Git為我們自動創(chuàng)建的第一個分支master,
以及指向master的一個指針叫HEAD。請注意暫存區(qū)也是放在這個隱藏目錄里面的。
把文件往Git版本庫里添加的時候,是分兩步執(zhí)行的:
第一步是用git add把文件添加進(jìn)去,實(shí)際上就是把文件修改添加到暫存區(qū);
第二步是用git commit提交更改,實(shí)際上就是把暫存區(qū)的所有內(nèi)容提交到當(dāng)前分支。
再次修改readme.txt:
1 2 3 4 hello git I like git very much add something 這次加一行啦啦啦
另外再添加一個文件LICENSE,內(nèi)容如下:
先用git status查看一下狀態(tài):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 [root@controller161 gitdemo]# git status On branch master Changes not staged for commit: (use "git add <file>..." to update what will be committed) (use "git checkout -- <file>..." to discard changes in working directory) modified: readme.txt Untracked files: (use "git add <file>..." to include in what will be committed) LICENSE no changes added to commit (use "git add" and/or "git commit -a")
Git非常清楚地告訴我們,readme.txt被修改了,而LICENSE還從來沒有被添加過,所以它的狀態(tài)是Untracked
現(xiàn)在,使用兩次命令git add或者git add --all,把readme.txt和LICENSE都添加后,用git status再查看一下:
1 2 3 4 5 6 7 8 [root@controller161 gitdemo]# git add --all [root@controller161 gitdemo]# git status On branch master Changes to be committed: (use "git reset HEAD <file>..." to unstage) new file: LICENSE modified: readme.txt
現(xiàn)在,暫存區(qū)的狀態(tài)就變成這樣了:
所以,git add命令實(shí)際上就是把要提交的所有修改放到暫存區(qū)(Stage),
然后,執(zhí)行git commit就可以一次性把暫存區(qū)的所有修改提交到分支:
1 2 3 4 [root@controller161 gitdemo]# git commit -m "show stage work" [master 32db843] show stage work 2 files changed, 2 insertions(+) create mode 100644 contact.txt
一旦提交后,如果你又沒有對工作區(qū)做任何修改,那么工作區(qū)就是“干凈”的:
1 2 3 [root@controller161 gitdemo]# git status On branch master nothing to commit, working directory clean
現(xiàn)在版本庫變成了這樣,暫存區(qū)就沒有任何內(nèi)容了:
關(guān)于diff很多時候需要用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ū)的修改全部撤銷,這里有兩種情況:
readme.txt自從修改后還沒有被放到暫存區(qū),現(xiàn)在,撤銷修改就回到和版本庫一模一樣的狀態(tài);
readme.txt已經(jīng)添加到暫存區(qū)后,又作了修改,現(xiàn)在,撤銷修改就回到添加到暫存區(qū)時的狀態(tài)。
總之,就是讓這個文件回到最近一次git commit或git add時的狀態(tài)。
git checkout -- file 命令中的--很重要,沒有--,就變成了“切換到另一個分支”的命令,
我們在后面的分支管理中會再次遇到git checkout命令。
還有一種情況是,你想將暫存區(qū)的修改撤銷掉:
1 git reset HEAD readme.txt
git reset命令既可以回退版本,也可以把暫存區(qū)的修改撤銷掉。當(dāng)我們用HEAD時,表示最新的版本。
注意:這里git reset并沒有不會對工作區(qū)產(chǎn)生任何影響,只是撤銷了暫存區(qū)的修改。
還記得如何丟棄工作區(qū)的修改嗎?
1 git checkout -- readme.txt
整個世界終于清靜了
總結(jié)一下撤銷修改場景:
場景1:當(dāng)你改亂了工作區(qū)某個文件的內(nèi)容,想直接丟棄工作區(qū)的修改時,用命令git checkout -- file
場景2:當(dāng)你不但改亂了工作區(qū)某個文件的內(nèi)容,還添加到了暫存區(qū)時,想丟棄修改,分兩步,
第一步用命令git reset HEAD file,就回到了場景1,第二步按場景1操作。
場景3:已經(jīng)提交了不合適的修改到版本庫時,想要撤銷本次提交,參考版本回退一節(jié),不過前提是沒有推送到遠(yuǎn)程庫。
場景4:刪除版本庫里面的文件,但是工作區(qū)間內(nèi)容保留,一般是先提交了想忽略的文件。git reset --mixed 版本庫ID
對于已提交至遠(yuǎn)程服務(wù)器的錯誤提交,參考下面的命令:
1 2 3 4 5 6 git reset --soft/mixed/hard <commit_id> git push origin HEAD --force HEAD 最新提交 HEAD~ 上1個提交 HEAD~2 上2個提交
重要的事說三遍,最后有必要再次總結(jié)幾個重要的指令:
當(dāng)執(zhí)行 git reset HEAD 命令時,暫存區(qū)的目錄樹會被重寫,被 master 分支指向的目錄樹所替換,但是工作區(qū)不受影響。
當(dāng)執(zhí)行 git rm --cached <file> 命令時,會直接從暫存區(qū)刪除文件,工作區(qū)則不做出改變。
當(dāng)執(zhí)行 git rm -f <file> 命令時,會直接把暫存區(qū)和工作區(qū)全部刪了。
當(dāng)執(zhí)行 git checkout . 或者 git checkout -- <file> 命令時,會用暫存區(qū)全部或指定的文件替換工作區(qū)的文件。
這個操作很危險,會清除工作區(qū)中未添加到暫存區(qū)的改動。
當(dāng)執(zhí)行 git checkout HEAD . 或者 git checkout HEAD <file> 命令時,
會用 HEAD 指向的 master 分支中的全部或者部分文件替換暫存區(qū)和工作區(qū)中的文件。
這個命令也是極具危險性的,因?yàn)椴坏珪宄ぷ鲄^(qū)中未提交的改動,也會清除暫存區(qū)中未提交的改動。
git reset 有三個選項(xiàng),--hard、--mixed、--soft。
注意這三個選項(xiàng)對文件層面的git reset毫無作用,因?yàn)榫彺鎱^(qū)中的文件一定會變化,而工作目錄中的文件一定不變。
1 2 3 4 5 6 7 8 //僅僅只是撤銷已提交的版本庫,不會修改暫存區(qū)和工作區(qū) git reset --soft 版本庫ID `` //僅僅只是撤銷已提交的版本庫和暫存區(qū),不會修改工作區(qū) git reset --mixed 版本庫ID //徹底將工作區(qū)、暫存區(qū)和版本庫記錄恢復(fù)到指定的版本庫 git reset --hard 版本庫ID
注意這個版本庫ID應(yīng)該不是你剛剛提交的版本庫ID,而是剛剛提交版本庫的上一個版本庫。