聲明:這篇文章來源于廖雪峰老師的官方網(wǎng)站,我僅僅是作為學(xué)習(xí)之用
Git簡(jiǎn)介
Git是什么?
Git是目前世界上最先進(jìn)的分布式版本控制系統(tǒng)。
什么是版本控制系統(tǒng)?
Git的誕生
很多人知道,Linus在1991年開源了Linux,從此,Linux系統(tǒng)不斷發(fā)展,已經(jīng)成為最大的服務(wù)器系統(tǒng)軟件了。
Linus雖然創(chuàng)建了Linux,但Linux的壯大是靠全世界熱心的志愿者參與的,這么多人在世界各地為L(zhǎng)inux編寫代碼,那么Linux的代碼是如何管理的呢?
事實(shí)上,在2002年以前,代碼作者把源文件以diff的方式發(fā)給Linus,然后由Linus本人通過手工的方式合并代碼。
當(dāng)時(shí)也有免費(fèi)的CVS、SVN這些免費(fèi)的版本控制系統(tǒng)。但是Linus堅(jiān)決的反對(duì)CVS、SVN,這些集中式的版本控制系統(tǒng)不但速度慢,而且必須聯(lián)網(wǎng)才能使用。
不過到了2002年,經(jīng)過十年的發(fā)展,通過手工方式管理代碼已經(jīng)相當(dāng)累人啦,而且社區(qū)的兄弟們也對(duì)這種方式表達(dá)了強(qiáng)烈的不滿,于是Linus選擇了一個(gè)商業(yè)的版本控制系統(tǒng)BitKeeper。BitKeeper的東家BitMover公司出于人道精神,授權(quán)Linux社區(qū)免費(fèi)使用這個(gè)版本控制系統(tǒng)。
安定團(tuán)結(jié)的局面在2005年被打破了,原因是開發(fā)Samba的Andrew試圖破解BitKeeper的協(xié)議,被BitMover公司發(fā)現(xiàn)了,要收回Linux社區(qū)的免費(fèi)使用權(quán)。
于是Linus花了兩周的時(shí)間自己用C寫了一個(gè)分布式的版本控制系統(tǒng),這就是Git!一個(gè)月之內(nèi),Linux系統(tǒng)的源碼就可以由Git管理了!
Git迅速成為最流行的分布式版本控制系統(tǒng),尤其是2008年,GitHub網(wǎng)站上線了,它為開源項(xiàng)目提供免費(fèi)Git存儲(chǔ),無數(shù)開源項(xiàng)目開始遷移至GitHub,包括JQuery,PHP,Ruby等等。
歷史就是這么偶然,如果不是當(dāng)年BitMover的決絕,那么現(xiàn)在我們可能就沒有免費(fèi)而且超級(jí)好用的Git了。
集中式VS分布式
Linus一直痛恨的CVS和SVN都是集中式的版本控制系統(tǒng),而Git是分布式的版本控制系統(tǒng),集中式和分布式的版本控制系統(tǒng)有什么分別呢?
先說集中式版本控制系統(tǒng),版本庫(kù)是放在中央服務(wù)器上的,而干活的時(shí)候,用的都是自己的電腦,就需要把版本先從中央服務(wù)器取得最新的版本,然后開始干活。干完活了,再把自己的勞動(dòng)成果推給中央服務(wù)器。
集中式版本控制系統(tǒng)最大的問題就是必須聯(lián)網(wǎng)才能工作。如果在局域網(wǎng)內(nèi)還好說,帶寬夠大,速度夠大。可如果在互聯(lián)網(wǎng)上,遭遇網(wǎng)速慢的情況,可能提交一個(gè)10M的文件就需要5分鐘。5分鐘可以憋死一頭牛啦。
分布式版本控制系統(tǒng)呢?它沒有“中央服務(wù)器”,每個(gè)人的電腦上都是一個(gè)完整的版本庫(kù),這樣你干活的時(shí)候就不需要聯(lián)網(wǎng)啦,因?yàn)榘姹編?kù)就在本地啊。既然每個(gè)人的電腦上都有一個(gè)版本庫(kù),那么如何多人協(xié)作呢?比方說你在自己的電腦上修改了A文件,你的同事也在他的電腦上修改了A文件;你們只需要把修改的地方推給對(duì)方就可以互相看到對(duì)方的修改了。
和集中式版本控制系統(tǒng)相比,分布式版本控制系統(tǒng)的安全性不知道要高到哪里去。因?yàn)槊總€(gè)人電腦里都有完整的版本庫(kù),某個(gè)人的電腦壞了也不要緊,隨便從其他人那里復(fù)制一個(gè)就可以。而集中式版本控制器的中央服務(wù)器要是出了問題,所有人都沒法干活了。
在實(shí)際使用分布式版本控制系統(tǒng)的時(shí)候,其實(shí)很少在兩人之間的電腦上推送版本庫(kù)的修改,因?yàn)榭赡苣銈儌z不在同一個(gè)局域網(wǎng)內(nèi),兩臺(tái)電腦互相訪問不了;也可能你的同事今天生了病,根本就沒開機(jī)。因此,分布式版本控制系統(tǒng)通常也有一臺(tái)充當(dāng)“中央服務(wù)器”的電腦,但這個(gè)服務(wù)器的作用僅僅是用來方便“交換”大家的修改;沒有它大家一樣干活,只是交換修改不方便了而已。
當(dāng)然Git優(yōu)勢(shì)不單是不必聯(lián)網(wǎng)這么簡(jiǎn)單,后面我們還會(huì)看到Git極其強(qiáng)大的分支管理,把SVN等遠(yuǎn)遠(yuǎn)拋在了后面。
安裝Git
在Linux上安裝Git
如果你碰巧使用Debian或Ubuntu Linux,通過一條 sudo apt-get install git
就可以完成git的安裝,非常之簡(jiǎn)單。
如果是其它Linux的版本,可以直接通過源碼安裝。先從Git官網(wǎng)下載源碼,然后解壓,依次輸入: ./config
, make
, sudo make install
這幾個(gè)命令就安裝好了。
在Mac上安裝Git
推薦一種簡(jiǎn)單的方法。直接從AppStore安裝Xcode,Xcode中集成了Git,不過默認(rèn)沒有安裝。你需要運(yùn)行Xcode,選擇菜單"Xcode"->"Preferences",在彈出的窗口中找到“Downloads”,選擇“Command Line Tools”,點(diǎn)“Install”就可以完成安裝啦。
安裝完成后,還需要進(jìn)行最后一步設(shè)置,在命令行輸入:
$ git config --global user.name "Your Name"
$ git config --global user.email "email@example.com"
因?yàn)镚it是分布式版本控制系統(tǒng),所以每個(gè)機(jī)器都必須自報(bào)家門。
注意git config
命令的--global
參數(shù)。用了這個(gè)參數(shù),表示你的這臺(tái)機(jī)器上所有的Git倉(cāng)庫(kù)都會(huì)使用這個(gè)配置。當(dāng)然你也可以對(duì)某個(gè)倉(cāng)庫(kù)指定不同的用戶名和email地址。
創(chuàng)建版本庫(kù)
版本庫(kù)又叫倉(cāng)庫(kù),英文名repository。版本庫(kù)可以簡(jiǎn)單的理解成目錄,這個(gè)目錄里面的所有文件都可以被Git管理起來,每個(gè)文件的修改、刪除,Git都能追蹤,以便在任何時(shí)刻都可以追蹤歷史,或者在將來的某個(gè)時(shí)候可以“還原”。
創(chuàng)建版本庫(kù)非常簡(jiǎn)單,選擇一個(gè)合適的地方,創(chuàng)建一個(gè)空目錄:
$ mkdir learngit
$ cd learngit
$ pwd
/Users/michael/learngit
pwd
命令用于顯示當(dāng)前目錄。在我的Mac上,這個(gè)倉(cāng)庫(kù)位于/Users/michael/learngit
。
第二步,通過git init
命令把這個(gè)目錄變成Git可以管理的倉(cāng)庫(kù):
$ git init
Initialized empty Git repository in /Users/michael/learngit/.git
瞬間Git倉(cāng)庫(kù)就建好了,而且告訴你是一個(gè)空倉(cāng)庫(kù),多出來的 .git
目錄是來跟蹤管理版本庫(kù)的,里面的文件不能手動(dòng)修改,否則Git倉(cāng)庫(kù)會(huì)遭到破壞。
如果沒有看到.git
目錄,那是因?yàn)檫@個(gè)目錄默認(rèn)是隱藏的,用ls -ah
命令就可以看見。
把文件添加到版本庫(kù)
首先聲明一點(diǎn),所有的版本控制系統(tǒng),其實(shí)只能跟蹤文本文件的改變,比如txt文件,網(wǎng)頁,所有的程序代碼等等。Git也不例外。版本控制系統(tǒng)可以告訴你每次的改動(dòng),比如第5行增加了一個(gè)單詞“Linux”,而在第8行刪除了一個(gè)單詞“Windows”等等...。而圖片、視頻這些二進(jìn)制文件雖然也能由版本控制系統(tǒng)管理,但沒法跟蹤文件的改變,只能把二進(jìn)制文件每次改動(dòng)串起來,也就是只知道圖片從100KB改成了120KB,但是到底改了啥,版本控制系統(tǒng)不知道,也沒法知道。
不幸的是Microsoft的Word格式是二進(jìn)制格式,因此版本控制系統(tǒng)無法跟蹤Word的改變。如果要真正實(shí)用版本控制系統(tǒng),就要以純文本的方式編寫文件。因?yàn)槲谋臼怯芯幋a的,如中文有常用的GBK編碼,日文有常用的Shift_JIS編碼,如果沒有歷史遺留問題,強(qiáng)烈推薦使用標(biāo)準(zhǔn)的UTF-8編碼,所有的語言使用同一種編碼,既沒有沖突,又被所有平臺(tái)所支持。
另外注意千萬不要用windows自帶的記事本 編輯任何文本文件。原因是記事本保存UTF-8編碼文件時(shí)會(huì)在每個(gè)文件開頭添加0xefbbbf字符。
言歸正傳,現(xiàn)在編寫一個(gè)readme.txt
文件,內(nèi)容如下:
Git is a version control system.
Git is free software.
這個(gè)文件一定要放在learngit
目錄下,放在別的地方Git找不到。
把一個(gè)文件放到Git倉(cāng)庫(kù)只需要兩步:
第一步, 用git add
告訴git,把文件添加到倉(cāng)庫(kù)
$ git add readme.txt
第二步,用git commit
告訴Git,把文件提交到倉(cāng)庫(kù)
$ git commit -m "wrote a readme file"
[master (root -commit) cb926e7] wrote a readme file 1 file changed, 2 insertions(+)
create mode 10064 readme.txt
簡(jiǎn)單解釋一下git commit
命令,-m
后面輸入的是本次提交的說明,這樣你就能從歷史紀(jì)錄中方便的查找改動(dòng)紀(jì)錄。
時(shí)光穿梭機(jī)
我們已經(jīng)添加并提交了一個(gè)readme.txt文件,現(xiàn)在我們修改readme.txt,改成如下內(nèi)容:
Git is a distributed version control system.
Git is free software.
現(xiàn)在運(yùn)行git status
命令。git status
命令可以讓我們看到倉(cāng)庫(kù)的狀態(tài),上面的命令告訴我們,readme.txt被修改過了。并且告訴我們后序的處理方案。git add
將修改添加到倉(cāng)庫(kù),git checkout
丟棄修改。
git status
只能告訴我們修改了哪些文件,但具體修改了文件的哪些內(nèi)容就需要用git diff
這個(gè)命令了。
提交修改
提交修改和提交新文件是一樣的兩個(gè)步驟。
第一步 是git add
$ git add readme.txt
在執(zhí)行第二步git commit
之前,我們用git status
查看倉(cāng)庫(kù)狀態(tài)。git commit
命令告訴我們后序的操作,看來Git還是很友好的。
$ git commit -m "add distributed"
提交后,我們?cè)儆?code>git status命令查看倉(cāng)庫(kù)狀態(tài)。
$ git status
# On branch master
nothing to commit (working directory clean)
Git 告訴我們當(dāng)前沒有需要提交的修改,工作目錄是干凈的。
小結(jié)
- 隨時(shí)掌握工作區(qū)狀態(tài),使用
git status
命令 - 用
git diff
可以查看具體的修改內(nèi)容
版本回退
現(xiàn)在我們?cè)俅涡薷膔eadme.txt文件
Git is a distributed version control system.
Git is free software distributed under the GPL.
然后提交
$ git add readme.txt
$ git commit -m "append GPL"
像這樣可以不斷修改不斷提交到版本庫(kù),這樣Git就能跟蹤文本的改變。當(dāng)你覺得文件修改到一定程度的時(shí)候,就可以“保存一個(gè)快照”,這個(gè)快找在Git中成為commit
。一旦你把文件改亂了,或者誤刪了文件,就可以從最近的一個(gè)commit
恢復(fù),然后繼續(xù)工作。這樣就不會(huì)把前幾個(gè)月的工作成果丟失。
現(xiàn)在我們的Git倉(cāng)庫(kù)中有三個(gè)版本啦。
版本1: wrote a readme file
版本2: add distributed
版本3: append GPL
查看歷史紀(jì)錄
版本控制系統(tǒng)有某個(gè)命令可以查看歷史紀(jì)錄,在Git中git log
就是也。
$ git log
commit 8ae8b878c335c5ae8ed381460345d2b03e8e3c6e
Author: zhangxiaojun <adam.zhang@ydx.hk>
Date: Fri Mar 11 00:01:24 2016 +0800
append GPL
commit 3eb82b8e2c94fbc5fdb0ca248fa7c15789df922c
Author: zhangxiaojun <adam.zhang@ydx.hk>
Date: Thu Mar 10 23:59:53 2016 +0800
add distributed
commit e9da25c20a570b2f6280d3bb47a5e0d3935a5a8b
Author: zhangxiaojun <adam.zhang@ydx.hk>
Date: Thu Mar 10 23:58:00 2016 +0800
wrote a readme file
git log
可以顯示從最近到最遠(yuǎn)的提交日至。如果嫌棄輸出太多的信息,可以加上--pretty=oneline
參數(shù):
$ git log --pretty=oneline
8ae8b878c335c5ae8ed381460345d2b03e8e3c6e append GPL
3eb82b8e2c94fbc5fdb0ca248fa7c15789df922c add distributed
e9da25c20a570b2f6280d3bb47a5e0d3935a5a8b wrote a readme file
版本號(hào)
一大串類似8ae8b87...d2b03ec6e
的數(shù)字是commit id
(版本號(hào))。和SVN不一樣,Git的commit id
不是1、2、3...的遞增數(shù)字,而是由SHA1計(jì)算出來的一個(gè)非常大的數(shù)字,用十六進(jìn)制表示。為什么commit id
需要一串這么大的數(shù)字來表示呢?因?yàn)镚it是分布式控制系統(tǒng),后面我們還要研究多個(gè)人在同一個(gè)版本庫(kù)工作,如果大家都用1、2、3...作為版本號(hào),那么肯定會(huì)引發(fā)沖突。
每提交一個(gè)版本,實(shí)際Git就會(huì)把它們自動(dòng)串成一條時(shí)間線。如果使用可視化工具查看Git歷史,就可以清楚地看到提交歷史的時(shí)間線。
時(shí)光穿梭機(jī)
首先Git必須要知道當(dāng)前版本是哪個(gè)版本,在Git中用HEAD
表示當(dāng)前版本,也就是最新的commit id
,上一個(gè)版本用HEAD^
表示,上上個(gè)版本是HEAD^^
,再往上一個(gè)版本就是HEARD~3
。
現(xiàn)在我們從當(dāng)前版本“append GPL”退回到上一個(gè)版本“add distributed”,就可以使用git reset
命令:
$ git reset --hard HEAD^
HEAD is now at 3eb82b8 add distributed
最新版本"append GPL"已經(jīng)不見了!好比從21世紀(jì)坐時(shí)光穿梭機(jī)回到了19世紀(jì),現(xiàn)在又想回去了,腫么辦?
辦法還是有的,只要?jiǎng)偛诺拿钚写翱跊]有關(guān),可以往上找到"append GPL"對(duì)應(yīng)的commit id
,于是就可以回到未來的某個(gè)版本
$ git reset --hard 8ae8
HEAD is now at 8ae8b87 append GPL
Git的版本回退速度是非常快,因?yàn)镚it在內(nèi)部有個(gè)指向當(dāng)前版本的HEAD
指針,當(dāng)你回退版本時(shí),Git僅僅是把HEAD
指向append GPL
。然后順便把工作區(qū)的文件更新了,所以你讓HEAD
指向哪個(gè)版本號(hào),你就把當(dāng)前版本定位在了哪里。
現(xiàn)在你回退到了某個(gè)版本,關(guān)掉了電腦,第二天早上就后悔了,想要恢復(fù)到最新的版本怎么辦?找不到新版本的commit id
怎么辦?
在Git中總是有后悔藥可醫(yī)吃的。Git提供了一個(gè)命令git reflog
來紀(jì)錄你的每一次命令:
$ git reflog
e9da25c HEAD@{0}: reset: moving to HEAD^^
8ae8b87 HEAD@{1}: reset: moving to 8ae8
3eb82b8 HEAD@{2}: reset: moving to HEAD^
8ae8b87 HEAD@{3}: commit: append GPL
3eb82b8 HEAD@{4}: commit: add distributed
e9da25c HEAD@{5}: commit (initial): wrote a readme file
長(zhǎng)出一口氣,第5行顯示 append GPL
的commit id
是 8ae8b87
。現(xiàn)在我們可以乘坐時(shí)光機(jī)回到未來了。
小結(jié)
小結(jié)一下
-
HEAD
指向的版本就是當(dāng)前版本,Git允許我們?cè)诎姹局g時(shí)光穿梭,使用命令git reset --hard commit_id
。 - 穿梭前,用
git log
查看提交歷史紀(jì)錄,以便確認(rèn)要退到哪個(gè)版本。 - 要重返未來,用
git reflog
查看命令歷史,以便確定回到未來的哪個(gè)版本。
遠(yuǎn)程倉(cāng)庫(kù)
用過集中式版本管理系統(tǒng)的同學(xué)會(huì)說,前面這些功能SVN里早就有了,沒有看出Git有什么特別的地方。
沒錯(cuò),如果只是一個(gè)倉(cāng)庫(kù)管理文件歷史,Git和SVN真的沒啥區(qū)別。為了保證你學(xué)習(xí)Git物超所值,將來絕對(duì)不會(huì)后悔,我們開始介紹Git的殺手級(jí)功能之一:遠(yuǎn)程倉(cāng)庫(kù)。
Git是分布式版本控制系統(tǒng),同一個(gè)Git倉(cāng)庫(kù),可以分布到不同的機(jī)器上。怎么分布呢?最早,肯定只有一臺(tái)機(jī)器有一個(gè)原始版本,此后,別的機(jī)器可以“克隆”這個(gè)原始版本庫(kù),而且每臺(tái)機(jī)器的版本庫(kù)其實(shí)都是一樣的,并沒有主次之分。
你肯定會(huì)想,至少需要兩臺(tái)機(jī)器才能玩遠(yuǎn)程庫(kù)不是?但是直有一臺(tái)電腦怎么玩?
- 其實(shí)一臺(tái)電腦也可以玩,只要倉(cāng)庫(kù)在不同的目錄下,但是在世紀(jì)開發(fā)中這種方式完全沒有意思。
- 實(shí)際開發(fā)時(shí),需要自己搭建一臺(tái)運(yùn)行Git的服務(wù)器。
- 有一個(gè)叫GitHub的神奇網(wǎng)站,這個(gè)網(wǎng)站是專門提供Git倉(cāng)庫(kù)托管服務(wù)的,所以,只要注冊(cè)一個(gè)GitHub賬號(hào),就可以免費(fèi)獲得Git遠(yuǎn)程倉(cāng)庫(kù)。
我們現(xiàn)在就將Git倉(cāng)庫(kù)托管到GitHub上,來看一下具體的步驟。
第一步,創(chuàng)建 SSH KEY。在用戶主目錄下,看看有沒有.ssh目錄,如果有,再看看這個(gè)目錄下有沒有id_rsa
和id_rsa.pub
這兩個(gè)文件,如果有了可以直接進(jìn)行下一步。如果沒有,打開Shell(Windows下打開Git Bash),創(chuàng)建SSH KEY:
$ ssh-keygen -t rsa -C "youremail@example.com"
需要把郵件地址換成自己的郵件地址,然后一路回車,使用默認(rèn)值即可,也無需設(shè)置密碼。
如果一切順利會(huì)在用戶主目錄找到.ssh
目錄里面有id_rsa
和id_rsa.pub
這兩個(gè)文件。這兩個(gè)就是SSH的秘鑰對(duì),id_rsa
是私鑰,id_rsa.pub
是公鑰,公鑰可以放心的告訴別人。
第二步,登錄GitHub,打開“Account settings”,“SSH Keys”頁面。然后點(diǎn)擊“Add SSH Key”,填上任意Title,在Key文本框里粘貼id_rsa.pub
文件內(nèi)容,通俗的講就是把公鑰告訴GitHub。
為什么GitHub需要知道你的SSH Key呢?因?yàn)镚itHub需要認(rèn)證你推送的提交確實(shí)是你推送的,而不是別人冒充的。而Git支持SSH協(xié)議,所以GitHub只要知道了你的公鑰,就可以確定只有你自己才能推送。
當(dāng)然GitHub允許你添加多個(gè)key。假定你有若干電腦,你一會(huì)兒在公司提交,一會(huì)兒在家里提交。只要把每臺(tái)電腦的key都添加到GitHub,就可以在每臺(tái)電腦上往GitHub推送了。
最后友情提示,在GitHub上免費(fèi)托管的Git倉(cāng)庫(kù),任何人都可以看到喔(但是只有你自己才能修改)。所以不要把敏感信息放進(jìn)去。
如果你不想讓別人看到Git倉(cāng)庫(kù),有兩個(gè)辦法。一個(gè)是交點(diǎn)保護(hù)費(fèi)把公開的倉(cāng)庫(kù)變成私有的,這樣別人就看不到了。另一個(gè)辦法是自己動(dòng)手搭建一個(gè)Git服務(wù)器。
小結(jié)
有了Git遠(yuǎn)程倉(cāng)庫(kù),再也不用擔(dān)心自己的硬盤了,代碼的存放也會(huì)有管家替我管理啦,另外我也可以把純文本的學(xué)習(xí)筆記讓GitHub替我托管啦。
添加遠(yuǎn)程倉(cāng)庫(kù)
現(xiàn)在的情景是,你已經(jīng)在本地創(chuàng)建了一個(gè)Git倉(cāng)庫(kù)后,又想在GitHub創(chuàng)建一個(gè)Git倉(cāng)庫(kù),并且讓這兩個(gè)倉(cāng)庫(kù)進(jìn)行遠(yuǎn)程同步,這樣GitHub上的倉(cāng)庫(kù)即可以作為備份,又可以讓其他人通過倉(cāng)庫(kù)來協(xié)作,真是一舉多得。
首先登錄GitHub,然后點(diǎn)擊右上角的加號(hào)“Create a new rep”按鈕,創(chuàng)建一個(gè)新的倉(cāng)庫(kù):
在Repository name 中填入learngit
,其它保持默認(rèn)設(shè)置,點(diǎn)擊"Create repository"按鈕,就成功創(chuàng)建了一個(gè)新的Git倉(cāng)庫(kù):

現(xiàn)在我們要做的是將本地倉(cāng)庫(kù)與遠(yuǎn)程倉(cāng)庫(kù)關(guān)聯(lián),根據(jù)GitHub的提示,在本地的learngit
倉(cāng)庫(kù)運(yùn)行命令:
$ git remote add origin git@github.com:honeywolf/learngit.git
將honeywolf
替換成自己的GitHub賬戶名,否則你在本地關(guān)聯(lián)的是我的遠(yuǎn)程倉(cāng)庫(kù),關(guān)聯(lián)沒有問題,但是以后推送代碼是推送不了的,因?yàn)槟愕腟SH Key公鑰不再我的帳戶列表中。
添加后,遠(yuǎn)程倉(cāng)庫(kù)的名字就是origin
,這事Git默認(rèn)的叫法,也可以改成別的,但是origin
這個(gè)名字一看就知道是遠(yuǎn)程庫(kù)。
下一步就是把本地的內(nèi)容推送到遠(yuǎn)程庫(kù):
$ git push -u origin master
Counting objects: 3, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (2/2), done.
Writing objects: 100% (3/3), 270 bytes | 0 bytes/s, done.
Total 3 (delta 0), reused 0 (delta 0)
To git@github.com:honeywolf/learngit.git
* [new branch] master -> master
Branch master set up to track remote branch master from origin.
把本地內(nèi)容推送到遠(yuǎn)程,用git push
命令,實(shí)際上是把當(dāng)前分支master
推送到遠(yuǎn)程。
由于遠(yuǎn)程庫(kù)是空的,我們第一次推送master
分支時(shí),加上了-u
參數(shù),Git不但會(huì)把本地的master
分支和遠(yuǎn)程的master
分支關(guān)聯(lián)起來,在以后的推送或者拉取時(shí)可以簡(jiǎn)化命令。
從現(xiàn)在起,只要本地作了提交,就可以通過命令:
$ git push origin master
把本地master
分支的最新修改推送至GitHub,現(xiàn)在,你就真正擁有了分布式版本庫(kù)。
創(chuàng)建與合并分支
在版本回退里,我們已經(jīng)知道,每次提交,Git會(huì)把它們串成一條時(shí)間線,這條時(shí)間線就是一個(gè)分支。截止到目前,只有一條時(shí)間線,在Git中這個(gè)分支叫做主分支,即master
分支。HEAD
嚴(yán)格來講并不是指向提交(commit_id
),而是指向master
,master
才是指向提交的,所以,HEAD
指向的就是當(dāng)前分支。
一開始的時(shí)候,master
分支是一條線,Git用master
指向最新的提交,再用HEAD
指向master
,就能確定當(dāng)前分支,以及當(dāng)前分支的提交點(diǎn)。
每次提交,master
分支都會(huì)向前移動(dòng)一步,這樣,隨著你不斷提交,master
分支的線也越來越長(zhǎng)。
當(dāng)我們創(chuàng)建一個(gè)新的分支dev
的時(shí)候,Git新建了一個(gè)指針叫做dev
,指向master
相同的提交,這樣一個(gè)新的分支就誕生了。再把HEAD
指向dev
,就表示當(dāng)前分支在dev
上。這樣看來Git創(chuàng)建分支的速度很迅猛,因?yàn)槌嗽黾右粋€(gè)指針dev
,改變HEAD
的指向,工作區(qū)的文件都沒有發(fā)生變化。
不過從現(xiàn)在開始,對(duì)工作區(qū)的修改和提交就是針對(duì)dev
分支啦,比如新提交一次后,dev
指針往前移動(dòng)了一步,而master
指針是不變的。
假如我們?cè)?code>dev上的工作完成了,就可以把dev
合并到master
上。Git怎么合并分支呢?最簡(jiǎn)單的辦法,就是直接把master
指向dev
的當(dāng)前提交,就完成了合并。所以Git分支的合并也很迅捷,工作區(qū)的內(nèi)容也不會(huì)發(fā)生改變。
合并完成以后,甚至可以刪除dev
分支。刪除dev
分支就是把dev
指針給刪掉,刪掉后,我們就剩下了一條master
分支。
下面開始實(shí)戰(zhàn)。
首先,我們創(chuàng)建一個(gè)dev
分支,然后切換到dev
分支。
$ git checkout -b dev
Switched to a new branch 'dev'
git checkout
命令加上-b
參數(shù)表示創(chuàng)建并切換,相當(dāng)于以下兩條指令
$ git branch dev
$ git checkout dev
Switched to a new branch 'dev'
git branch
命令會(huì)列出所有分支,當(dāng)前分支前面有個(gè)*號(hào):
$ git branch
* dev
master
然后我們?cè)?code>dev上可以做正常的提交,比如對(duì)readme.txt的修改,加上一行:
creating a new branch is quick
然后提交
$ git commit -a -m "branch test"
[dev e96c626] branch test
1 file changed, 2 insertions(+)
現(xiàn)在dev
的工作完成了,我們就可以切換回master
分支。
$ git checkout master
Switched to branch 'master'
Your branch is up-to-date with 'origin/master'.
現(xiàn)在我們切回master
分支后,再查看readme.txt文件,剛才添加的內(nèi)容不見了!因?yàn)槟莻€(gè)提交實(shí)在dev
分支上,而master
分支此刻的提交點(diǎn)并沒有發(fā)生變化。
現(xiàn)在,我們把dev
的工作成果合并到master
分支:
$ git merge dev -m "merge dev"
Updating e9da25c..e96c626
Fast-forward
readme.txt | 2 ++
1 file changed, 2 insertions(+)
git merge
命令用于合并指定分支到當(dāng)前分支。合并后,再查看 readme.txt文件內(nèi)容,就可以看到,和dev
分支最后提交的是完全一樣的。
注意到上面的Fast-forward
信息,Git告訴我們,這次合并是“快進(jìn)模式”,也就是直接把master
指向dev
的當(dāng)前提交,所以合并的速度非常的快。
當(dāng)然并不是每次合并都能使用Fast-forward
模式,后面我們會(huì)講道其他方式的合并。
合并完成后,就可以放心地刪除dev
分支了。
$ git branch -d dev
Deleted branch dev (was e96c626).
因?yàn)閯?chuàng)建、合并和刪除分支非常快,所以Git鼓勵(lì)你使用分支完成某個(gè)任務(wù),合并后再刪除分支,這和直接在master
分支上工作效果是一樣的,但過程更安全。
小結(jié)
Git鼓勵(lì)大量使用分支:
查看分支 git branch
創(chuàng)建分支 git branch <name>
切換分支 git checkout <name>
創(chuàng)建+切換分支 git checkout -b <name>
合并到當(dāng)前分支 git merge <name>
刪除分支 git branch -d <name>
解決沖突
人生不如意事有九八,合并分支往往也不是一帆分順的。
準(zhǔn)備新的feature1
分支,繼續(xù)我們的新分支開發(fā):
$ git checkout -b feature1
Switched to a new branch 'feature1'
修改readme.txt最后一行,改為:
$ git add readme.txt
$ git commit -m "AND simple"
[feature1 8974d1b] AND simple
1 file changed, 1 insertion(+), 1 deletion(-)
切換到master
分支:
$ git checkout master
Switched to branch 'master'
Your branch is ahead of 'origin/master' by 1 commit.
(use "git push" to publish your local commits)
Git 會(huì)自動(dòng)提示我們當(dāng)前的master
分支比遠(yuǎn)程的master
分支要超前一個(gè)提交。
在master
分支上把readme.txt最后一行改為:
Creating a new branch is quick & simple.
提交:
$ git add readme.txt
$ git commit -m "& simple"
[master e4a1309] & simple
1 file changed, 1 insertion(+), 1 deletion(-)
現(xiàn)在master
分支和feature1
分支各自都分別有新的提交:
在這種情況下Git無法執(zhí)行快速合并,只能試圖把各自的修改合并起來,但這種合并就可能會(huì)有沖突,我們?cè)囋嚳矗?/p>
$ git merge feature1
Auto-merging readme.txt
CONFLICT (content): Merge conflict in readme.txt
Automatic merge failed; fix conflicts and then commit the result.
果然沖突了!Git告訴我們,readme.txt文件存在沖突,必須手動(dòng)解決沖突然后再提交。git status
也可以告訴我們沖突的文件:
$ git status
On branch master
Your branch is ahead of 'origin/master' by 2 commits.
(use "git push" to publish your local commits)
You have unmerged paths.
(fix conflicts and run "git commit")
Unmerged paths:
(use "git add <file>..." to mark resolution)
both modified: readme.txt
no changes added to commit (use "git add" and/or "git commit -a")
我們可以查看readme.txt的內(nèi)容:
Git is a version control system.
Git is a free software.
<<<<<<< HEAD
Create a new branch is quick & simple.
=======
Create a new branch is quick AND simple.
>>>>>>> feature1
Git使用<<<<<<<
,=======
,>>>>>>>
標(biāo)記出不同的內(nèi)容,我們修改成如下后保存。
Create a new branch is quick and simple.
再提交:
$ git add readme.txt
$ git commit -m "conflic fixed"
[master 4f8be33] conflic fixed
用帶參數(shù)的git log
也可以看到分支的合并情況:
$ git log --graph --pretty=oneline --abbrev
* 4f8be331c4a0972055f295cf8b8c4e7cec15e8d3 conflic fixed
|\
| * 8974d1bf72c971d6904568f5ada712559a13a1b2 AND simple
* | e4a1309271c343c844caaa4217ca8724024b8642 & simple
|/
* e96c626c9adf06390a936c51e3de6723e1d2e3b3 branch test
* e9da25c20a570b2f6280d3bb47a5e0d3935a5a8b wrote a readme file
最后刪除feature1
分支:
$ git branch -d feature1
Deleted branch feature1 (was 8974d1b).
工作完成。
小結(jié)
當(dāng)Git無法自動(dòng)合并分支時(shí),就必須首先要解決沖突。解決沖突后,再提交,合并完成。
用git log --graph
命令可以看到分支合并圖。
分支管理策略
通常,合并分支時(shí),如果可能,Git會(huì)用Fast forward
模式,但在這種模式下,刪除分支后,會(huì)丟掉分支信息。
如果強(qiáng)制禁用Fast forward
模式,Git會(huì)在merger時(shí)生成一個(gè)新的commit,這樣,從分支歷史上就可以看出分支信息。
下面我們實(shí)戰(zhàn)一下--no-ff
方式的git merge
首先我們?nèi)匀灰獎(jiǎng)?chuàng)建并切換dev
分支
修改readme.txt文件,并提交一個(gè)新的commit:
我們切回master
準(zhǔn)備合并dev
分支,請(qǐng)注意--no-f
參數(shù),表示禁止使用Fast forward
:
$ git merge --no-f -m "merge with no-ff" dev
Merge made by the 'recursive' strategy.
readme.txt | 1 +
1 file changed, 1 insertion(+)
因?yàn)楸敬魏喜⒁獎(jiǎng)?chuàng)建一個(gè)新的commit,所以加上-m
參數(shù),把commit描述寫了進(jìn)去。
合并后我們用git log
查看分支歷史:
$ git log --graph --pretty=oneline
* 9e2804ba5091cf25825479b4483d98d1767f31b0 merge with no-ff
|\
| * 41b696e22b98cd19baa45824a355656658a1d06d add merge
|/
* 4f8be331c4a0972055f295cf8b8c4e7cec15e8d3 conflic fixed
|\
| * 8974d1bf72c971d6904568f5ada712559a13a1b2 AND simple
* | e4a1309271c343c844caaa4217ca8724024b8642 & simple
|/
* e96c626c9adf06390a936c51e3de6723e1d2e3b3 branch test
* e9da25c20a570b2f6280d3bb47a5e0d3935a5a8b wrote a readme file
分支策略
在實(shí)際開發(fā)中,我們應(yīng)該按照幾個(gè)基本原則進(jìn)行分支管理:
首先:master
分支應(yīng)該是非常穩(wěn)定的,也就是僅僅用來發(fā)布新版本,平時(shí)不能在上面干活;
那在哪里干活呢?干活都在dev
分支上,也就是說dev
分支是不穩(wěn)定的,到某個(gè)時(shí)候,比如1.0版本發(fā)布時(shí),再把dev
分支合并到master
上。在master
上發(fā)布1.0版本。
你和你的小伙伴每個(gè)人都在dev
分支上干活,每個(gè)人都有自己的分支,時(shí)不時(shí)地往dev
分支上合并就可以了。
小結(jié)
Git分支十分強(qiáng)大,在團(tuán)隊(duì)開發(fā)中應(yīng)該充分應(yīng)用。
合并分支時(shí),加上--no-ff
參數(shù)就可以以普通模式合并,合并后的歷史分支,能看出來作了哪些合并,而Fast forward
合并就看不出來曾經(jīng)做過合并。
Bug分支
軟件開發(fā)中,bug就像家常便飯一樣。有了bug就需要修復(fù),在Git中,由于分支是如此強(qiáng)大。所以每個(gè)bug都可以通過一個(gè)臨時(shí)分支修復(fù),修復(fù)后合并分支,然后將臨時(shí)分支刪除。
當(dāng)你接到一個(gè)修復(fù)代號(hào)為101的bug任務(wù)時(shí),很自然的,你想創(chuàng)建一個(gè)分支issue-101
來修復(fù)它。但是,先等一等,當(dāng)前正在dev
上進(jìn)行的工作還沒提交呢。
$ git status
On branch dev
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
new file: hello.py
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
并不是你不想提交,而是工作還沒有完成,還沒法提交,預(yù)計(jì)完成還需一天時(shí)間。但是,必須在兩個(gè)小時(shí)之內(nèi)修復(fù)改bug,怎么辦?
Git考慮的很周到,為我們提供了git stash
功能,可以把工作區(qū)現(xiàn)場(chǎng)“存儲(chǔ)”起來,等以后恢復(fù)現(xiàn)場(chǎng)后繼續(xù)工作。
$ git stash
Saved working directory and index state WIP on dev: 41b696e add merge
HEAD is now at 41b696e add merge
現(xiàn)在用git status
來查看情況,工作區(qū)就是干凈的,除非有沒有被Git管理的文件。因此你就可以放心大膽的地創(chuàng)建新的分支來修復(fù)bug。
首先要確定在哪個(gè)分支上修復(fù)bug,假定需要在master
上修復(fù),就從master
創(chuàng)建臨時(shí)分支:
$ git checkout master
Switched to branch 'master'
Your branch is ahead of 'origin/master' by 6 commits.
(use "git push" to publish your local commits)
$ git checkout -b issue-101
Switched to a new branch 'issue-101'
現(xiàn)在修復(fù)bug,然后提交:
$ git add readme.txt
$ git commit -m "fix bug 101"
[issue-101 20f6aa2] fix bug 101
1 file changed, 1 insertion(+), 1 deletion(-)
修復(fù)完,切換到master
分支,完成合并,刪除issue-101
分支:
$ git checkout master
Switched to branch 'master'
Your branch is ahead of 'origin/master' by 6 commits.
(use "git push" to publish your local commits)
$ git merge --no-ff -m "merge fix bug 101" issue-101
Merge made by the 'recursive' strategy.
readme.txt | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
$ git branch -d issue-101
Deleted branch issue-101 (was 20f6aa2).
太棒了,原計(jì)劃兩個(gè)小時(shí)的bug修復(fù)只花費(fèi)了5分鐘!現(xiàn)在是時(shí)候回到dev
分支干活啦。
$ git checkout dev
Switched to branch 'dev'
$ git status
On branch dev
nothing to commit, working directory clean
工作區(qū)是干凈的,剛才的工作現(xiàn)場(chǎng)到哪里去了?用git stash list
查看
$ git stash list
stash@{0}: WIP on dev: 41b696e add merge
工作現(xiàn)場(chǎng)還在,Git把工作現(xiàn)場(chǎng)存儲(chǔ)到了某個(gè)地方,但是需要恢復(fù)一下,有兩個(gè)辦法:
一是用git stash apply
恢復(fù),但是恢復(fù)后,stash內(nèi)容并不清楚,你需要用git stash drop
來刪除;
另一種方法是用 git stash pop
,恢復(fù)的同時(shí)把stash的內(nèi)容也刪了:
$ git stash pop
On branch dev
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
new file: hello.py
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
Dropped refs/stash@{0} (8ba1ca0c4d887d79faba741dca8ff37fa5612fb6)
再用git stash list
查看,就看不到任何stash的內(nèi)容了:
$ git stash list
你可以多次stash,恢復(fù)的時(shí)候先用git stash list
查看,然后恢復(fù)指定的stash,用命令:
git stash apply stash@{1}
小結(jié)
修復(fù)bug時(shí),我們會(huì)通過創(chuàng)建新的bug分支進(jìn)行修復(fù),然后合并,最后刪除
當(dāng)手頭工作沒有完成時(shí),我們用git stash
將工作區(qū)緩存,然后去修復(fù)bug。bug改完后,再git stash pop
。
Feature分支
軟件開發(fā)過程匯總,總有無窮盡的新功能要不斷添加進(jìn)來。
添加一個(gè)新功能時(shí),你肯定不希望因?yàn)橐恍?shí)驗(yàn)性質(zhì)的代碼,把主分支搞亂了。所以每添加一個(gè)新功能最好添加一個(gè)新的feature分支,在上面開發(fā)新功能。完成后,合并,刪除該feature分支。
現(xiàn)在你終于接到了一個(gè)新任務(wù):開發(fā)代號(hào)為Vulcan的新功能,該功能計(jì)劃用于下一代星際飛船。
于是準(zhǔn)備開發(fā)
$ git checkout -b feature-vulcan
Switched to a new branch 'feature-vulcan'
5分鐘后,開發(fā)完畢:
$ git status
On branch feature-vulcan
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
new file: vulcan.py
$ git commit -m "add feature vulcan"
[feature-vulcan 8f9c087] add feature vulcan
1 file changed, 1 insertion(+)
create mode 100644 vulcan.py
切回dev
,準(zhǔn)備合并:
$ git checkout dev
Switched to branch 'dev'
一切順利的話,feature分支和bug分支是類似的,合并,然后刪除。
但是,就在此時(shí),接到上級(jí)命令,因經(jīng)費(fèi)不足,新功能必須取消!
雖然白干了,但是這個(gè)分支還是必須就地銷毀:
$ git branch -d feature-vulcan
error: The branch 'feature-vulcan' is not fully merged.
If you are sure you want to delete it, run 'git branch -D feature- vulcan'.
Git提示,該分支未完全merge,還不能刪除,如果要強(qiáng)行刪除,需要使用git branch -D feature- vulcan
命令:
現(xiàn)在我們強(qiáng)行刪除:
$ git branch -D feature-vulcan
Deleted branch feature-vulcan (was 8f9c087).
小結(jié)
開發(fā)一個(gè)新的feature,最好創(chuàng)建一個(gè)新的分支;
如果要丟棄一個(gè)沒有被合并過的分支,可以通過 git branch -D feature-vulcan
強(qiáng)行刪除。
多人協(xié)作
當(dāng)你從遠(yuǎn)程倉(cāng)庫(kù)克隆時(shí),實(shí)際上Git自動(dòng)把本地的master
分支和遠(yuǎn)程的master
分支對(duì)應(yīng)起來了,并且,遠(yuǎn)程倉(cāng)庫(kù)的默認(rèn)名稱是origin
。
要查看遠(yuǎn)程倉(cāng)庫(kù)的信息用git remote
$ git remote
origin
或者用git remote -v
顯示更詳細(xì)的信息:
$ git remote -v
origin git@github.com:hoenywolf/learngit.git
(frtch)
origin git@github.com:hoenywolf/learngit.git
(push)
上面顯示了可以抓取和推送的origin
地址。如果沒有推送權(quán)限就看不到push地址。
推送分支
推送分支,就是把分支上的所有本地?cái)?shù)據(jù)提交推送到遠(yuǎn)程庫(kù)。推送時(shí),要指定本地分支,這樣,Git就會(huì)把該分支推送到遠(yuǎn)程庫(kù)對(duì)應(yīng)的分支上:
$ git push origin master
如果要推送其他分支,比如dev
,就改成:
$ git push origin dev
但是,并不是一定要把本地分支往遠(yuǎn)程推送,那么哪些分支需要推送,哪些不需要呢?
-
master
分支是主分支,因此要時(shí)刻與遠(yuǎn)程倉(cāng)庫(kù)同步; -
dev
是開發(fā)分支,團(tuán)隊(duì)所有的成員都要在上面工作,所以也需要與遠(yuǎn)程同步; -
bug
分支只用于在本地修復(fù)bug,就沒必要推送到遠(yuǎn)程了,除非老板要看看你每周修復(fù)了幾個(gè)bug -
feature
分支是否推送到遠(yuǎn)程取決于你是否和你的伙伴合作在上面開發(fā)。
總之在Git中,分支完全可以在本地自己藏著玩,是否推送,視你的心情而定!
抓取分支
多人協(xié)作時(shí),大家都會(huì)往master
分支和dev
分支上推送各自的修改。
現(xiàn)在模擬一個(gè)你的小伙伴,可 以在另一臺(tái)電腦(注意要把SSH Key添加到GitHub)或者同一臺(tái)電腦的另一個(gè)目錄下克隆:
$ git clone git@github.com:honeywolf/learngit.git
當(dāng)你的小伙伴從遠(yuǎn)程倉(cāng)庫(kù)clone時(shí),默認(rèn)情況下,你的小伙伴只能看到本地master
分支。
$ git branch
* master
現(xiàn)在,你的小伙伴要在dev
分支上開發(fā),就必須創(chuàng)建遠(yuǎn)程orgin
的dev
分支到本地,于是他用這個(gè)命令創(chuàng)建本地分支:
$ git checkout -b dev origin/dev
現(xiàn)在,他就可以在dev
分支上繼續(xù)修改,然后時(shí)不時(shí)地不斷把dev
分支push
到遠(yuǎn)程:
$ git commit -m "add /usr/bin/env"
....
$ git push origin dev
....
你的小伙伴已經(jīng)向origin/dev
分支推送了他的提交,而碰巧你也對(duì)同樣的文件作了修改,并試圖推送:
$ git push origin dev
To git@github.com:honeywolf/learngit.git
! [rejected] dev -> dev (fetch first)
error: failed to push some refs to 'git@github.com:honeywolf/learngit.git'
hint: Updates were rejected because the remote contains work that you do
hint: not have locally. This is usually caused by another repository pushing
hint: to the same ref. You may want to first integrate the remote changes
hint: (e.g., 'git pull ...') before pushing again.
hint: See the 'Note about fast-forwards' in 'git push --help' for details.
推送失敗,因?yàn)槟愕男』锇榈淖钚绿峤缓湍阍噲D推送的提交有沖突,解決辦法也很簡(jiǎn)單,Git已經(jīng)提示我們,先用 git pull
把最新的提交從origin/dev
抓下來,然后,在本地合并,解決沖突,再推送:
$ git pull
remote: Counting objects: 8, done.
remote: Compressing objects: 100% (4/4), done.
remote: Total 8 (delta 0), reused 8 (delta 0), pack-reused 0
Unpacking objects: 100% (8/8), done.
From github.com:honeywolf/learngit
a947c9b..2f95e78 dev -> origin/dev
There is no tracking information for the current branch.
Please specify which branch you want to merge with.
See git-pull(1) for details
git pull <remote> <branch>
If you wish to set tracking information for this branch you can do so with:
git branch --set-upstream-to=origin/<branch> dev
git pull
也失敗了,原因是沒有指定本地dev
分支與遠(yuǎn)程origin/dev
分支的鏈接,根據(jù)提示,設(shè)置dev
和origin/dev
的鏈接:
$ git branch --set-upstream-to=origin/dev dev
再pull:
$ git pull
這回git pull
成功,但是合并有沖突,需要手動(dòng)解決,解決的辦法和分支管理中解決沖突完全一樣。解決后,然后提交,再push:
$ git commit -m "merge & fix hello.py"
...
$ git push origin dev
...
因此多人協(xié)作的工作模式通常是這樣的:
- 首先,自己試圖用
git push origin branch-name
推送自己的修改; - 如果推送失敗,則因?yàn)檫h(yuǎn)程分支比你的本地更新,需要用
git pull
試圖合并 - 如果合并有沖突,則解決沖突,并在本地提交;
- 沒有沖突或者解決掉沖突后,再用
git push origin brach-name
推送就能成功。
如果git pull
提示"no tracking information",則說明本地分支和遠(yuǎn)程分支的鏈接關(guān)系沒有創(chuàng)建,用命令git branch --set-upstream branch-name origin/branch-name
。
這就是多人協(xié)作的工作模式,一旦熟悉了,就非常簡(jiǎn)單。
小結(jié)
- 查看遠(yuǎn)程庫(kù)信息,使用
git remote -v
; - 本地新建的分支如果不推送到遠(yuǎn)程,對(duì)其他人就是不可見的;
- 從本地推送分支,使用
git push origin branch-name
,如果推送失敗先用git pull
抓取遠(yuǎn)程的最新提交; - 在本地創(chuàng)建和遠(yuǎn)程分支對(duì)應(yīng)的分支,使用
git checkout -b branch-name origin/branch-name
,本地和遠(yuǎn)程分支的名稱最好一致; - 建立本地分支和遠(yuǎn)程分支的關(guān)聯(lián),使用
git branch --set-upstream-to=origin/<branch-name> <branch-name>
- 從遠(yuǎn)程抓取分支,使用
git pull
,如果有沖突,要先處理沖突。
標(biāo)簽管理
發(fā)布一個(gè)版本時(shí),我們通常在版本庫(kù)中打一個(gè)標(biāo)簽,這樣,就唯一確定了打標(biāo)簽時(shí)刻的版本。將來無論什么時(shí)候,取這個(gè)標(biāo)簽的版本,就是把那個(gè)打標(biāo)簽時(shí)刻的歷史版本取出來。所以標(biāo)簽也是版本庫(kù)的一個(gè)快照。
Git標(biāo)簽雖然是版本庫(kù)的快照,但其實(shí)它就是指向某個(gè)commit 指針(跟分支很像對(duì)不對(duì)?但是分支是可以移動(dòng)的,標(biāo)簽不可以移動(dòng))所以,創(chuàng)建和刪除標(biāo)簽都是瞬間完成的。
創(chuàng)建標(biāo)簽
在Git中打標(biāo)簽非常簡(jiǎn)單,首先,切換到需要打標(biāo)簽的分支上:
$ git branch
* dev
master
$ git checkout master
Switched to branch 'master'
Your branch is up-to-date with 'origin/master'.
然后敲命令git tag <name>
就可以打一個(gè)新標(biāo)簽:
$ git tag v1.0
可以用git tag
命令查看所有的標(biāo)簽
$ git tag
v1.0
默認(rèn)的標(biāo)簽都是打在最新的commit提交上的。有時(shí)候,會(huì)忘記打標(biāo)簽。比如現(xiàn)在已經(jīng)周五了,但應(yīng)該在周一打的標(biāo)簽沒有打,該怎么辦呢?
方法是找到歷史提交的 commit id,然后打上就可以了:
$ git log --pretty=oneline --abbrev-commit
4e90863 merge fix bug 101
20f6aa2 fix bug 101
9e2804b merge with no-ff
41b696e add merge
4f8be33 conflic fixed
e4a1309 & simple
8974d1b AND simple
e96c626 branch test
e9da25c wrote a readme file
比方說要對(duì)add merge
這次提交打標(biāo)簽,它對(duì)應(yīng)的commit id 是41b696e
,敲入命令
$ git tag v0.9 41b696e
$ git tag
v0.9
v1.0
注意標(biāo)簽不是按時(shí)間順序列出,而是按字母排序的。可以用git show <tagname>
查看標(biāo)簽信息:
$ git show v0.9
commit 41b696e22b98cd19baa45824a355656658a1d06d
Author: honeywolf <adam.h@gmail.com>
Date: Fri Mar 11 23:42:57 2016 +0800
add merge
...
可以看到,v0.9
確實(shí)打在add merge
這次提交上。
還可以創(chuàng)建帶有說明的標(biāo)簽,用-a
指定標(biāo)簽名,-m
指定說明文字:
$ git tag -a v0.1 -m "version 0.1 released" 20f6aa220
用命令git show <tagname>
可以看到說明文字
$ git show v0.1
tag v0.1
Tagger: honeywolf <adam.h@gmail.com>
Date: Sat Mar 12 14:56:11 2016 +0800
version 0.1 released
commit 20f6aa22033491f033d54efe1088fc04e7fbff38
Author: honeywolf <adam.h@gmail.com>
Date: Sat Mar 12 00:16:50 2016 +0800
fix bug 101
還可以通過-s
用私鑰簽名一個(gè)標(biāo)簽:
$ git tag -s v0.2 -m "signed version 0.2 released" 4e90863dc
簽名采用PGP簽名,因此,必須首先安裝gpg(GnuPG),如果沒有找到gpg,或者沒有g(shù)pg密鑰對(duì),就會(huì)報(bào)錯(cuò):
error: cannot run gpg: No such file or directory
error: could not run gpg.
error: unable to sign the tag
如果報(bào)錯(cuò),請(qǐng)參考GnuPG
小結(jié)
- 命令
git tag <name>
用于新建一個(gè)標(biāo)簽,默認(rèn)為HEAD
,也可以指定一個(gè)commit id; -
git tag -a <tagname> -m "balabalabala..."
可以指定標(biāo)簽信息; -
git tag -s <tagname> -m "balabalabala..."
可以用PGP簽名標(biāo)簽; - 命令
git tag
可以查看所有標(biāo)簽。
操作標(biāo)簽
如果標(biāo)簽打錯(cuò)了,可以刪除:
$ git tag -d v0.1
Deleted tag 'v0.1' (was c9e4c38)
因?yàn)閯?chuàng)建的標(biāo)簽都只存儲(chǔ)在本地,不會(huì)自動(dòng)推送到遠(yuǎn)程。所以,打錯(cuò)的標(biāo)簽可以在本地安全刪除。
如果要推送到遠(yuǎn)程,使用命令git push origin <tagname>
:
$ git push origin v1.0
Total 0 (delta 0), reused 0 (delta 0)
To git@github.com:honeywolf/learngit.git
* [new tag] v1.0 -> v1.0
或者一次性推送全部尚未推送到遠(yuǎn)程的本地標(biāo)簽:
git push origin --tags
Total 0 (delta 0), reused 0 (delta 0)
To git@github.com:honeywolf/learngit.git
* [new tag] v0.9 -> v0.9
如果標(biāo)簽已經(jīng)推送到遠(yuǎn)程,要?jiǎng)h除遠(yuǎn)程標(biāo)簽就麻煩一點(diǎn),先從本地刪除:
git tag -d v0.9
Deleted tag 'v0.9' (was 41b696e)
然后從遠(yuǎn)程刪除。刪除命令也是push,但是格式如下:
$ git push origin :refs/tags/v0.9
To git@github.com:honeywolf/learngit.git
- [deleted] v0.9
要看看是否真的從遠(yuǎn)程刪除了標(biāo)簽,可以登錄GitHub查看。
小結(jié)
- 命令
git push origin <tagname>
可以推送一個(gè)本地標(biāo)簽 - 命令
git push origin --tags
可以推送全部未推送過的本地標(biāo)簽; - 命令
git tag -d <tagname>
可以刪除一個(gè)本地標(biāo)簽; - 命令
git push origin :refs/tags/<tagname>
可以刪除一個(gè)遠(yuǎn)程標(biāo)簽。
使用GitHub
我們一直用GitHub作為免費(fèi)的倉(cāng)庫(kù),如果是個(gè)人開源項(xiàng)目,放到GitHub上是完全沒有問題的。其實(shí)GitHub還是一個(gè)開源協(xié)作社區(qū),通過GitHub,即可以讓別人參與你的 開源項(xiàng)目,也可以參與別人的開源項(xiàng)目。
在GitHub出現(xiàn)以前,開源項(xiàng)目開源容易,但讓廣大人民群眾參與進(jìn)來比較困難,因?yàn)橐獏⑴c,就要提交代碼,而給每個(gè)想提交代碼的群眾都開一個(gè)賬號(hào)那是不現(xiàn)實(shí)的,因此,群眾也僅限于報(bào)個(gè)bug,即使能改掉bug,也只能把diff文件用郵件發(fā)過去,很不方便。
但是在GitHub上,利用Git及其強(qiáng)大的克隆和分支功能,廣大人民群眾真正可以第一次自由參與各種開源項(xiàng)目了。
如何參與一個(gè)開源項(xiàng)目呢?比如人氣極高的booststrap項(xiàng)目,這是一個(gè)非常強(qiáng)大的CSS框架,你可以訪問它的項(xiàng)目主頁https://github.com/twbs/bootstrap,點(diǎn)擊"Fork"就在自己的帳號(hào)下克隆了一個(gè)bootstrap倉(cāng)庫(kù),然后,從自己的帳號(hào)下clone:
git clone git@github.com:honeywolf/bootstrap.git
一定要從自己的帳號(hào)下clone倉(cāng)庫(kù),這樣你才能推送修改。如果從bootstrap作者的倉(cāng)庫(kù)地址git@github.com:twbs/bootstrap.git
克隆,因?yàn)闆]有權(quán)限,你將不能推送修改。
如果你希望修復(fù)bootstrap的一個(gè)bug,或者新增一個(gè)功能,立刻就可以開始干活,干完后,往自己的倉(cāng)庫(kù)推送。
如果你希望bootstrap的官方庫(kù)能接受你的修改,你就可以在GitHub上發(fā)起一個(gè)pull request。當(dāng)然,對(duì)方是否接受你的pull request就不一定了。
小結(jié)
- 在GitHub上,可以任意Fork開源倉(cāng)庫(kù);
- 自己擁有Fork后的倉(cāng)庫(kù)的讀寫權(quán)限;
- 可以推送pull request給官方倉(cāng)庫(kù)來貢獻(xiàn)代碼。
自定義Git
在安裝Git一節(jié)中,我們已經(jīng)配置了user.name
和user.email
,實(shí)際上,Git還有很多可配置項(xiàng)。比如,讓Git顯示顏色,會(huì)讓命令輸出看起來更醒目:
$ git status
On branch dev
Your branch is up-to-date with 'origin/dev'.
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
modified: readme.txt
文件名就會(huì)標(biāo)上顏色。我們后序還會(huì)介紹如何更好的配置Git,以便讓你的工作更高效。
忽略特殊文件
有些時(shí)候,你必須把某些文件放到Git工作目錄中,但是又不想提交它們,比如保存了數(shù)據(jù)庫(kù)密碼的配置文件啦,等等,每次git status
都會(huì)顯示Untracked files ...
,有強(qiáng)迫癥的童鞋心里肯定不爽。
好在Git考慮到了大家的感受,這個(gè)問題解決起來也很簡(jiǎn)單,在Git工作區(qū)的根目錄下創(chuàng)建一個(gè)特殊的.gitignore
文件,GitHub已經(jīng)為我們準(zhǔn)備了各種配置文件,只要組合一下就可以使用了。所有配置文件可以直接在線瀏覽:https://github.com/github/gitignore
忽略文件的原則是:
- 忽略操作系統(tǒng)自動(dòng)生成的文件,比如縮略圖等;
- 忽略編譯生成的中間文件、可執(zhí)行文件等,也就是如果一個(gè)文件是通過另一個(gè)文件自動(dòng)生成的,那自動(dòng)生成的文件就沒必要放進(jìn)版本庫(kù),比如Java編譯產(chǎn)生的
.class
文件; - 忽略你自己的帶有敏感信息的配置文件,比如存放口令的配置文件。
舉個(gè)例子:
假設(shè)你在Windows下進(jìn)行Python開發(fā),Windows會(huì)自動(dòng)在有圖片的目錄下生成隱藏的縮略圖文件,如果有自定義目錄,目錄下就會(huì)有Desktop.ini文件,因此你需要忽略Windows自動(dòng)生成的垃圾文件:
# Windows:
Thumbs.db
ehthumbs.db
Desktop.ini
然后,繼續(xù)忽略Python編譯產(chǎn)生的.pyc
、.pyo
、dist
等文件或目錄:
# Python:
*.py[cod]
*.so
*.egg
*.egg-info
dist
build
加上你自己定義的文件,最終得到一個(gè)完整的.gitignore
文件,內(nèi)容如下:
# Windows:
Thumbs.db
ehthumbs.db
Desktop.ini
# Python:
*.py[cod]
*.so
*.egg
*.egg-info
dist
build
# My configurations:
db.ini
deploy_key_rsa
最后一步就是把.gitignore也提交到Git,就完成了!當(dāng)然檢驗(yàn).gitignore的標(biāo)準(zhǔn)是git status命令是不是說working directory clean。
使用Windows的童鞋注意了,如果你在資源管理器里新建一個(gè).gitignore文件,它會(huì)非常弱智地提示你必須輸入文件名,但是在文本編輯器里“保存”或者“另存為”就可以把文件保存為.gitignore了。
OC語言的.gitignore
# Xcode
#
# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
## Build generated
build/
DerivedData/
## Various settings
*.pbxuser
!default.pbxuser
*.mode1v3
!default.mode1v3
*.mode2v3
!default.mode2v3
*.perspectivev3
!default.perspectivev3
xcuserdata/
## Other
*.moved-aside
*.xcuserstate
## Obj-C/Swift specific
*.hmap
*.ipa
# CocoaPods
#
# We recommend against adding the Pods directory to your .gitignore. However
# you should judge for yourself, the pros and cons are mentioned at:
# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
#
# Pods/
# Carthage
#
# Add this line if you want to avoid checking in source code from Carthage dependencies.
# Carthage/Checkouts
Carthage/Build
# fastlane
#
# It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the
# screenshots whenever they are needed.
# For more information about the recommended setup visit:
# https://github.com/fastlane/fastlane/blob/master/docs/Gitignore.md
fastlane/report.xml
fastlane/screenshots
小結(jié)
- 忽略某些文件時(shí),需要編寫.gitignore;
- .gitignore文件本身要放到版本庫(kù)里,并且可以對(duì).gitignore做版本管理!
配置別名
有沒有經(jīng)常敲錯(cuò)命令?比如git status
? status
這個(gè)單詞真心不好記。
如果敲git st
就表示git status
那就簡(jiǎn)單多了,當(dāng)然這種偷懶的辦法我們是極力贊成的。
我們只需要敲一行代碼,告訴Git,以后st
就是status
:
$ git config --global alias.st status
當(dāng)然還有別的命令可以簡(jiǎn)寫,很多人都用co表示checkout,ci表示commit,br表示branch:
$ git config --global alias.co checkout
$ git config --global alias.ci commit
$ git config --global alias.br branch
--global參數(shù)是全局參數(shù),也就是這些命令在這臺(tái)電腦的所有Git倉(cāng)庫(kù)下都有用。
在撤銷修改一節(jié)中,我們知道,命令git reset HEAD file可以把暫存區(qū)的修改撤銷掉(unstage),重新放回工作區(qū)。既然是一個(gè)unstage操作,就可以配置一個(gè)unstage別名:
$ git config --global alias.unstage 'reset HEAD'
配置一個(gè)git last,讓其顯示最后一次提交信息:
$ git config --global alias.last 'log -1'
甚至還有人喪心病狂地把lg配置成了:
git config --global alias.lg "log --color --graph -- pretty=format:'%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr) %C(bold blue)<%an>%Creset' --abbrev-commit"
配置文件
配置Git的時(shí)候,加上--global是針對(duì)當(dāng)前用戶起作用的,如果不加,那只針對(duì)當(dāng)前的倉(cāng)庫(kù)起作用。
配置文件放哪了?每個(gè)倉(cāng)庫(kù)的Git配置文件都放在.git/config文件中:
$ cat .git/config
[core]
repositoryformatversion = 0
filemode = true
bare = false
logallrefupdates = true
ignorecase = true
precomposeunicode = true
[remote "origin"]
url = git@github.com:honeywolf/learngit.git
fetch = +refs/heads/*:refs/remotes/origin/*
[branch "master"]
remote = origin
merge = refs/heads/master
[branch "dev"]
remote = origin
merge = refs/heads/dev
別名就在[alias]后面,要?jiǎng)h除別名,直接把對(duì)應(yīng)的行刪掉即可。
而當(dāng)前用戶的Git配置文件放在用戶主目錄下的一個(gè)隱藏文件.gitconfig中:
$ cat .gitconfig
[filter "lfs"]
clean = git-lfs clean %f
smudge = git-lfs smudge %f
required = true
[user]
name = honeywolf
email = adam.honey@ydx.hk
[color]
ui = true
[alias]
st = status
co = checkout
ci = commit
br = branch
unstage = reset HEAD
last = log -1
lg = log --color --graph --pretty=format:'%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr) %C(bold blue)<%an>%Creset' --abbrev-commit
配置別名也可以直接修改這個(gè)文件,如果改錯(cuò)了,可以刪掉文件重新通過命令配置。
小結(jié)
給Git配置好別名,就可以輸入命令時(shí)偷個(gè)懶。我們鼓勵(lì)偷懶 。