Git使用

聲明:這篇文章來源于廖雪峰老師的官方網(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 GPLcommit id8ae8b87。現(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_rsaid_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_rsaid_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ù):

hah
hah

現(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),而是指向mastermaster才是指向提交的,所以,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)程orgindev分支到本地,于是他用這個(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è)置devorigin/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é)作的工作模式通常是這樣的:

  1. 首先,自己試圖用 git push origin branch-name推送自己的修改;
  2. 如果推送失敗,則因?yàn)檫h(yuǎn)程分支比你的本地更新,需要用git pull試圖合并
  3. 如果合并有沖突,則解決沖突,并在本地提交;
  4. 沒有沖突或者解決掉沖突后,再用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.nameuser.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

忽略文件的原則是:

  1. 忽略操作系統(tǒng)自動(dòng)生成的文件,比如縮略圖等;
  2. 忽略編譯生成的中間文件、可執(zhí)行文件等,也就是如果一個(gè)文件是通過另一個(gè)文件自動(dòng)生成的,那自動(dòng)生成的文件就沒必要放進(jìn)版本庫(kù),比如Java編譯產(chǎn)生的.class文件;
  3. 忽略你自己的帶有敏感信息的配置文件,比如存放口令的配置文件。

舉個(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.pyodist等文件或目錄:

# 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ì)偷懶 。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,533評(píng)論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,055評(píng)論 3 414
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,365評(píng)論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,561評(píng)論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,346評(píng)論 6 404
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 54,889評(píng)論 1 321
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 42,978評(píng)論 3 439
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,118評(píng)論 0 286
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,637評(píng)論 1 333
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,558評(píng)論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,739評(píng)論 1 369
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,246評(píng)論 5 355
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 43,980評(píng)論 3 346
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,362評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,619評(píng)論 1 280
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,347評(píng)論 3 390
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,702評(píng)論 2 370

推薦閱讀更多精彩內(nèi)容

  • 原文地址主要用到的命令: git config user.name 設(shè)置用戶名 git config user....
    AFinalStone閱讀 478評(píng)論 0 2
  • 轉(zhuǎn)載自:http://www.open-open.com/lib/view/open1414396787325.h...
    Bbooo閱讀 462評(píng)論 0 3
  • 一:Git是什么? Git是目前世界上最先進(jìn)的分布式版本控制系統(tǒng)。 二:SVN與Git的最主要的區(qū)別? SVN是集...
    毛子阿卡西閱讀 256評(píng)論 0 1
  • 一:Git是什么? Git是目前世界上最先進(jìn)的分布式版本控制系統(tǒng)。 二:SVN與Git的最主要的區(qū)別? SVN是集...
    傲慢二鍋頭閱讀 422評(píng)論 0 0
  • 4fa4c4103e6c閱讀 282評(píng)論 0 0