Git學(xué)習(xí)

引用

感謝廖海峰老師的精彩文章
文章出處https://www.liaoxuefeng.com/wiki/896043488029600/1317161920364578

Git簡(jiǎn)介

很多人都知道,Linus在1991年創(chuàng)建了開源的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本人通過手工方式合并代碼刁品!

你也許會(huì)想泣特,為什么Linus不把Linux代碼放到版本控制系統(tǒng)里呢?不是有CVS挑随、SVN這些免費(fèi)的版本控制系統(tǒng)嗎状您?因?yàn)長(zhǎng)inus堅(jiān)定地反對(duì)CVS和SVN,這些集中式的版本控制系統(tǒng)不但速度慢兜挨,而且必須聯(lián)網(wǎng)才能使用膏孟。有一些商用的版本控制系統(tǒng),雖然比CVS拌汇、SVN好用柒桑,但那是付費(fèi)的,和Linux的開源精神不符担猛。

不過幕垦,到了2002年,Linux系統(tǒng)已經(jīng)發(fā)展了十年了傅联,代碼庫(kù)之大讓Linus很難繼續(xù)通過手工方式管理了先改,社區(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年就被打破了比驻,原因是Linux社區(qū)牛人聚集该溯,不免沾染了一些梁山好漢的江湖習(xí)氣。開發(fā)Samba的Andrew試圖破解BitKeeper的協(xié)議(這么干的其實(shí)也不只他一個(gè))别惦,被BitMover公司發(fā)現(xiàn)了(監(jiān)控工作做得不錯(cuò)1奋浴),于是BitMover公司怒了掸掸,要收回Linux社區(qū)的免費(fèi)使用權(quán)氯庆。

Linus可以向BitMover公司道個(gè)歉蹭秋,保證以后嚴(yán)格管教弟兄們,嗯堤撵,這是不可能的仁讨。實(shí)際情況是這樣的:

Linus花了兩周時(shí)間自己用C寫了一個(gè)分布式版本控制系統(tǒng),這就是Git实昨!一個(gè)月之內(nèi)洞豁,Linux系統(tǒng)的源碼已經(jīng)由Git管理了!牛是怎么定義的呢荒给?大家可以體會(huì)一下丈挟。

Git迅速成為最流行的分布式版本控制系統(tǒng),尤其是2008年锐墙,GitHub網(wǎng)站上線了礁哄,它為開源項(xiàng)目免費(fèi)提供Git存儲(chǔ),無數(shù)開源項(xiàng)目開始遷移至GitHub溪北,包括jQuery桐绒,PHP,Ruby等等之拨。

歷史就是這么偶然茉继,如果不是當(dāng)年BitMover公司威脅Linux社區(qū),可能現(xiàn)在我們就沒有免費(fèi)而超級(jí)好用的Git了蚀乔。

Linus一直痛恨的CVS及SVN都是集中式的版本控制系統(tǒng)烁竭,而Git是分布式版本控制系統(tǒng),集中式和分布式版本控制系統(tǒng)有什么區(qū)別呢吉挣?

先說集中式版本控制系統(tǒng)派撕,版本庫(kù)是集中存放在中央服務(wù)器的,而干活的時(shí)候睬魂,用的都是自己的電腦,所以要先從中央服務(wù)器取得最新的版本氯哮,然后開始干活际跪,干完活了,再把自己的活推送給中央服務(wù)器喉钢。中央服務(wù)器就好比是一個(gè)圖書館姆打,你要改一本書,必須先從圖書館借出來肠虽,然后回到家自己改幔戏,改完了,再放回圖書館税课。

image.png

集中式版本控制系統(tǒng)最大的毛病就是必須聯(lián)網(wǎng)才能工作评抚,如果在局域網(wǎng)內(nèi)還好豹缀,帶寬夠大,速度夠快慨代,可如果在互聯(lián)網(wǎng)上,遇到網(wǎng)速慢的話啸如,可能提交一個(gè)10M的文件就需要5分鐘侍匙,這還不得把人給憋死啊。

那分布式版本控制系統(tǒng)與集中式版本控制系統(tǒng)有何不同呢叮雳?首先想暗,分布式版本控制系統(tǒng)根本沒有“中央服務(wù)器”,每個(gè)人的電腦上都是一個(gè)完整的版本庫(kù)帘不,這樣说莫,你工作的時(shí)候,就不需要聯(lián)網(wǎng)了寞焙,因?yàn)榘姹編?kù)就在你自己的電腦上储狭。既然每個(gè)人電腦上都有一個(gè)完整的版本庫(kù),那多個(gè)人如何協(xié)作呢捣郊?比方說你在自己電腦上改了文件A辽狈,你的同事也在他的電腦上改了文件A,這時(shí)呛牲,你們倆之間只需把各自的修改推送給對(duì)方刮萌,就可以互相看到對(duì)方的修改了。

和集中式版本控制系統(tǒng)相比娘扩,分布式版本控制系統(tǒng)的安全性要高很多着茸,因?yàn)槊總€(gè)人電腦里都有完整的版本庫(kù),某一個(gè)人的電腦壞掉了不要緊琐旁,隨便從其他人那里復(fù)制一個(gè)就可以了涮阔。而集中式版本控制系統(tǒng)的中央服務(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ù)器的作用僅僅是用來方便“交換”大家的修改碱工,沒有它大家也一樣干活娃承,只是交換修改不方便而已奏夫。

image.png

當(dāng)然,Git的優(yōu)勢(shì)不單是不必聯(lián)網(wǎng)這么簡(jiǎn)單历筝,后面我們還會(huì)看到Git極其強(qiáng)大的分支管理酗昼,把SVN等遠(yuǎn)遠(yuǎn)拋在了后面。

CVS作為最早的開源而且免費(fèi)的集中式版本控制系統(tǒng)梳猪,直到現(xiàn)在還有不少人在用麻削。由于CVS自身設(shè)計(jì)的問題,會(huì)造成提交文件不完整春弥,版本庫(kù)莫名其妙損壞的情況呛哟。同樣是開源而且免費(fèi)的SVN修正了CVS的一些穩(wěn)定性問題,是目前用得最多的集中式版本庫(kù)控制系統(tǒng)匿沛。

除了免費(fèi)的外扫责,還有收費(fèi)的集中式版本控制系統(tǒng),比如IBM的ClearCase(以前是Rational公司的逃呼,被IBM收購(gòu)了)鳖孤,特點(diǎn)是安裝比Windows還大,運(yùn)行比蝸牛還慢蜘渣,能用ClearCase的一般是世界500強(qiáng)淌铐,他們有個(gè)共同的特點(diǎn)是財(cái)大氣粗,或者人傻錢多蔫缸。

微軟自己也有一個(gè)集中式版本控制系統(tǒng)叫VSS腿准,集成在Visual Studio中。由于其反人類的設(shè)計(jì)拾碌,連微軟自己都不好意思用了吐葱。

分布式版本控制系統(tǒng)除了Git以及促使Git誕生的BitKeeper外,還有類似Git的Mercurial和Bazaar等校翔。這些分布式版本控制系統(tǒng)各有特點(diǎn)弟跑,但最快、最簡(jiǎn)單也最流行的依然是Git防症!

安裝Git

最早Git是在Linux上開發(fā)的孟辑,很長(zhǎng)一段時(shí)間內(nèi),Git也只能在Linux和Unix系統(tǒng)上跑蔫敲。不過饲嗽,慢慢地有人把它移植到了Windows上。現(xiàn)在奈嘿,Git可以在Linux貌虾、Unix、Mac和Windows這幾大平臺(tái)上正常運(yùn)行了裙犹。

要使用Git尽狠,第一步當(dāng)然是安裝Git了衔憨。根據(jù)你當(dāng)前使用的平臺(tái)來閱讀下面的文字:

在Linux上安裝Git

首先,你可以試著輸入git袄膏,看看系統(tǒng)有沒有安裝Git:

$ git
The program 'git' is currently not installed. You can install it by typing:
sudo apt-get install git

像上面的命令践图,有很多Linux會(huì)友好地告訴你Git沒有安裝,還會(huì)告訴你如何安裝Git哩陕。

如果你碰巧用Debian或Ubuntu Linux平项,通過一條sudo apt-get install git就可以直接完成Git的安裝,非常簡(jiǎn)單悍及。

老一點(diǎn)的Debian或Ubuntu Linux,要把命令改為sudo apt-get install git-core接癌,因?yàn)橐郧坝袀€(gè)軟件也叫GIT(GNU Interactive Tools)心赶,結(jié)果Git就只能叫git-core了。由于Git名氣實(shí)在太大缺猛,后來就把GNU Interactive Tools改成gnuit缨叫,git-core正式改為git

如果是其他Linux版本荔燎,可以直接通過源碼安裝耻姥。先從Git官網(wǎng)下載源碼刊咳,然后解壓打肝,依次輸入:./config蕴忆,make拌阴,sudo make install這幾個(gè)命令安裝就好了淮韭。

在Mac OS X上安裝Git

如果你正在使用Mac做開發(fā)菱涤,有兩種安裝Git的方法晶通。

一是安裝homebrew项钮,然后通過homebrew安裝Git渣叛,具體方法請(qǐng)參考homebrew的文檔:http://brew.sh/丈秩。

第二種方法更簡(jiǎn)單,也是推薦的方法淳衙,就是直接從AppStore安裝Xcode蘑秽,Xcode集成了Git,不過默認(rèn)沒有安裝箫攀,你需要運(yùn)行Xcode肠牲,選擇菜單“Xcode”->“Preferences”,在彈出窗口中找到“Downloads”匠童,選擇“Command Line Tools”埂材,點(diǎn)“Install”就可以完成安裝了。

image.png

Xcode是Apple官方IDE汤求,功能非常強(qiáng)大俏险,是開發(fā)Mac和iOS App的必選裝備严拒,而且是免費(fèi)的!

在Windows上安裝Git

在Windows上使用Git竖独,可以從Git官網(wǎng)直接下載安裝程序裤唠,然后按默認(rèn)選項(xiàng)安裝即可。

安裝完成后莹痢,在開始菜單里找到“Git”->“Git Bash”种蘸,蹦出一個(gè)類似命令行窗口的東西,就說明Git安裝成功竞膳!

image.png

安裝完成后航瞭,還需要最后一步設(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)家門:你的名字和Email地址。你也許會(huì)擔(dān)心锉走,如果有人故意冒充別人怎么辦滨彻?這個(gè)不必?fù)?dān)心,首先我們相信大家都是善良無知的群眾挪蹭,其次亭饵,真的有冒充的也是有辦法可查的。

注意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ù)呢只冻?版本庫(kù)又名倉(cāng)庫(kù),英文名repository计技,你可以簡(jiǎn)單理解成一個(gè)目錄喜德,這個(gè)目錄里面的所有文件都可以被Git管理起來,每個(gè)文件的修改垮媒、刪除舍悯,Git都能跟蹤,以便任何時(shí)刻都可以追蹤歷史睡雇,或者在將來某個(gè)時(shí)刻可以“還原”萌衬。

所以,創(chuàng)建一個(gè)版本庫(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祠墅。

如果你使用Windows系統(tǒng),為了避免遇到各種莫名其妙的問題歌径,請(qǐng)確保目錄名(包括父目錄)不包含中文毁嗦。
第二步,通過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ù)(empty Git repository)狗准,細(xì)心的讀者可以發(fā)現(xiàn)當(dāng)前目錄下多了一個(gè).git的目錄,這個(gè)目錄是Git來跟蹤管理版本庫(kù)的茵肃,沒事千萬不要手動(dòng)修改這個(gè)目錄里面的文件腔长,不然改亂了,就把Git倉(cāng)庫(kù)給破壞了验残。

如果你沒有看到.git目錄饼酿,那是因?yàn)檫@個(gè)目錄默認(rèn)是隱藏的,用ls -ah命令就可以看見胚膊。也不一定必須在空目錄下創(chuàng)建Git倉(cāng)庫(kù),選擇一個(gè)已經(jīng)有東西的目錄也是可以的想鹰。不過紊婉,不建議你使用自己正在開發(fā)的公司項(xiàng)目來學(xué)習(xí)Git,否則造成的一切后果概不負(fù)責(zé)辑舷。

把文件添加到版本庫(kù)

首先這里再明確一下喻犁,所有的版本控制系統(tǒng),其實(shí)只能跟蹤文本文件的改動(dòng)何缓,比如TXT文件肢础,網(wǎng)頁(yè),所有的程序代碼等等碌廓,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文件的改動(dòng)的叁巨,前面我們舉的例子只是為了演示,如果要真正使用版本控制系統(tǒng)呐籽,就要以純文本方式編寫文件锋勺。

因?yàn)槲谋臼怯芯幋a的,比如中文有常用的GBK編碼狡蝶,日文有Shift_JIS編碼庶橱,如果沒有歷史遺留問題,強(qiáng)烈建議使用標(biāo)準(zhǔn)的UTF-8編碼贪惹,所有語(yǔ)言使用同一種編碼苏章,既沒有沖突,又被所有平臺(tái)所支持奏瞬。

使用Windows的童鞋要特別注意:

千萬不要使用Windows自帶的記事本編輯任何文本文件枫绅。原因是Microsoft開發(fā)記事本的團(tuán)隊(duì)使用了一個(gè)非常弱智的行為來保存UTF-8編碼的文件,他們自作聰明地在每個(gè)文件開頭添加了0xefbbbf(十六進(jìn)制)的字符硼端,你會(huì)遇到很多不可思議的問題并淋,比如,網(wǎng)頁(yè)第一行可能會(huì)顯示一個(gè)“?”珍昨,明明正確的程序一編譯就報(bào)語(yǔ)法錯(cuò)誤县耽,等等,都是由記事本的弱智行為帶來的镣典。建議你下載Notepad++代替記事本兔毙,不但功能強(qiáng)大,而且免費(fèi)兄春!記得把Notepad++的默認(rèn)編碼設(shè)置為UTF-8 without BOM即可:

image.png

言歸正傳澎剥,現(xiàn)在我們編寫一個(gè)readme.txt文件,內(nèi)容如下:

Git is a version control system.
Git is free software.
一定要放到learngit目錄下(子目錄也行)神郊,因?yàn)檫@是一個(gè)Git倉(cāng)庫(kù)肴裙,放到其他地方Git再厲害也找不到這個(gè)文件。

和把大象放到冰箱需要3步相比涌乳,把一個(gè)文件放到Git倉(cāng)庫(kù)只需要兩步蜻懦。

第一步,用命令git add告訴Git夕晓,把文件添加到倉(cāng)庫(kù):

$ git add readme.txt
執(zhí)行上面的命令宛乃,沒有任何顯示,這就對(duì)了,Unix的哲學(xué)是“沒有消息就是好消息”征炼,說明添加成功析既。

第二步,用命令git commit告訴Git谆奥,把文件提交到倉(cāng)庫(kù):

$ git commit -m "wrote a readme file"
[master (root-commit) eaadf4e] wrote a readme file
1 file changed, 2 insertions(+)
create mode 100644 readme.txt
簡(jiǎn)單解釋一下git commit命令眼坏,-m后面輸入的是本次提交的說明,可以輸入任意內(nèi)容酸些,當(dāng)然最好是有意義的宰译,這樣你就能從歷史記錄里方便地找到改動(dòng)記錄。

嫌麻煩不想輸入-m "xxx"行不行魄懂?確實(shí)有辦法可以這么干沿侈,但是強(qiáng)烈不建議你這么干,因?yàn)檩斎胝f明對(duì)自己對(duì)別人閱讀都很重要市栗。實(shí)在不想輸入說明的童鞋請(qǐng)自行Google缀拭,我不告訴你這個(gè)參數(shù)。

git commit命令執(zhí)行成功后會(huì)告訴你填帽,1 file changed:1個(gè)文件被改動(dòng)(我們新添加的readme.txt文件)蛛淋;2 insertions:插入了兩行內(nèi)容(readme.txt有兩行內(nèi)容)。
為什么Git添加文件需要add篡腌,commit一共兩步呢铣鹏?因?yàn)閏ommit可以一次提交很多文件,所以你可以多次add不同的文件哀蘑,比如:

git add file1.txt git add file2.txt file3.txt
$ git commit -m "add 3 files."
疑難解答
Q:輸入git add readme.txt,得到錯(cuò)誤:fatal: not a git repository (or any of the parent directories)葵第。

A:Git命令必須在Git倉(cāng)庫(kù)目錄內(nèi)執(zhí)行(git init除外)绘迁,在倉(cāng)庫(kù)目錄外執(zhí)行是沒有意義的。

Q:輸入git add readme.txt卒密,得到錯(cuò)誤fatal: pathspec 'readme.txt' did not match any files缀台。

A:添加某個(gè)文件時(shí),該文件必須在當(dāng)前目錄下存在哮奇,用ls或者dir命令查看當(dāng)前目錄的文件膛腐,看看文件是否存在,或者是否寫錯(cuò)了文件名鼎俘。

小結(jié)
現(xiàn)在總結(jié)一下今天學(xué)的兩點(diǎn)內(nèi)容:

初始化一個(gè)Git倉(cāng)庫(kù)哲身,使用git init命令。

添加文件到Git倉(cāng)庫(kù)贸伐,分兩步:

使用命令git add <file>勘天,注意,可反復(fù)多次使用,添加多個(gè)文件脯丝;
使用命令git commit -m <message>商膊,完成。

時(shí)光機(jī)穿梭

我們已經(jīng)成功地添加并提交了一個(gè)readme.txt文件宠进,現(xiàn)在晕拆,是時(shí)候繼續(xù)工作了,于是材蹬,我們繼續(xù)修改readme.txt文件实幕,改成如下內(nèi)容:

Git is a distributed version control system.
Git is free software.
現(xiàn)在,運(yùn)行g(shù)it status命令看看結(jié)果:

$ 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命令可以讓我們時(shí)刻掌握倉(cāng)庫(kù)當(dāng)前的狀態(tài)赚导,上面的命令輸出告訴我們茬缩,readme.txt被修改過了,但還沒有準(zhǔn)備提交的修改吼旧。

雖然Git告訴我們r(jià)eadme.txt被修改了凰锡,但如果能看看具體修改了什么內(nèi)容,自然是很好的圈暗。比如你休假兩周從國(guó)外回來掂为,第一天上班時(shí),已經(jīng)記不清上次怎么修改的readme.txt员串,所以脊框,需要用git diff這個(gè)命令看看:

$ git diff readme.txt
diff --git a/readme.txt b/readme.txt
index 46d49bf..9247db6 100644
--- a/readme.txt
+++ b/readme.txt
@@ -1,2 +1,2 @@
-Git is a version control system.
+Git is a distributed version control system.
Git is free software.
git diff顧名思義就是查看difference,顯示的格式正是Unix通用的diff格式瞭恰,可以從上面的命令輸出看到腮郊,我們?cè)诘谝恍刑砑恿艘粋€(gè)distributed單詞。

知道了對(duì)readme.txt作了什么修改后渺鹦,再把它提交到倉(cāng)庫(kù)就放心多了扰法,提交修改和提交新文件是一樣的兩步,第一步是git add:

$ git add readme.txt
同樣沒有任何輸出毅厚。在執(zhí)行第二步git commit之前塞颁,我們?cè)龠\(yùn)行g(shù)it status看看當(dāng)前倉(cāng)庫(kù)的狀態(tài):

$ git status
On branch master
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)

modified:   readme.txt

git status告訴我們,將要被提交的修改包括readme.txt吸耿,下一步祠锣,就可以放心地提交了:

$ git commit -m "add distributed"
[master e475afc] add distributed
1 file changed, 1 insertion(+), 1 deletion(-)
提交后,我們?cè)儆胓it status命令看看倉(cāng)庫(kù)的當(dāng)前狀態(tài):

$ git status
On branch master
nothing to commit, working tree clean
Git告訴我們當(dāng)前沒有需要提交的修改咽安,而且伴网,工作目錄是干凈(working tree clean)的。
小結(jié)
要隨時(shí)掌握工作區(qū)的狀態(tài)妆棒,使用git status命令是偷。

如果git status告訴你有文件被修改過拳氢,用git diff可以查看修改內(nèi)容。

版本回退

現(xiàn)在蛋铆,你已經(jīng)學(xué)會(huì)了修改文件馋评,然后把修改提交到Git版本庫(kù),現(xiàn)在刺啦,再練習(xí)一次留特,修改readme.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"
[master 1094adb] append GPL
1 file changed, 1 insertion(+), 1 deletion(-)
像這樣,你不斷對(duì)文件進(jìn)行修改玛瘸,然后不斷提交修改到版本庫(kù)里蜕青,就好比玩RPG游戲時(shí),每通過一關(guān)就會(huì)自動(dòng)把游戲狀態(tài)存盤糊渊,如果某一關(guān)沒過去右核,你還可以選擇讀取前一關(guān)的狀態(tài)。有些時(shí)候渺绒,在打Boss之前贺喝,你會(huì)手動(dòng)存盤,以便萬一打Boss失敗了宗兼,可以從最近的地方重新開始躏鱼。Git也是一樣,每當(dāng)你覺得文件修改到一定程度的時(shí)候殷绍,就可以“保存一個(gè)快照”染苛,這個(gè)快照在Git中被稱為commit。一旦你把文件改亂了主到,或者誤刪了文件茶行,還可以從最近的一個(gè)commit恢復(fù),然后繼續(xù)工作登钥,而不是把幾個(gè)月的工作成果全部丟失拢军。

現(xiàn)在,我們回顧一下readme.txt文件一共有幾個(gè)版本被提交到Git倉(cāng)庫(kù)里了:

版本1:wrote a readme file

Git is a version control system.
Git is free software.
版本2:add distributed

Git is a distributed version control system.
Git is free software.
版本3:append GPL

Git is a distributed version control system.
Git is free software distributed under the GPL.
當(dāng)然了怔鳖,在實(shí)際工作中,我們腦子里怎么可能記得一個(gè)幾千行的文件每次都改了什么內(nèi)容固蛾,不然要版本控制系統(tǒng)干什么结执。版本控制系統(tǒng)肯定有某個(gè)命令可以告訴我們歷史記錄,在Git中艾凯,我們用git log命令查看:

$ git log
commit 1094adb7b9b3807259d8cb349e7df1d4d6477073 (HEAD -> master)
Author: Michael Liao askxuefeng@gmail.com
Date: Fri May 18 21:06:15 2018 +0800

append GPL

commit e475afc93c209a690c39c13a46716e8fa000c366
Author: Michael Liao askxuefeng@gmail.com
Date: Fri May 18 21:03:36 2018 +0800

add distributed

commit eaadf4e385e865d25c48e7ca9c8395c3f7dfaef0
Author: Michael Liao askxuefeng@gmail.com
Date: Fri May 18 20:59:18 2018 +0800

wrote a readme file

git log命令顯示從最近到最遠(yuǎn)的提交日志献幔,我們可以看到3次提交,最近的一次是append GPL趾诗,上一次是add distributed蜡感,最早的一次是wrote a readme file蹬蚁。

如果嫌輸出信息太多,看得眼花繚亂的郑兴,可以試試加上--pretty=oneline參數(shù):

$ git log --pretty=oneline
1094adb7b9b3807259d8cb349e7df1d4d6477073 (HEAD -> master) append GPL
e475afc93c209a690c39c13a46716e8fa000c366 add distributed
eaadf4e385e865d25c48e7ca9c8395c3f7dfaef0 wrote a readme file
需要友情提示的是犀斋,你看到的一大串類似1094adb...的是commit id(版本號(hào)),和SVN不一樣情连,Git的commit id不是1叽粹,2,3……遞增的數(shù)字却舀,而是一個(gè)SHA1計(jì)算出來的一個(gè)非常大的數(shù)字虫几,用十六進(jìn)制表示,而且你看到的commit id和我的肯定不一樣挽拔,以你自己的為準(zhǔn)辆脸。為什么commit id需要用這么一大串?dāng)?shù)字表示呢?因?yàn)镚it是分布式的版本控制系統(tǒng)螃诅,后面我們還要研究多人在同一個(gè)版本庫(kù)里工作啡氢,如果大家都用1,2州刽,3……作為版本號(hào)空执,那肯定就沖突了。

每提交一個(gè)新版本穗椅,實(shí)際上Git就會(huì)把它們自動(dòng)串成一條時(shí)間線辨绊。如果使用可視化工具查看Git歷史,就可以更清楚地看到提交歷史的時(shí)間線:


image.png

好了匹表,現(xiàn)在我們啟動(dòng)時(shí)光穿梭機(jī)门坷,準(zhǔn)備把readme.txt回退到上一個(gè)版本,也就是add distributed的那個(gè)版本袍镀,怎么做呢默蚌?

首先,Git必須知道當(dāng)前版本是哪個(gè)版本苇羡,在Git中绸吸,用HEAD表示當(dāng)前版本,也就是最新的提交1094adb...(注意我的提交ID和你的肯定不一樣)设江,上一個(gè)版本就是HEAD锦茁,上上一個(gè)版本就是HEAD,當(dāng)然往上100個(gè)版本寫100個(gè)比較容易數(shù)不過來叉存,所以寫成HEAD~100码俩。

現(xiàn)在,我們要把當(dāng)前版本append GPL回退到上一個(gè)版本add distributed歼捏,就可以使用git reset命令:

$ git reset --hard HEAD^
HEAD is now at e475afc add distributed
--hard參數(shù)有啥意義稿存?這個(gè)后面再講笨篷,現(xiàn)在你先放心使用。

看看readme.txt的內(nèi)容是不是版本add distributed:

$ cat readme.txt
Git is a distributed version control system.
Git is free software.
果然被還原了瓣履。

還可以繼續(xù)回退到上一個(gè)版本wrote a readme file率翅,不過且慢,讓我們用git log再看看現(xiàn)在版本庫(kù)的狀態(tài):

$ git log
commit e475afc93c209a690c39c13a46716e8fa000c366 (HEAD -> master)
Author: Michael Liao askxuefeng@gmail.com
Date: Fri May 18 21:03:36 2018 +0800

add distributed

commit eaadf4e385e865d25c48e7ca9c8395c3f7dfaef0
Author: Michael Liao askxuefeng@gmail.com
Date: Fri May 18 20:59:18 2018 +0800

wrote a readme file

最新的那個(gè)版本append GPL已經(jīng)看不到了拂苹!好比你從21世紀(jì)坐時(shí)光穿梭機(jī)來到了19世紀(jì)安聘,想再回去已經(jīng)回不去了,腫么辦瓢棒?

辦法其實(shí)還是有的浴韭,只要上面的命令行窗口還沒有被關(guān)掉,你就可以順著往上找啊找啊脯宿,找到那個(gè)append GPL的commit id是1094adb...念颈,于是就可以指定回到未來的某個(gè)版本:

$ git reset --hard 1094a
HEAD is now at 83b0afe append GPL
版本號(hào)沒必要寫全,前幾位就可以了连霉,Git會(huì)自動(dòng)去找榴芳。當(dāng)然也不能只寫前一兩位,因?yàn)镚it可能會(huì)找到多個(gè)版本號(hào)跺撼,就無法確定是哪一個(gè)了窟感。

再小心翼翼地看看readme.txt的內(nèi)容:

$ cat readme.txt
Git is a distributed version control system.
Git is free software distributed under the GPL.
果然,我胡漢三又回來了歉井。

Git的版本回退速度非呈疗恚快,因?yàn)镚it在內(nèi)部有個(gè)指向當(dāng)前版本的HEAD指針哩至,當(dāng)你回退版本的時(shí)候躏嚎,Git僅僅是把HEAD從指向append GPL:

┌────┐
│HEAD│
└────┘

└──> ○ append GPL

○ add distributed

○ wrote a readme file
改為指向add distributed:

┌────┐
│HEAD│
└────┘

│ ○ append GPL
│ │
└──> ○ add distributed

○ wrote a readme file
然后順便把工作區(qū)的文件更新了。所以你讓HEAD指向哪個(gè)版本號(hào)菩貌,你就把當(dāng)前版本定位在哪卢佣。

現(xiàn)在,你回退到了某個(gè)版本箭阶,關(guān)掉了電腦虚茶,第二天早上就后悔了,想恢復(fù)到新版本怎么辦仇参?找不到新版本的commit id怎么辦嘹叫?

在Git中,總是有后悔藥可以吃的冈敛。當(dāng)你用$ git reset --hard HEAD^回退到add distributed版本時(shí),再想恢復(fù)到append GPL鸣皂,就必須找到append GPL的commit id抓谴。Git提供了一個(gè)命令git reflog用來記錄你的每一次命令:

$ git reflog
e475afc HEAD@{1}: reset: moving to HEAD^
1094adb (HEAD -> master) HEAD@{2}: commit: append GPL
e475afc HEAD@{3}: commit: add distributed
eaadf4e HEAD@{4}: commit (initial): wrote a readme file
終于舒了口氣暮蹂,從輸出可知,append GPL的commit id是1094adb癌压,現(xiàn)在仰泻,你又可以乘坐時(shí)光機(jī)回到未來了。
小結(jié)
現(xiàn)在總結(jié)一下:

HEAD指向的版本就是當(dāng)前版本滩届,因此集侯,Git允許我們?cè)诎姹镜臍v史之間穿梭,使用命令git reset --hard commit_id帜消。

穿梭前棠枉,用git log可以查看提交歷史,以便確定要回退到哪個(gè)版本泡挺。

要重返未來辈讶,用git reflog查看命令歷史,以便確定要回到未來的哪個(gè)版本娄猫。

工作區(qū)和暫存區(qū)

Git和其他版本控制系統(tǒng)如SVN的一個(gè)不同之處就是有暫存區(qū)的概念贱除。

先來看名詞解釋。

工作區(qū)(Working Directory)
就是你在電腦里能看到的目錄媳溺,比如我的learngit文件夾就是一個(gè)工作區(qū):


image.png

版本庫(kù)(Repository)
工作區(qū)有一個(gè)隱藏目錄.git月幌,這個(gè)不算工作區(qū),而是Git的版本庫(kù)悬蔽。

Git的版本庫(kù)里存了很多東西扯躺,其中最重要的就是稱為stage(或者叫index)的暫存區(qū),還有Git為我們自動(dòng)創(chuàng)建的第一個(gè)分支master屯阀,以及指向master的一個(gè)指針叫HEAD缅帘。


image.png

分支和HEAD的概念我們以后再講。

前面講了我們把文件往Git版本庫(kù)里添加的時(shí)候难衰,是分兩步執(zhí)行的:

第一步是用git add把文件添加進(jìn)去钦无,實(shí)際上就是把文件修改添加到暫存區(qū);

第二步是用git commit提交更改盖袭,實(shí)際上就是把暫存區(qū)的所有內(nèi)容提交到當(dāng)前分支失暂。

因?yàn)槲覀儎?chuàng)建Git版本庫(kù)時(shí),Git自動(dòng)為我們創(chuàng)建了唯一一個(gè)master分支鳄虱,所以弟塞,現(xiàn)在,git commit就是往master分支上提交更改拙已。

你可以簡(jiǎn)單理解為决记,需要提交的文件修改通通放到暫存區(qū),然后倍踪,一次性提交暫存區(qū)的所有修改系宫。

俗話說索昂,實(shí)踐出真知。現(xiàn)在扩借,我們?cè)倬毩?xí)一遍椒惨,先對(duì)readme.txt做個(gè)修改,比如加上一行內(nèi)容:

Git is a distributed version control system.
Git is free software distributed under the GPL.
Git has a mutable index called stage.
然后潮罪,在工作區(qū)新增一個(gè)LICENSE文本文件(內(nèi)容隨便寫)康谆。

先用git status查看一下狀態(tài):

$ 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描睦,把readme.txt和LICENSE都添加后,用git status再查看一下:

$ 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)就變成這樣了:


image.png

所以忱叭,git add命令實(shí)際上就是把要提交的所有修改放到暫存區(qū)(Stage),然后今艺,執(zhí)行g(shù)it commit就可以一次性把暫存區(qū)的所有修改提交到分支韵丑。

$ git commit -m "understand how stage works"
[master e43a48b] understand how stage works
2 files changed, 2 insertions(+)
create mode 100644 LICENSE
一旦提交后,如果你又沒有對(duì)工作區(qū)做任何修改虚缎,那么工作區(qū)就是“干凈”的:

$ git status
On branch master
nothing to commit, working tree clean
現(xiàn)在版本庫(kù)變成了這樣撵彻,暫存區(qū)就沒有任何內(nèi)容了:


image.png

小結(jié)
暫存區(qū)是Git非常重要的概念,弄明白了暫存區(qū)实牡,就弄明白了Git的很多操作到底干了什么陌僵。

沒弄明白暫存區(qū)是怎么回事的童鞋,請(qǐng)向上滾動(dòng)頁(yè)面创坞,再看一次碗短。

管理修改

現(xiàn)在,假定你已經(jīng)完全掌握了暫存區(qū)的概念题涨。下面台囱,我們要討論的就是服傍,為什么Git比其他版本控制系統(tǒng)設(shè)計(jì)得優(yōu)秀介汹,因?yàn)镚it跟蹤并管理的是修改棍辕,而非文件。

你會(huì)問席函,什么是修改铐望?比如你新增了一行,這就是一個(gè)修改,刪除了一行正蛙,也是一個(gè)修改炕舵,更改了某些字符,也是一個(gè)修改跟畅,刪了一些又加了一些,也是一個(gè)修改溶推,甚至創(chuàng)建一個(gè)新文件徊件,也算一個(gè)修改。

為什么說Git管理的是修改蒜危,而不是文件呢虱痕?我們還是做實(shí)驗(yàn)。第一步辐赞,對(duì)readme.txt做一個(gè)修改部翘,比如加一行內(nèi)容:

$ cat readme.txt
Git is a distributed version control system.
Git is free software distributed under the GPL.
Git has a mutable index called stage.
Git tracks changes.
然后,添加:

git add readme.txt git status

On branch master

Changes to be committed:

(use "git reset HEAD <file>..." to unstage)

modified: readme.txt

然后响委,再修改readme.txt:

$ cat readme.txt
Git is a distributed version control system.
Git is free software distributed under the GPL.
Git has a mutable index called stage.
Git tracks changes of files.
提交:

$ git commit -m "git tracks changes"
[master 519219b] git tracks changes
1 file changed, 1 insertion(+)
提交后新思,再看看狀態(tài):

$ 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")
咦,怎么第二次的修改沒有被提交赘风?

別激動(dòng)夹囚,我們回顧一下操作過程:

第一次修改 -> git add -> 第二次修改 -> git commit

你看,我們前面講了邀窃,Git管理的是修改荸哟,當(dāng)你用git add命令后,在工作區(qū)的第一次修改被放入暫存區(qū)瞬捕,準(zhǔn)備提交鞍历,但是,在工作區(qū)的第二次修改并沒有放入暫存區(qū)肪虎,所以劣砍,git commit只負(fù)責(zé)把暫存區(qū)的修改提交了,也就是第一次的修改被提交了笋轨,第二次的修改不會(huì)被提交秆剪。

提交后,用git diff HEAD -- readme.txt命令可以查看工作區(qū)和版本庫(kù)里面最新版本的區(qū)別:

$ git diff HEAD -- readme.txt
diff --git a/readme.txt b/readme.txt
index 76d770f..a9c5755 100644
--- a/readme.txt
+++ b/readme.txt
@@ -1,4 +1,4 @@
Git is a distributed version control system.
Git is free software distributed under the GPL.
Git has a mutable index called stage.
-Git tracks changes.
+Git tracks changes of files.
可見爵政,第二次修改確實(shí)沒有被提交仅讽。
那怎么提交第二次修改呢?你可以繼續(xù)git add再git commit钾挟,也可以別著急提交第一次修改洁灵,先git add第二次修改,再git commit,就相當(dāng)于把兩次修改合并后一塊提交了:

第一次修改 -> git add -> 第二次修改 -> git add -> git commit

好双抽,現(xiàn)在露该,把第二次修改提交了苛骨,然后開始小結(jié)请琳。

小結(jié)
現(xiàn)在踱讨,你又理解了Git是如何跟蹤修改的,每次修改,如果不用git add到暫存區(qū)赢笨,那就不會(huì)加入到commit中除破。

撤銷修改

自然擂达,你是不會(huì)犯錯(cuò)的理张。不過現(xiàn)在是凌晨?jī)牲c(diǎn)起意,你正在趕一份工作報(bào)告,你在readme.txt中添加了一行:

$ cat readme.txt
Git is a distributed version control system.
Git is free software distributed under the GPL.
Git has a mutable index called stage.
Git tracks changes of files.
My stupid boss still prefers SVN.
在你準(zhǔn)備提交前闰围,一杯咖啡起了作用乐纸,你猛然發(fā)現(xiàn)了stupid boss可能會(huì)讓你丟掉這個(gè)月的獎(jiǎng)金吗跋!

既然錯(cuò)誤發(fā)現(xiàn)得很及時(shí),就可以很容易地糾正它宁昭。你可以刪掉最后一行跌宛,手動(dòng)把文件恢復(fù)到上一個(gè)版本的狀態(tài)。如果用git status查看一下:

$ 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")
你可以發(fā)現(xiàn)积仗,Git會(huì)告訴你疆拘,git checkout -- file可以丟棄工作區(qū)的修改:

$ git checkout -- readme.txt
命令git checkout -- readme.txt意思就是,把readme.txt文件在工作區(qū)的修改全部撤銷寂曹,這里有兩種情況:

一種是readme.txt自修改后還沒有被放到暫存區(qū)哎迄,現(xiàn)在回右,撤銷修改就回到和版本庫(kù)一模一樣的狀態(tài);

一種是readme.txt已經(jīng)添加到暫存區(qū)后漱挚,又作了修改翔烁,現(xiàn)在,撤銷修改就回到添加到暫存區(qū)后的狀態(tài)棱烂。

總之租漂,就是讓這個(gè)文件回到最近一次git commit或git add時(shí)的狀態(tài)。

現(xiàn)在颊糜,看看readme.txt的文件內(nèi)容:

$ cat readme.txt
Git is a distributed version control system.
Git is free software distributed under the GPL.
Git has a mutable index called stage.
Git tracks changes of files.
文件內(nèi)容果然復(fù)原了哩治。

git checkout -- file命令中的--很重要,沒有--衬鱼,就變成了“切換到另一個(gè)分支”的命令业筏,我們?cè)诤竺娴姆种Ч芾碇袝?huì)再次遇到git checkout命令。
現(xiàn)在假定是凌晨3點(diǎn)鸟赫,你不但寫了一些胡話蒜胖,還git add到暫存區(qū)了:

$ cat readme.txt
Git is a distributed version control system.
Git is free software distributed under the GPL.
Git has a mutable index called stage.
Git tracks changes of files.
My stupid boss still prefers SVN.

$ git add readme.txt
慶幸的是,在commit之前抛蚤,你發(fā)現(xiàn)了這個(gè)問題台谢。用git status查看一下,修改只是添加到了暫存區(qū)岁经,還沒有提交:

$ git status
On branch master
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)

modified:   readme.txt

Git同樣告訴我們朋沮,用命令git reset HEAD <file>可以把暫存區(qū)的修改撤銷掉(unstage),重新放回工作區(qū):

$ git reset HEAD readme.txt
Unstaged changes after reset:
M readme.txt
git reset命令既可以回退版本缀壤,也可以把暫存區(qū)的修改回退到工作區(qū)樊拓。當(dāng)我們用HEAD時(shí),表示最新的版本塘慕。

再用git status查看一下筋夏,現(xiàn)在暫存區(qū)是干凈的,工作區(qū)有修改:

$ 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

還記得如何丟棄工作區(qū)的修改嗎图呢?

$ git checkout -- readme.txt

$ git status
On branch master
nothing to commit, working tree clean
整個(gè)世界終于清靜了条篷!

現(xiàn)在,假設(shè)你不但改錯(cuò)了東西蛤织,還從暫存區(qū)提交到了版本庫(kù)拥娄,怎么辦呢?還記得版本回退一節(jié)嗎瞳筏?可以回退到上一個(gè)版本稚瘾。不過,這是有條件的姚炕,就是你還沒有把自己的本地版本庫(kù)推送到遠(yuǎn)程摊欠。還記得Git是分布式版本控制系統(tǒng)嗎丢烘?我們后面會(huì)講到遠(yuǎn)程版本庫(kù),一旦你把stupid boss提交推送到遠(yuǎn)程版本庫(kù)些椒,你就真的慘了……

小結(jié)

又到了小結(jié)時(shí)間播瞳。

場(chǎng)景1:當(dāng)你改亂了工作區(qū)某個(gè)文件的內(nèi)容,想直接丟棄工作區(qū)的修改時(shí)免糕,用命令git checkout -- file赢乓。

場(chǎng)景2:當(dāng)你不但改亂了工作區(qū)某個(gè)文件的內(nèi)容,還添加到了暫存區(qū)時(shí)石窑,想丟棄修改牌芋,分兩步,第一步用命令git reset HEAD <file>松逊,就回到了場(chǎng)景1躺屁,第二步按場(chǎng)景1操作。

場(chǎng)景3:已經(jīng)提交了不合適的修改到版本庫(kù)時(shí)经宏,想要撤銷本次提交犀暑,參考版本回退一節(jié),不過前提是沒有推送到遠(yuǎn)程庫(kù)烁兰。

刪除文件

在Git中耐亏,刪除也是一個(gè)修改操作,我們實(shí)戰(zhàn)一下沪斟,先添加一個(gè)新文件test.txt到Git并且提交:

$ git add test.txt

$ git commit -m "add test.txt"
[master b84166e] add test.txt
1 file changed, 1 insertion(+)
create mode 100644 test.txt
一般情況下广辰,你通常直接在文件管理器中把沒用的文件刪了,或者用rm命令刪了:

$ rm test.txt
這個(gè)時(shí)候币喧,Git知道你刪除了文件轨域,因此袱耽,工作區(qū)和版本庫(kù)就不一致了杀餐,git status命令會(huì)立刻告訴你哪些文件被刪除了:

$ git status
On branch master
Changes not staged for commit:
(use "git add/rm <file>..." to update what will be committed)
(use "git checkout -- <file>..." to discard changes in working directory)

deleted:    test.txt

no changes added to commit (use "git add" and/or "git commit -a")
現(xiàn)在你有兩個(gè)選擇,一是確實(shí)要從版本庫(kù)中刪除該文件朱巨,那就用命令git rm刪掉史翘,并且git commit:

$ git rm test.txt
rm 'test.txt'

$ git commit -m "remove test.txt"
[master d46f35e] remove test.txt
1 file changed, 1 deletion(-)
delete mode 100644 test.txt
現(xiàn)在,文件就從版本庫(kù)中被刪除了冀续。
小提示:先手動(dòng)刪除文件琼讽,然后使用git rm <file>和git add<file>效果是一樣的。
另一種情況是刪錯(cuò)了洪唐,因?yàn)榘姹編?kù)里還有呢钻蹬,所以可以很輕松地把誤刪的文件恢復(fù)到最新版本:

$ git checkout -- test.txt
git checkout其實(shí)是用版本庫(kù)里的版本替換工作區(qū)的版本,無論工作區(qū)是修改還是刪除凭需,都可以“一鍵還原”问欠。

注意:從來沒有被添加到版本庫(kù)就被刪除的文件肝匆,是無法恢復(fù)的!
小結(jié)
命令git rm用于刪除一個(gè)文件顺献。如果一個(gè)文件已經(jīng)被提交到版本庫(kù)旗国,那么你永遠(yuǎn)不用擔(dān)心誤刪,但是要小心注整,你只能恢復(fù)文件到最新版本能曾,你會(huì)丟失最近一次提交后你修改的內(nèi)容。

遠(yuǎn)程倉(cāng)庫(kù)

到目前為止肿轨,我們已經(jīng)掌握了如何在Git倉(cāng)庫(kù)里對(duì)一個(gè)文件進(jìn)行時(shí)光穿梭寿冕,你再也不用擔(dān)心文件備份或者丟失的問題了。

可是有用過集中式版本控制系統(tǒng)SVN的童鞋會(huì)站出來說萝招,這些功能在SVN里早就有了蚂斤,沒看出Git有什么特別的地方。

沒錯(cuò)槐沼,如果只是在一個(gè)倉(cāng)庫(kù)里管理文件歷史曙蒸,Git和SVN真沒啥區(qū)別。為了保證你現(xiàn)在所學(xué)的Git物超所值岗钩,將來絕對(duì)不會(huì)后悔纽窟,同時(shí)為了打擊已經(jīng)不幸學(xué)了SVN的童鞋,本章開始介紹Git的殺手級(jí)功能之一(注意是之一兼吓,也就是后面還有之二臂港,之三……):遠(yuǎn)程倉(cāng)庫(kù)。

Git是分布式版本控制系統(tǒng)视搏,同一個(gè)Git倉(cāng)庫(kù)审孽,可以分布到不同的機(jī)器上。怎么分布呢浑娜?最早佑力,肯定只有一臺(tái)機(jī)器有一個(gè)原始版本庫(kù),此后筋遭,別的機(jī)器可以“克隆”這個(gè)原始版本庫(kù)打颤,而且每臺(tái)機(jī)器的版本庫(kù)其實(shí)都是一樣的,并沒有主次之分漓滔。

你肯定會(huì)想编饺,至少需要兩臺(tái)機(jī)器才能玩遠(yuǎn)程庫(kù)不是?但是我只有一臺(tái)電腦响驴,怎么玩透且?

其實(shí)一臺(tái)電腦上也是可以克隆多個(gè)版本庫(kù)的,只要不在同一個(gè)目錄下豁鲤。不過秽誊,現(xiàn)實(shí)生活中是不會(huì)有人這么傻的在一臺(tái)電腦上搞幾個(gè)遠(yuǎn)程庫(kù)玩罕邀,因?yàn)橐慌_(tái)電腦上搞幾個(gè)遠(yuǎn)程庫(kù)完全沒有意義,而且硬盤掛了會(huì)導(dǎo)致所有庫(kù)都掛掉养距,所以我也不告訴你在一臺(tái)電腦上怎么克隆多個(gè)倉(cāng)庫(kù)诉探。

實(shí)際情況往往是這樣,找一臺(tái)電腦充當(dāng)服務(wù)器的角色棍厌,每天24小時(shí)開機(jī)肾胯,其他每個(gè)人都從這個(gè)“服務(wù)器”倉(cāng)庫(kù)克隆一份到自己的電腦上,并且各自把各自的提交推送到服務(wù)器倉(cāng)庫(kù)里耘纱,也從服務(wù)器倉(cāng)庫(kù)中拉取別人的提交敬肚。

完全可以自己搭建一臺(tái)運(yùn)行Git的服務(wù)器,不過現(xiàn)階段束析,為了學(xué)Git先搭個(gè)服務(wù)器絕對(duì)是小題大作艳馒。好在這個(gè)世界上有個(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ù)陆爽。

在繼續(xù)閱讀后續(xù)內(nèi)容前,請(qǐng)自行注冊(cè)GitHub賬號(hào)扳缕。由于你的本地Git倉(cāng)庫(kù)和GitHub倉(cāng)庫(kù)之間的傳輸是通過SSH加密的慌闭,所以,需要一點(diǎn)設(shè)置:

第1步:創(chuàng)建SSH Key躯舔。在用戶主目錄下驴剔,看看有沒有.ssh目錄,如果有粥庄,再看看這個(gè)目錄下有沒有id_rsaid_rsa.pub這兩個(gè)文件沟于,如果已經(jīng)有了罐呼,可直接跳到下一步纯出。如果沒有杰刽,打開Shell(Windows下打開Git Bash)廊勃,創(chuàng)建SSH Key:

$ ssh-keygen -t rsa -C "youremail@example.com"

你需要把郵件地址換成你自己的郵件地址四苇,然后一路回車橄务,使用默認(rèn)值即可难礼,由于這個(gè)Key也不是用于軍事目的臀栈,所以也無需設(shè)置密碼蔫慧。

如果一切順利的話,可以在用戶主目錄里找到.ssh目錄权薯,里面有id_rsaid_rsa.pub兩個(gè)文件姑躲,這兩個(gè)就是SSH Key的秘鑰對(duì)睡扬,id_rsa是私鑰,不能泄露出去黍析,id_rsa.pub是公鑰卖怜,可以放心地告訴任何人。

第2步:登陸GitHub阐枣,打開“Account settings”马靠,“SSH Keys”頁(yè)面:

然后,點(diǎn)“Add SSH Key”蔼两,填上任意Title甩鳄,在Key文本框里粘貼id_rsa.pub文件的內(nèi)容:

image.png

點(diǎn)“Add Key”,你就應(yīng)該看到已經(jīng)添加的Key:


image.png

為什么GitHub需要SSH Key呢额划?因?yàn)镚itHub需要識(shí)別出你推送的提交確實(shí)是你推送的妙啃,而不是別人冒充的,而Git支持SSH協(xié)議俊戳,所以揖赴,GitHub只要知道了你的公鑰,就可以確認(rèn)只有你自己才能推送抑胎。

當(dāng)然储笑,GitHub允許你添加多個(gè)Key。假定你有若干電腦圆恤,你一會(huì)兒在公司提交突倍,一會(huì)兒在家里提交,只要把每臺(tái)電腦的Key都添加到GitHub盆昙,就可以在每臺(tái)電腦上往GitHub推送了羽历。

最后友情提示,在GitHub上免費(fèi)托管的Git倉(cāng)庫(kù)淡喜,任何人都可以看到喔(但只有你自己才能改)秕磷。所以,不要把敏感信息放進(jìn)去瘟芝。

如果你不想讓別人看到Git庫(kù)析显,有兩個(gè)辦法史飞,一個(gè)是交點(diǎn)保護(hù)費(fèi)纹笼,讓GitHub把公開的倉(cāng)庫(kù)變成私有的,這樣別人就看不見了(不可讀更不可寫)趟咆。另一個(gè)辦法是自己動(dòng)手迅箩,搭一個(gè)Git服務(wù)器枫疆,因?yàn)槭悄阕约旱腉it服務(wù)器爵川,所以別人也是看不見的。這個(gè)方法我們后面會(huì)講到的息楔,相當(dāng)簡(jiǎn)單寝贡,公司內(nèi)部開發(fā)必備。

確保你擁有一個(gè)GitHub賬號(hào)后值依,我們就即將開始遠(yuǎn)程倉(cāng)庫(kù)的學(xué)習(xí)圃泡。

小結(jié)
“有了遠(yuǎn)程倉(cāng)庫(kù),媽媽再也不用擔(dān)心我的硬盤了愿险∑睦”——Git點(diǎn)讀機(jī)

添加遠(yuǎn)程庫(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狸捅,然后,在右上角找到“Create a new repo”按鈕累提,創(chuàng)建一個(gè)新的倉(cāng)庫(kù):


image.png

在Repository name填入learngit尘喝,其他保持默認(rèn)設(shè)置,點(diǎn)擊“Create repository”按鈕斋陪,就成功地創(chuàng)建了一個(gè)新的Git倉(cāng)庫(kù):


image.png

目前瞧省,在GitHub上的這個(gè)learngit倉(cāng)庫(kù)還是空的,GitHub告訴我們鳍贾,可以從這個(gè)倉(cāng)庫(kù)克隆出新的倉(cāng)庫(kù)鞍匾,也可以把一個(gè)已有的本地倉(cāng)庫(kù)與之關(guān)聯(lián),然后骑科,把本地倉(cāng)庫(kù)的內(nèi)容推送到GitHub倉(cāng)庫(kù)橡淑。

現(xiàn)在,我們根據(jù)GitHub的提示咆爽,在本地的learngit倉(cāng)庫(kù)下運(yùn)行命令:

$ git remote add origin git@github.com:michaelliao/learngit.git
請(qǐng)千萬注意梁棠,把上面的michaelliao替換成你自己的GitHub賬戶名,否則斗埂,你在本地關(guān)聯(lián)的就是我的遠(yuǎn)程庫(kù)符糊,關(guān)聯(lián)沒有問題,但是你以后推送是推不上去的呛凶,因?yàn)槟愕腟SH Key公鑰不在我的賬戶列表中男娄。

添加后,遠(yuǎn)程庫(kù)的名字就是origin漾稀,這是Git默認(rèn)的叫法模闲,也可以改成別的,但是origin這個(gè)名字一看就知道是遠(yuǎn)程庫(kù)崭捍。

下一步尸折,就可以把本地庫(kù)的所有內(nèi)容推送到遠(yuǎn)程庫(kù)上:

$ git push -u origin master
Counting objects: 20, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (15/15), done.
Writing objects: 100% (20/20), 1.64 KiB | 560.00 KiB/s, done.
Total 20 (delta 5), reused 0 (delta 0)
remote: Resolving deltas: 100% (5/5), done.
To github.com:michaelliao/learngit.git

  • [new branch] master -> master
    Branch 'master' set up to track remote branch 'master' from 'origin'.
    把本地庫(kù)的內(nèi)容推送到遠(yuǎn)程,用git push命令殷蛇,實(shí)際上是把當(dāng)前分支master推送到遠(yuǎn)程实夹。

由于遠(yuǎn)程庫(kù)是空的橄浓,我們第一次推送master分支時(shí),加上了-u參數(shù)亮航,Git不但會(huì)把本地的master分支內(nèi)容推送的遠(yuǎn)程新的master分支荸实,還會(huì)把本地的master分支和遠(yuǎn)程的master分支關(guān)聯(lián)起來,在以后的推送或者拉取時(shí)就可以簡(jiǎn)化命令塞赂。
推送成功后泪勒,可以立刻在GitHub頁(yè)面中看到遠(yuǎn)程庫(kù)的內(nèi)容已經(jīng)和本地一模一樣


image.png

從現(xiàn)在起昼蛀,只要本地作了提交宴猾,就可以通過命令:

$ git push origin master

把本地master分支的最新修改推送至GitHub,現(xiàn)在叼旋,你就擁有了真正的分布式版本庫(kù)仇哆!

SSH警告

當(dāng)你第一次使用Git的clone或者push命令連接GitHub時(shí),會(huì)得到一個(gè)警告:

The authenticity of host 'github.com (xx.xx.xx.xx)' can't be established.
RSA key fingerprint is xx.xx.xx.xx.xx.
Are you sure you want to continue connecting (yes/no)?

這是因?yàn)镚it使用SSH連接夫植,而SSH連接在第一次驗(yàn)證GitHub服務(wù)器的Key時(shí)讹剔,需要你確認(rèn)GitHub的Key的指紋信息是否真的來自GitHub的服務(wù)器,輸入yes回車即可详民。

Git會(huì)輸出一個(gè)警告延欠,告訴你已經(jīng)把GitHub的Key添加到本機(jī)的一個(gè)信任列表里了:

Warning: Permanently added 'github.com' (RSA) to the list of known hosts.

這個(gè)警告只會(huì)出現(xiàn)一次,后面的操作就不會(huì)有任何警告了沈跨。

如果你實(shí)在擔(dān)心有人冒充GitHub服務(wù)器由捎,輸入yes前可以對(duì)照GitHub的RSA Key的指紋信息是否與SSH連接給出的一致。

刪除遠(yuǎn)程庫(kù)

如果添加的時(shí)候地址寫錯(cuò)了饿凛,或者就是想刪除遠(yuǎn)程庫(kù)狞玛,可以用git remote rm <name>命令。使用前涧窒,建議先用git remote -v查看遠(yuǎn)程庫(kù)信息:

$ git remote -v
origin  git@github.com:michaelliao/learn-git.git (fetch)
origin  git@github.com:michaelliao/learn-git.git (push)

然后心肪,根據(jù)名字刪除,比如刪除origin

$ git remote rm origin

此處的“刪除”其實(shí)是解除了本地和遠(yuǎn)程的綁定關(guān)系纠吴,并不是物理上刪除了遠(yuǎn)程庫(kù)硬鞍。遠(yuǎn)程庫(kù)本身并沒有任何改動(dòng)。要真正刪除遠(yuǎn)程庫(kù)戴已,需要登錄到GitHub膳凝,在后臺(tái)頁(yè)面找到刪除按鈕再刪除。

小結(jié)

要關(guān)聯(lián)一個(gè)遠(yuǎn)程庫(kù)恭陡,使用命令git remote add origin git@server-name:path/repo-name.git蹬音;

關(guān)聯(lián)一個(gè)遠(yuǎn)程庫(kù)時(shí)必須給遠(yuǎn)程庫(kù)指定一個(gè)名字,origin是默認(rèn)習(xí)慣命名休玩;

關(guān)聯(lián)后著淆,使用命令git push -u origin master第一次推送master分支的所有內(nèi)容劫狠;

此后,每次本地提交后永部,只要有必要独泞,就可以使用命令git push origin master推送最新修改;

分布式版本系統(tǒng)的最大好處之一是在本地工作完全不需要考慮遠(yuǎn)程庫(kù)的存在苔埋,也就是有沒有聯(lián)網(wǎng)都可以正常工作懦砂,而SVN在沒有聯(lián)網(wǎng)的時(shí)候是拒絕干活的!當(dāng)有網(wǎng)絡(luò)的時(shí)候组橄,再把本地提交推送一下就完成了同步荞膘,真是太方便了!

從遠(yuǎn)程庫(kù)克隆

上次我們講了先有本地庫(kù)玉工,后有遠(yuǎn)程庫(kù)的時(shí)候羽资,如何關(guān)聯(lián)遠(yuǎn)程庫(kù)。

現(xiàn)在遵班,假設(shè)我們從零開發(fā)屠升,那么最好的方式是先創(chuàng)建遠(yuǎn)程庫(kù),然后狭郑,從遠(yuǎn)程庫(kù)克隆腹暖。

首先,登陸GitHub翰萨,創(chuàng)建一個(gè)新的倉(cāng)庫(kù)脏答,名字叫g(shù)itskills:


image.png

我們勾選Initialize this repository with a README,這樣GitHub會(huì)自動(dòng)為我們創(chuàng)建一個(gè)README.md文件缨历。創(chuàng)建完畢后以蕴,可以看到README.md文件:


image.png

現(xiàn)在,遠(yuǎn)程庫(kù)已經(jīng)準(zhǔn)備好了辛孵,下一步是用命令git clone克隆一個(gè)本地庫(kù):

$ git clone git@github.com:michaelliao/gitskills.git
Cloning into 'gitskills'...
remote: Counting objects: 3, done.
remote: Total 3 (delta 0), reused 0 (delta 0), pack-reused 3
Receiving objects: 100% (3/3), done.
注意把Git庫(kù)的地址換成你自己的丛肮,然后進(jìn)入gitskills目錄看看,已經(jīng)有README.md文件了:

cd gitskills ls
README.md
如果有多個(gè)人協(xié)作開發(fā)魄缚,那么每個(gè)人各自從遠(yuǎn)程克隆一份就可以了宝与。

你也許還注意到,GitHub給出的地址不止一個(gè)冶匹,還可以用https://github.com/michaelliao/gitskills.git這樣的地址习劫。實(shí)際上,Git支持多種協(xié)議嚼隘,默認(rèn)的git://使用ssh诽里,但也可以使用https等其他協(xié)議。

使用https除了速度慢以外飞蛹,還有個(gè)最大的麻煩是每次推送都必須輸入口令谤狡,但是在某些只開放http端口的公司內(nèi)部就無法使用ssh協(xié)議而只能用https灸眼。

小結(jié)
要克隆一個(gè)倉(cāng)庫(kù),首先必須知道倉(cāng)庫(kù)的地址墓懂,然后使用git clone命令克隆焰宣。

Git支持多種協(xié)議,包括https捕仔,但ssh協(xié)議速度最快匕积。

分支管理

分支就是科幻電影里面的平行宇宙闪唆,當(dāng)你正在電腦前努力學(xué)習(xí)Git的時(shí)候斜做,另一個(gè)你正在另一個(gè)平行宇宙里努力學(xué)習(xí)SVN。

如果兩個(gè)平行宇宙互不干擾佛吓,那對(duì)現(xiàn)在的你也沒啥影響哟沫。不過悼泌,在某個(gè)時(shí)間點(diǎn)世分,兩個(gè)平行宇宙合并了检访,結(jié)果寨昙,你既學(xué)會(huì)了Git又學(xué)會(huì)了SVN!

image.png

](https://upload-images.jianshu.io/upload_images/1433617-4950952372251c0b.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
分支在實(shí)際中有什么用呢墩莫?假設(shè)你準(zhǔn)備開發(fā)一個(gè)新功能芙委,但是需要兩周才能完成,第一周你寫了50%的代碼狂秦,如果立刻提交灌侣,由于代碼還沒寫完,不完整的代碼庫(kù)會(huì)導(dǎo)致別人不能干活了裂问。如果等代碼全部寫完再一次提交侧啼,又存在丟失每天進(jìn)度的巨大風(fēng)險(xiǎn)。

現(xiàn)在有了分支堪簿,就不用怕了痊乾。你創(chuàng)建了一個(gè)屬于你自己的分支,別人看不到椭更,還繼續(xù)在原來的分支上正常工作哪审,而你在自己的分支上干活,想提交就提交虑瀑,直到開發(fā)完畢后湿滓,再一次性合并到原來的分支上,這樣舌狗,既安全叽奥,又不影響別人工作。

其他版本控制系統(tǒng)如SVN等都有分支管理把夸,但是用過之后你會(huì)發(fā)現(xiàn)而线,這些版本控制系統(tǒng)創(chuàng)建和切換分支比蝸牛還慢,簡(jiǎn)直讓人無法忍受,結(jié)果分支功能成了擺設(shè)膀篮,大家都不去用嘹狞。

但Git的分支是與眾不同的,無論創(chuàng)建誓竿、切換和刪除分支磅网,Git在1秒鐘之內(nèi)就能完成!無論你的版本庫(kù)是1個(gè)文件還是1萬個(gè)文件。

創(chuàng)建與合并分支

版本回退里,你已經(jīng)知道潭流,每次提交,Git都把它們串成一條時(shí)間線燎潮,這條時(shí)間線就是一個(gè)分支。截止到目前扼倘,只有一條時(shí)間線确封,在Git里,這個(gè)分支叫主分支再菊,即master分支爪喘。HEAD嚴(yán)格來說不是指向提交,而是指向master纠拔,master才是指向提交的秉剑,所以,HEAD指向的就是當(dāng)前分支稠诲。

一開始的時(shí)候侦鹏,master分支是一條線,Git用master指向最新的提交吕粹,再用HEAD指向master种柑,就能確定當(dāng)前分支,以及當(dāng)前分支的提交點(diǎn):

image.png

每次提交匹耕,master分支都會(huì)向前移動(dòng)一步,這樣荠雕,隨著你不斷提交稳其,master分支的線也越來越長(zhǎng)。

當(dāng)我們創(chuàng)建新的分支炸卑,例如dev時(shí)既鞠,Git新建了一個(gè)指針叫dev,指向master相同的提交盖文,再把HEAD指向dev嘱蛋,就表示當(dāng)前分支在dev上:


image.png

你看,Git創(chuàng)建一個(gè)分支很快,因?yàn)槌嗽黾右粋€(gè)dev指針洒敏,改改HEAD的指向龄恋,工作區(qū)的文件都沒有任何變化!

不過凶伙,從現(xiàn)在開始郭毕,對(duì)工作區(qū)的修改和提交就是針對(duì)dev分支了,比如新提交一次后函荣,dev指針往前移動(dòng)一步显押,而master指針不變:


image.png

假如我們?cè)赿ev上的工作完成了,就可以把dev合并到master上傻挂。Git怎么合并呢乘碑?最簡(jiǎn)單的方法,就是直接把master指向dev的當(dāng)前提交金拒,就完成了合并:


image.png

所以Git合并分支也很快蝉仇!就改改指針,工作區(qū)內(nèi)容也不變殖蚕!

合并完分支后轿衔,甚至可以刪除dev分支。刪除dev分支就是把dev指針給刪掉睦疫,刪掉后害驹,我們就剩下了一條master分支:


image.png

真是太神奇了,你看得出來有些提交是通過分支完成的嗎蛤育?

下面開始實(shí)戰(zhàn)宛官。

首先,我們創(chuàng)建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 branch 'dev'
然后,用git branch命令查看當(dāng)前分支:

$ git branch

  • dev
    master
    git branch命令會(huì)列出所有分支咕娄,當(dāng)前分支前面會(huì)標(biāo)一個(gè)*號(hào)亥揖。

然后,我們就可以在dev分支上正常提交圣勒,比如對(duì)readme.txt做個(gè)修改费变,加上一行:

Creating a new branch is quick.
然后提交:

git add readme.txt git commit -m "branch test"
[dev b17d20e] branch test
1 file changed, 1 insertion(+)
現(xiàn)在,dev分支的工作完成圣贸,我們就可以切換回master分支:

$ git checkout master
Switched to branch 'master'
切換回master分支后挚歧,再查看一個(gè)readme.txt文件,剛才添加的內(nèi)容不見了吁峻!因?yàn)槟莻€(gè)提交是在dev分支上滑负,而master分支此刻的提交點(diǎn)并沒有變:


image.png

現(xiàn)在在张,我們把dev分支的工作成果合并到master分支上:

$ git merge dev
Updating d46f35e..b17d20e
Fast-forward
readme.txt | 1 +
1 file changed, 1 insertion(+)
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 b17d20e).
刪除后,查看branch亲雪,就只剩下master分支了:

$ git branch

  • master
    因?yàn)閯?chuàng)建勇凭、合并和刪除分支非常快义辕,所以Git鼓勵(lì)你使用分支完成某個(gè)任務(wù)虾标,合并后再刪掉分支,這和直接在master分支上工作效果是一樣的灌砖,但過程更安全璧函。
    switch
    我們注意到切換分支使用git checkout <branch>,而前面講過的撤銷修改則是git checkout -- <file>基显,同一個(gè)命令蘸吓,有兩種作用,確實(shí)有點(diǎn)令人迷惑撩幽。

實(shí)際上库继,切換分支這個(gè)動(dòng)作,用switch更科學(xué)摸航。因此,最新版本的Git提供了新的git switch命令來切換分支:

創(chuàng)建并切換到新的dev分支排监,可以使用:

$ git switch -c dev
直接切換到已有的master分支,可以使用:

$ git switch master
使用新的git switch命令,比git checkout要更容易理解砌创。

小結(jié)
Git鼓勵(lì)大量使用分支:

查看分支:git branch

創(chuàng)建分支:git branch <name>

切換分支:git checkout <name>或者git switch <name>

創(chuàng)建+切換分支:git checkout -b <name>或者git switch -c <name>

合并某分支到當(dāng)前分支:git merge <name>

刪除分支:git branch -d <name>

解決沖突

人生不如意之事十之八九舶赔,合并分支往往也不是一帆風(fēng)順的。

準(zhǔn)備新的feature1分支谦秧,繼續(xù)我們的新分支開發(fā):

$ git switch -c feature1
Switched to a new branch 'feature1'
修改readme.txt最后一行竟纳,改為:

Creating a new branch is quick AND simple.
在feature1分支上提交:

$ git add readme.txt

$ git commit -m "AND simple"
[feature1 14096d0] AND simple
1 file changed, 1 insertion(+), 1 deletion(-)
切換到master分支:

$ git switch 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分支要超前1個(gè)提交。

在master分支上把readme.txt文件的最后一行改為:

Creating a new branch is quick & simple.
提交:

git add readme.txt git commit -m "& simple"
[master 5dc6824] & simple
1 file changed, 1 insertion(+), 1 deletion(-)
現(xiàn)在疚鲤,master分支和feature1分支各自都分別有新的提交锥累,變成了這樣:

image.png

這種情況下,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")
(use "git merge --abort" to abort the merge)

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 distributed version control system.
Git is free software distributed under the GPL.
Git has a mutable index called stage.
Git tracks changes of files.
<<<<<<< HEAD
Creating a new branch is quick & simple.
=======
Creating a new branch is quick AND simple.

feature1
Git用<<<<<<<鹅心,=======,>>>>>>>標(biāo)記出不同分支的內(nèi)容纺荧,我們修改如下后保存:

Creating a new branch is quick and simple.
再提交:

git add readme.txt git commit -m "conflict fixed"
[master cf810e4] conflict fixed
現(xiàn)在旭愧,master分支和feature1分支變成了下圖所示:

image.png

用帶參數(shù)的git log也可以看到分支的合并情況:

$ git log --graph --pretty=oneline --abbrev-commit

  • cf810e4 (HEAD -> master) conflict fixed
    |\
    | * 14096d0 (feature1) AND simple
  • | 5dc6824 & simple
    |/
  • b17d20e branch test
  • d46f35e (origin/master) remove test.txt
  • b84166e add test.txt
  • 519219b git tracks changes
  • e43a48b understand how stage works
  • 1094adb append GPL
  • e475afc add distributed
  • eaadf4e wrote a readme file
    最后,刪除feature1分支:

$ git branch -d feature1
Deleted branch feature1 (was 14096d0).
工作完成宙暇。
小結(jié)
當(dāng)Git無法自動(dòng)合并分支時(shí)输枯,就必須首先解決沖突。解決沖突后占贫,再提交桃熄,合并完成。

解決沖突就是把Git合并失敗的文件手動(dòng)編輯為我們希望的內(nèi)容型奥,再提交蜻拨。

用git log --graph命令可以看到分支合并圖池充。

分支管理策略

通常桩引,合并分支時(shí)缎讼,如果可能,Git會(huì)用Fast forward模式坑匠,但這種模式下血崭,刪除分支后,會(huì)丟掉分支信息厘灼。

如果要強(qiáng)制禁用Fast forward模式夹纫,Git就會(huì)在merge時(shí)生成一個(gè)新的commit,這樣设凹,從分支歷史上就可以看出分支信息舰讹。

下面我們實(shí)戰(zhàn)一下--no-ff方式的git merge:

首先,仍然創(chuàng)建并切換dev分支:

$ git switch -c dev
Switched to a new branch 'dev'
修改readme.txt文件闪朱,并提交一個(gè)新的commit:

git add readme.txt git commit -m "add merge"
[dev f52c633] add merge
1 file changed, 1 insertion(+)
現(xiàn)在月匣,我們切換回master:

$ git switch master
Switched to branch 'master'
準(zhǔn)備合并dev分支,請(qǐng)注意--no-ff參數(shù)奋姿,表示禁用Fast forward:

$ git merge --no-ff -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 --abbrev-commit

  • e1e9c68 (HEAD -> master) merge with no-ff
    |\
    | * f52c633 (dev) add merge
    |/
  • cf810e4 conflict fixed
    ...
    可以看到,不使用Fast forward模式寓免,merge后就像這樣:


    image.png

    分支策略
    在實(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分支上合并就可以了盒延。

所以缩擂,團(tuán)隊(duì)合作的分支看起來就像這樣:


image.png

小結(jié)
Git分支十分強(qiáng)大,在團(tuán)隊(duì)開發(fā)中應(yīng)該充分應(yīng)用添寺。

合并分支時(shí)胯盯,加上--no-ff參數(shù)就可以用普通模式合并,合并后的歷史有分支计露,能看出來曾經(jīng)做過合并博脑,而fast forward合并就看不出來曾經(jīng)做過合并。

Bug分支

軟件開發(fā)中票罐,bug就像家常便飯一樣叉趣。有了bug就需要修復(fù),在Git中该押,由于分支是如此的強(qiáng)大疗杉,所以,每個(gè)bug都可以通過一個(gè)新的臨時(shí)分支來修復(fù)蚕礼,修復(fù)后烟具,合并分支,然后將臨時(shí)分支刪除闻牡。

當(dāng)你接到一個(gè)修復(fù)一個(gè)代號(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

并不是你不想提交割以,而是工作只進(jìn)行到一半金度,還沒法提交,預(yù)計(jì)完成還需1天時(shí)間严沥。但是猜极,必須在兩個(gè)小時(shí)內(nèi)修復(fù)該bug,怎么辦消玄?

幸好跟伏,Git還提供了一個(gè)stash功能,可以把當(dāng)前工作現(xiàn)場(chǎng)“儲(chǔ)藏”起來翩瓜,等以后恢復(fù)現(xiàn)場(chǎng)后繼續(xù)工作:

$ git stash
Saved working directory and index state WIP on dev: f52c633 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 is free software ...”改為“Git is a free software ...”蕊蝗,然后提交:

git add readme.txt git commit -m "fix bug 101"
[issue-101 4c805e2] fix bug 101
1 file changed, 1 insertion(+), 1 deletion(-)
修復(fù)完成后,切換到master分支赖舟,并完成合并蓬戚,最后刪除issue-101分支:

$ git switch 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 "merged bug fix 101" issue-101
Merge made by the 'recursive' strategy.
readme.txt | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
太棒了,原計(jì)劃兩個(gè)小時(shí)的bug修復(fù)只花了5分鐘建蹄!現(xiàn)在碌更,是時(shí)候接著回到dev分支干活了!

$ git switch dev
Switched to branch 'dev'

$ git status
On branch dev
nothing to commit, working tree clean
工作區(qū)是干凈的洞慎,剛才的工作現(xiàn)場(chǎng)存到哪去了?用git stash list命令看看:

$ git stash list
stash@{0}: WIP on dev: f52c633 add merge
工作現(xiàn)場(chǎng)還在嘿棘,Git把stash內(nèi)容存在某個(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} (5d677e2ee266f39ea296182fb2354265b91b3b2a)
再用git stash list查看,就看不到任何stash內(nèi)容了:

$ git stash list
你可以多次stash房午,恢復(fù)的時(shí)候矿辽,先用git stash list查看,然后恢復(fù)指定的stash郭厌,用命令:

$ git stash apply stash@{0}
在master分支上修復(fù)了bug后袋倔,我們要想一想,dev分支是早期從master分支分出來的折柠,所以宾娜,這個(gè)bug其實(shí)在當(dāng)前dev分支上也存在。

那怎么在dev分支上修復(fù)同樣的bug扇售?重復(fù)操作一次前塔,提交不就行了?

有木有更簡(jiǎn)單的方法承冰?

有华弓!

同樣的bug,要在dev上修復(fù)巷懈,我們只需要把4c805e2 fix bug 101這個(gè)提交所做的修改“復(fù)制”到dev分支该抒。注意:我們只想復(fù)制4c805e2 fix bug 101這個(gè)提交所做的修改盘榨,并不是把整個(gè)master分支merge過來。

為了方便操作蝙寨,Git專門提供了一個(gè)cherry-pick命令妻导,讓我們能復(fù)制一個(gè)特定的提交到當(dāng)前分支:

$ git branch

  • dev
    master
    $ git cherry-pick 4c805e2
    [master 1d4b803] fix bug 101
    1 file changed, 1 insertion(+), 1 deletion(-)
    Git自動(dòng)給dev分支做了一次提交,注意這次提交的commit是1d4b803欧引,它并不同于master的4c805e2频伤,因?yàn)檫@兩個(gè)commit只是改動(dòng)相同,但確實(shí)是兩個(gè)不同的commit芝此。用git cherry-pick憋肖,我們就不需要在dev分支上手動(dòng)再把修bug的過程重復(fù)一遍。

有些聰明的童鞋會(huì)想了婚苹,既然可以在master分支上修復(fù)bug后岸更,在dev分支上可以“重放”這個(gè)修復(fù)過程,那么直接在dev分支上修復(fù)bug膊升,然后在master分支上“重放”行不行怎炊?當(dāng)然可以,不過你仍然需要git stash命令保存現(xiàn)場(chǎng)廓译,才能從dev分支切換到master分支评肆。

小結(jié)
修復(fù)bug時(shí),我們會(huì)通過創(chuàng)建新的bug分支進(jìn)行修復(fù)非区,然后合并瓜挽,最后刪除;

當(dāng)手頭工作沒有完成時(shí)征绸,先把工作現(xiàn)場(chǎng)git stash一下久橙,然后去修復(fù)bug,修復(fù)后歹垫,再git stash pop剥汤,回到工作現(xiàn)場(chǎng);

在master分支上修復(fù)的bug排惨,想要合并到當(dāng)前dev分支吭敢,可以用git cherry-pick <commit>命令,把bug提交的修改“復(fù)制”到當(dāng)前分支暮芭,避免重復(fù)勞動(dòng)鹿驼。

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 switch -c feature-vulcan
Switched to a new branch 'feature-vulcan'
5分鐘后,開發(fā)完畢:

$ git add vulcan.py

$ 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 287773e] add feature vulcan
1 file changed, 2 insertions(+)
create mode 100644 vulcan.py
切回dev甜害,準(zhǔn)備合并:

$ git switch dev
一切順利的話舶掖,feature分支和bug分支是類似的,合并唾那,然后刪除访锻。

但是!

就在此時(shí)闹获,接到上級(jí)命令,因經(jīng)費(fèi)不足河哑,新功能必須取消避诽!

雖然白干了,但是這個(gè)包含機(jī)密資料的分支還是必須就地銷毀:

$ 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友情提醒沙庐,feature-vulcan分支還沒有被合并,如果刪除佳吞,將丟失掉修改拱雏,如果要強(qiáng)行刪除,需要使用大寫的-D參數(shù)底扳。铸抑。

現(xiàn)在我們強(qiáng)行刪除:

$ git branch -D feature-vulcan
Deleted branch feature-vulcan (was 287773e).
終于刪除成功!

小結(jié)
開發(fā)一個(gè)新feature衷模,最好新建一個(gè)分支鹊汛;

如果要丟棄一個(gè)沒有被合并過的分支,可以通過git branch -D <name>強(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)程庫(kù)的信息,用git remote:

$ git remote
origin
或者,用git remote -v顯示更詳細(xì)的信息:

$ git remote -v
origin git@github.com:michaelliao/learngit.git (fetch)
origin git@github.com:michaelliao/learngit.git (push)
上面顯示了可以抓取和推送的origin的地址尘颓。如果沒有推送權(quán)限走触,就看不到push的地址。

推送分支
推送分支泥耀,就是把該分支上的所有本地提交推送到遠(yuǎn)程庫(kù)饺汹。推送時(shí),要指定本地分支痰催,這樣兜辞,Git就會(huì)把該分支推送到遠(yuǎn)程庫(kù)對(duì)應(yīng)的遠(yuǎn)程分支上:

$ git push origin master
如果要推送其他分支,比如dev夸溶,就改成:

$ git push origin dev
但是逸吵,并不是一定要把本地分支往遠(yuǎn)程推送,那么缝裁,哪些分支需要推送扫皱,哪些不需要呢?

master分支是主分支捷绑,因此要時(shí)刻與遠(yuǎn)程同步韩脑;

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:michaelliao/learngit.git
Cloning into 'learngit'...
remote: Counting objects: 40, done.
remote: Compressing objects: 100% (21/21), done.
remote: Total 40 (delta 14), reused 40 (delta 14), pack-reused 0
Receiving objects: 100% (40/40), done.
Resolving deltas: 100% (14/14), done.
當(dāng)你的小伙伴從遠(yuǎn)程庫(kù)clone時(shí),默認(rèn)情況下撩满,你的小伙伴只能看到本地的master分支蜒程。不信可以用git branch命令看看:

$ git branch

  • master
    現(xiàn)在绅你,你的小伙伴要在dev分支上開發(fā),就必須創(chuàng)建遠(yuǎn)程origin的dev分支到本地昭躺,于是他用這個(gè)命令創(chuàng)建本地dev分支:

$ git checkout -b dev origin/dev
現(xiàn)在忌锯,他就可以在dev上繼續(xù)修改,然后领炫,時(shí)不時(shí)地把dev分支push到遠(yuǎn)程:

$ git add env.txt

$ git commit -m "add env"
[dev 7a5e5dd] add env
1 file changed, 1 insertion(+)
create mode 100644 env.txt

$ git push origin dev
Counting objects: 3, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (2/2), done.
Writing objects: 100% (3/3), 308 bytes | 308.00 KiB/s, done.
Total 3 (delta 0), reused 0 (delta 0)
To github.com:michaelliao/learngit.git
f52c633..7a5e5dd dev -> dev
你的小伙伴已經(jīng)向origin/dev分支推送了他的提交偶垮,而碰巧你也對(duì)同樣的文件作了修改,并試圖推送:

$ cat env.txt
env

$ git add env.txt

$ git commit -m "add new env"
[dev 7bd91f1] add new env
 1 file changed, 1 insertion(+)
 create mode 100644 env.txt

$ git push origin dev
To github.com:michaelliao/learngit.git
 ! [rejected]        dev -> dev (non-fast-forward)
error: failed to push some refs to 'git@github.com:michaelliao/learngit.git'
hint: Updates were rejected because the tip of your current branch is behind
hint: its remote counterpart. Integrate the remote changes (e.g.
hint: '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
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
Branch 'dev' set up to track remote branch 'dev' from 'origin'.

再pull:

$ git pull
Auto-merging env.txt
CONFLICT (add/add): Merge conflict in env.txt
Automatic merge failed; fix conflicts and then commit the result.

這回git pull成功,但是合并有沖突胸哥,需要手動(dòng)解決漓糙,解決的方法和分支管理中的解決沖突完全一樣。解決后烘嘱,提交,再push:

$ git commit -m "fix env conflict"
[dev 57c53ab] fix env conflict

$ git push origin dev
Counting objects: 6, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (4/4), done.
Writing objects: 100% (6/6), 621 bytes | 621.00 KiB/s, done.
Total 6 (delta 0), reused 0 (delta 0)
To github.com:michaelliao/learngit.git
   7a5e5dd..57c53ab  dev -> dev

因此蝗蛙,多人協(xié)作的工作模式通常是這樣:

首先蝇庭,可以試圖用git push origin <branch-name>推送自己的修改;

如果推送失敗捡硅,則因?yàn)檫h(yuǎn)程分支比你的本地更新哮内,需要先用git pull試圖合并;

如果合并有沖突壮韭,則解決沖突北发,并在本地提交;

沒有沖突或者解決掉沖突后喷屋,再用git push origin <branch-name>推送就能成功琳拨!

如果git pull提示no tracking information,則說明本地分支和遠(yuǎn)程分支的鏈接關(guān)系沒有創(chuàng)建屯曹,用命令git branch --set-upstream-to <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 branch-name origin/branch-name冻辩;

從遠(yuǎn)程抓取分支,使用git pull拆祈,如果有沖突恨闪,要先處理沖突。

Rebase

在上一節(jié)我們看到了放坏,多人在同一個(gè)分支上協(xié)作時(shí)咙咽,很容易出現(xiàn)沖突。即使沒有沖突淤年,后push的童鞋不得不先pull钧敞,在本地合并,然后才能push成功麸粮。

每次合并再push后溉苛,分支變成了這樣:

$ git log --graph --pretty=oneline --abbrev-commit
* d1be385 (HEAD -> master, origin/master) init hello
*   e5e69f1 Merge branch 'dev'
|\  
| *   57c53ab (origin/dev, dev) fix env conflict
| |\  
| | * 7a5e5dd add env
| * | 7bd91f1 add new env
| |/  
* |   12a631b merged bug fix 101
|\ \  
| * | 4c805e2 fix bug 101
|/ /  
* |   e1e9c68 merge with no-ff
|\ \  
| |/  
| * f52c633 add merge
|/  
*   cf810e4 conflict fixed

總之看上去很亂,有強(qiáng)迫癥的童鞋會(huì)問:為什么Git的提交歷史不能是一條干凈的直線弄诲?

其實(shí)是可以做到的愚战!

Git有一種稱為rebase的操作,有人把它翻譯成“變基”齐遵。

[圖片上傳失敗...(image-3a5070-1623061368755)]

先不要隨意展開想象寂玲。我們還是從實(shí)際問題出發(fā),看看怎么把分叉的提交變成直線梗摇。

在和遠(yuǎn)程分支同步后拓哟,我們對(duì)hello.py這個(gè)文件做了兩次提交。用git log命令看看:

$ git log --graph --pretty=oneline --abbrev-commit
* 582d922 (HEAD -> master) add author
* 8875536 add comment
* d1be385 (origin/master) init hello
*   e5e69f1 Merge branch 'dev'
|\  
| *   57c53ab (origin/dev, dev) fix env conflict
| |\  
| | * 7a5e5dd add env
| * | 7bd91f1 add new env
...

注意到Git用(HEAD -> master)(origin/master)標(biāo)識(shí)出當(dāng)前分支的HEAD和遠(yuǎn)程origin的位置分別是582d922 add authord1be385 init hello留美,本地分支比遠(yuǎn)程分支快兩個(gè)提交彰檬。

現(xiàn)在我們嘗試推送本地分支:

$ git push origin master
To github.com:michaelliao/learngit.git
 ! [rejected]        master -> master (fetch first)
error: failed to push some refs to 'git@github.com:michaelliao/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.

很不幸伸刃,失敗了,這說明有人先于我們推送了遠(yuǎn)程分支逢倍。按照經(jīng)驗(yàn)捧颅,先pull一下:

$ git pull
remote: Counting objects: 3, done.
remote: Compressing objects: 100% (1/1), done.
remote: Total 3 (delta 1), reused 3 (delta 1), pack-reused 0
Unpacking objects: 100% (3/3), done.
From github.com:michaelliao/learngit
   d1be385..f005ed4  master     -> origin/master
 * [new tag]         v1.0       -> v1.0
Auto-merging hello.py
Merge made by the 'recursive' strategy.
 hello.py | 1 +
 1 file changed, 1 insertion(+)

再用git status看看狀態(tài):

$ git status
On branch master
Your branch is ahead of 'origin/master' by 3 commits.
  (use "git push" to publish your local commits)

nothing to commit, working tree clean

加上剛才合并的提交,現(xiàn)在我們本地分支比遠(yuǎn)程分支超前3個(gè)提交较雕。

git log看看:

$ git log --graph --pretty=oneline --abbrev-commit
*   e0ea545 (HEAD -> master) Merge branch 'master' of github.com:michaelliao/learngit
|\  
| * f005ed4 (origin/master) set exit=1
* | 582d922 add author
* | 8875536 add comment
|/  
* d1be385 init hello
...

對(duì)強(qiáng)迫癥童鞋來說碉哑,現(xiàn)在事情有點(diǎn)不對(duì)頭,提交歷史分叉了亮蒋。如果現(xiàn)在把本地分支push到遠(yuǎn)程扣典,有沒有問題?

有慎玖!

什么問題贮尖?

不好看!

有沒有解決方法趁怔?

有湿硝!

這個(gè)時(shí)候,rebase就派上了用場(chǎng)润努。我們輸入命令git rebase試試:

$ git rebase
First, rewinding head to replay your work on top of it...
Applying: add comment
Using index info to reconstruct a base tree...
M   hello.py
Falling back to patching base and 3-way merge...
Auto-merging hello.py
Applying: add author
Using index info to reconstruct a base tree...
M   hello.py
Falling back to patching base and 3-way merge...
Auto-merging hello.py

輸出了一大堆操作关斜,到底是啥效果?再用git log看看:

$ git log --graph --pretty=oneline --abbrev-commit
* 7e61ed4 (HEAD -> master) add author
* 3611cfe add comment
* f005ed4 (origin/master) set exit=1
* d1be385 init hello
...

原本分叉的提交現(xiàn)在變成一條直線了铺浇!這種神奇的操作是怎么實(shí)現(xiàn)的痢畜?其實(shí)原理非常簡(jiǎn)單。我們注意觀察鳍侣,發(fā)現(xiàn)Git把我們本地的提交“挪動(dòng)”了位置丁稀,放到了f005ed4 (origin/master) set exit=1之后,這樣倚聚,整個(gè)提交歷史就成了一條直線二驰。rebase操作前后,最終的提交內(nèi)容是一致的秉沼,但是,我們本地的commit修改內(nèi)容已經(jīng)變化了矿酵,它們的修改不再基于d1be385 init hello唬复,而是基于f005ed4 (origin/master) set exit=1,但最后的提交7e61ed4內(nèi)容是一致的全肮。

這就是rebase操作的特點(diǎn):把分叉的提交歷史“整理”成一條直線敞咧,看上去更直觀。缺點(diǎn)是本地的分叉提交已經(jīng)被修改過了辜腺。

最后休建,通過push操作把本地分支推送到遠(yuǎn)程:

Mac:~/learngit michael$ git push origin master
Counting objects: 6, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (5/5), done.
Writing objects: 100% (6/6), 576 bytes | 576.00 KiB/s, done.
Total 6 (delta 2), reused 0 (delta 0)
remote: Resolving deltas: 100% (2/2), completed with 1 local object.
To github.com:michaelliao/learngit.git
   f005ed4..7e61ed4  master -> master

再用git log看看效果:

$ git log --graph --pretty=oneline --abbrev-commit
* 7e61ed4 (HEAD -> master, origin/master) add author
* 3611cfe add comment
* f005ed4 set exit=1
* d1be385 init hello
...

遠(yuǎn)程分支的提交歷史也是一條直線乍恐。

標(biāo)簽管理

發(fā)布一個(gè)版本時(shí),我們通常先在版本庫(kù)中打一個(gè)標(biāo)簽(tag)测砂,這樣茵烈,就唯一確定了打標(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)簽都是瞬間完成的洒扎。

Git有commit,為什么還要引入tag甜橱?

“請(qǐng)把上周一的那個(gè)版本打包發(fā)布逊笆,commit號(hào)是6a5819e...”

“一串亂七八糟的數(shù)字不好找!”

如果換一個(gè)辦法:

“請(qǐng)把上周一的那個(gè)版本打包發(fā)布岂傲,版本號(hào)是v1.2”

“好的难裆,按照tag v1.2查找commit就行!”

所以镊掖,tag就是一個(gè)讓人容易記住的有意義的名字乃戈,它跟某個(gè)commit綁在一起。

創(chuàng)建標(biāo)簽

在Git中打標(biāo)簽非常簡(jiǎn)單亩进,首先症虑,切換到需要打標(biāo)簽的分支上:

$ git branch

  • dev
    master
    $ git checkout master
    Switched to branch '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í)候谍憔,如果忘了打標(biāo)簽,比如主籍,現(xiàn)在已經(jīng)是周五了习贫,但應(yīng)該在周一打的標(biāo)簽沒有打,怎么辦?

方法是找到歷史提交的commit id,然后打上就可以了:

$ git log --pretty=oneline --abbrev-commit
12a631b (HEAD -> master, tag: v1.0, origin/master) merged bug fix 101
4c805e2 fix bug 101
e1e9c68 merge with no-ff
f52c633 add merge
cf810e4 conflict fixed
5dc6824 & simple
14096d0 AND simple
b17d20e branch test
d46f35e remove test.txt
b84166e add test.txt
519219b git tracks changes
e43a48b understand how stage works
1094adb append GPL
e475afc add distributed
eaadf4e wrote a readme file
比方說要對(duì)add merge這次提交打標(biāo)簽胰坟,它對(duì)應(yīng)的commit id是f52c633辜妓,敲入命令:

$ git tag v0.9 f52c633
再用命令git tag查看標(biāo)簽:

$ git tag
v0.9
v1.0
注意防楷,標(biāo)簽不是按時(shí)間順序列出汰具,而是按字母排序的卢佣》拷遥可以用git show <tagname>查看標(biāo)簽信息:

$ git show v0.9
commit f52c63349bc3c1593499807e5c8e972b82c8f286 (tag: v0.9)
Author: Michael Liao askxuefeng@gmail.com
Date: Fri May 18 21:56:54 2018 +0800

add merge

diff --git a/readme.txt b/readme.txt
...
可以看到袜硫,v0.9確實(shí)打在add merge這次提交上氯葬。

還可以創(chuàng)建帶有說明的標(biāo)簽,用-a指定標(biāo)簽名父款,-m指定說明文字:

$ git tag -a v0.1 -m "version 0.1 released" 1094adb
用命令git show <tagname>可以看到說明文字:

$ git show v0.1
tag v0.1
Tagger: Michael Liao askxuefeng@gmail.com
Date: Fri May 18 22:48:43 2018 +0800

version 0.1 released

commit 1094adb7b9b3807259d8cb349e7df1d4d6477073 (tag: v0.1)
Author: Michael Liao askxuefeng@gmail.com
Date: Fri May 18 21:06:15 2018 +0800

append GPL

diff --git a/readme.txt b/readme.txt
...
注意:標(biāo)簽總是和某個(gè)commit掛鉤溢谤。如果這個(gè)commit既出現(xiàn)在master分支,又出現(xiàn)在dev分支憨攒,那么在這兩個(gè)分支上都可以看到這個(gè)標(biāo)簽世杀。

小結(jié)
命令git tag <tagname>用于新建一個(gè)標(biāo)簽,默認(rèn)為HEAD肝集,也可以指定一個(gè)commit id瞻坝;

命令git tag -a <tagname> -m "blablabla..."可以指定標(biāo)簽信息;

命令git tag可以查看所有標(biāo)簽杏瞻。

操作標(biāo)簽

如果標(biāo)簽打錯(cuò)了所刀,也可以刪除:

$ git tag -d v0.1
Deleted tag 'v0.1' (was f15b0dd)
因?yàn)閯?chuàng)建的標(biāo)簽都只存儲(chǔ)在本地,不會(huì)自動(dòng)推送到遠(yuǎn)程捞挥。所以浮创,打錯(cuò)的標(biāo)簽可以在本地安全刪除。

如果要推送某個(gè)標(biāo)簽到遠(yuǎn)程砌函,使用命令git push origin <tagname>:

$ git push origin v1.0
Total 0 (delta 0), reused 0 (delta 0)
To github.com:michaelliao/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 github.com:michaelliao/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 f52c633)
然后垦沉,從遠(yuǎn)程刪除。刪除命令也是push仍劈,但是格式如下:

$ git push origin :refs/tags/v0.9
To github.com:michaelliao/learngit.git

  • [deleted] v0.9
    要看看是否真的從遠(yuǎn)程庫(kù)刪除了標(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)的遠(yuǎn)程倉(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)目呢蝶缀?比如人氣極高的bootstrap項(xiàng)目,這是一個(gè)非常強(qiáng)大的CSS框架薄货,你可以訪問它的項(xiàng)目主頁(yè)https://github.com/twbs/bootstrap翁都,點(diǎn)“Fork”就在自己的賬號(hào)下克隆了一個(gè)bootstrap倉(cāng)庫(kù),然后谅猾,從自己的賬號(hào)下clone:

git clone git@github.com:michaelliao/bootstrap.git

一定要從自己的賬號(hào)下clone倉(cāng)庫(kù)柄慰,這樣你才能推送修改。如果從bootstrap的作者的倉(cāng)庫(kù)地址git@github.com:twbs/bootstrap.git克隆赊瞬,因?yàn)闆]有權(quán)限先煎,你將不能推送修改。

Bootstrap的官方倉(cāng)庫(kù)twbs/bootstrap巧涧、你在GitHub上克隆的倉(cāng)庫(kù)my/bootstrap薯蝎,以及你自己克隆到本地電腦的倉(cāng)庫(kù),他們的關(guān)系就像下圖顯示的那樣:

┌─ GitHub ────────────────────────────────────┐
│                                             │
│ ┌─────────────────┐     ┌─────────────────┐ │
│ │ twbs/bootstrap  │────>│  my/bootstrap   │ │
│ └─────────────────┘     └─────────────────┘ │
│                                  ▲          │
└──────────────────────────────────┼──────────┘
                                   ▼
                          ┌─────────────────┐
                          │ local/bootstrap │
                          └─────────────────┘

如果你想修復(fù)bootstrap的一個(gè)bug谤绳,或者新增一個(gè)功能占锯,立刻就可以開始干活,干完后缩筛,往自己的倉(cāng)庫(kù)推送消略。

如果你希望bootstrap的官方庫(kù)能接受你的修改,你就可以在GitHub上發(fā)起一個(gè)pull request瞎抛。當(dāng)然艺演,對(duì)方是否接受你的pull request就不一定了。

如果你沒能力修改bootstrap,但又想要試一把pull request胎撤,那就Fork一下我的倉(cāng)庫(kù):https://github.com/michaelliao/learngit晓殊,創(chuàng)建一個(gè)your-github-id.txt的文本文件,寫點(diǎn)自己學(xué)習(xí)Git的心得伤提,然后推送一個(gè)pull request給我巫俺,我會(huì)視心情而定是否接受。

小結(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 config --global color.ui true

這樣豪娜,Git會(huì)適當(dāng)?shù)仫@示不同的顏色,比如git status命令:

image.png

文件名就會(huì)標(biāo)上顏色哟楷。

我們?cè)诤竺孢€會(huì)介紹如何更好地配置Git瘤载,以便讓你的工作更高效

忽略特殊文件

有些時(shí)候,你必須把某些文件放到Git工作目錄中卖擅,但又不能提交它們鸣奔,比如保存了數(shù)據(jù)庫(kù)密碼的配置文件啦,等等惩阶,每次git status都會(huì)顯示Untracked files ...挎狸,有強(qiáng)迫癥的童鞋心里肯定不爽。

好在Git考慮到了大家的感受断楷,這個(gè)問題解決起來也很簡(jiǎn)單锨匆,在Git工作區(qū)的根目錄下創(chuàng)建一個(gè)特殊的.gitignore文件,然后把要忽略的文件名填進(jìn)去冬筒,Git就會(huì)自動(dòng)忽略這些文件恐锣。

不需要從頭寫.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了。

有些時(shí)候彻秆,你想添加一個(gè)文件到Git楔绞,但發(fā)現(xiàn)添加不了,原因是這個(gè)文件被.gitignore忽略了:

$ git add App.class
The following paths are ignored by one of your .gitignore files:
App.class
Use -f if you really want to add them.

如果你確實(shí)想添加該文件唇兑,可以用-f強(qiáng)制添加到Git:

$ git add -f App.class

或者你發(fā)現(xiàn)酒朵,可能是.gitignore寫得有問題,需要找出來到底哪個(gè)規(guī)則寫錯(cuò)了扎附,可以用git check-ignore命令檢查:

$ git check-ignore -v App.class
.gitignore:3:*.class    App.class

Git會(huì)告訴我們蔫耽,.gitignore的第3行規(guī)則忽略了該文件,于是我們就可以知道應(yīng)該修訂哪個(gè)規(guī)則帕棉。

還有些時(shí)候针肥,當(dāng)我們編寫了規(guī)則排除了部分文件時(shí):

# 排除所有.開頭的隱藏文件:
.*
# 排除所有.class文件:
*.class

但是我們發(fā)現(xiàn).*這個(gè)規(guī)則把.gitignore也排除了,并且App.class需要被添加到版本庫(kù)香伴,但是被*.class規(guī)則排除了慰枕。

雖然可以用git add -f強(qiáng)制添加進(jìn)去,但有強(qiáng)迫癥的童鞋還是希望不要破壞.gitignore規(guī)則即纲,這個(gè)時(shí)候具帮,可以添加兩條例外規(guī)則:

# 排除所有.開頭的隱藏文件:
.*
# 排除所有.class文件:
*.class

# 不排除.gitignore和App.class:
!.gitignore
!App.class

把指定文件排除在.gitignore規(guī)則外的寫法就是!+文件名,所以,只需把例外文件添加進(jìn)去即可蜂厅。

小結(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

好了飞主,現(xiàn)在敲git st看看效果狮惜。

當(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

以后提交就可以簡(jiǎn)寫成:

$ git ci -m "bala bala bala..."

--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'

當(dāng)你敲入命令:

$ git unstage test.py

實(shí)際上Git執(zhí)行的是:

$ git reset HEAD test.py

配置一個(gè)git last佩番,讓其顯示最后一次提交信息:

$ git config --global alias.last 'log -1'

這樣众旗,用git last就能顯示最近一次的提交:

$ git last
commit adca45d317e6d8a4b23f9811c3d7b7f0f180bfe2
Merge: bd6ae48 291bea8
Author: Michael Liao <askxuefeng@gmail.com>
Date:   Thu Aug 22 22:49:22 2013 +0800

    merge & fix hello.py

甚至還有人喪心病狂地把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 lg的效果:

image.png

為什么不早點(diǎn)告訴我?別激動(dòng)趟畏,咱不是為了多記幾個(gè)英文單詞嘛贡歧!

配置文件
配置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:michaelliao/learngit.git
fetch = +refs/heads/:refs/remotes/origin/
[branch "master"]
remote = origin
merge = refs/heads/master
[alias]
last = log -1
別名就在[alias]后面绍弟,要?jiǎng)h除別名,直接把對(duì)應(yīng)的行刪掉即可著洼。

而當(dāng)前用戶的Git配置文件放在用戶主目錄下的一個(gè)隱藏文件.gitconfig中:

$ cat .gitconfig
[alias]
co = checkout
ci = commit
br = branch
st = status
[user]
name = Your Name
email = your@email.com
配置別名也可以直接修改這個(gè)文件樟遣,如果改錯(cuò)了而叼,可以刪掉文件重新通過命令配置。

小結(jié)
給Git配置好別名豹悬,就可以輸入命令時(shí)偷個(gè)懶葵陵。我們鼓勵(lì)偷懶。

搭建Git服務(wù)器

遠(yuǎn)程倉(cāng)庫(kù)一節(jié)中瞻佛,我們講了遠(yuǎn)程倉(cāng)庫(kù)實(shí)際上和本地倉(cāng)庫(kù)沒啥不同脱篙,純粹為了7x24小時(shí)開機(jī)并交換大家的修改。

GitHub就是一個(gè)免費(fèi)托管開源代碼的遠(yuǎn)程倉(cāng)庫(kù)伤柄。但是對(duì)于某些視源代碼如生命的商業(yè)公司來說涡尘,既不想公開源代碼,又舍不得給GitHub交保護(hù)費(fèi)响迂,那就只能自己搭建一臺(tái)Git服務(wù)器作為私有倉(cāng)庫(kù)使用。

搭建Git服務(wù)器需要準(zhǔn)備一臺(tái)運(yùn)行Linux的機(jī)器细疚,強(qiáng)烈推薦用Ubuntu或Debian蔗彤,這樣,通過幾條簡(jiǎn)單的apt命令就可以完成安裝疯兼。

假設(shè)你已經(jīng)有sudo權(quán)限的用戶賬號(hào)然遏,下面,正式開始安裝吧彪。

第一步待侵,安裝git

$ sudo apt-get install git

第二步,創(chuàng)建一個(gè)git用戶姨裸,用來運(yùn)行git服務(wù):

$ sudo adduser git

第三步秧倾,創(chuàng)建證書登錄:

收集所有需要登錄的用戶的公鑰,就是他們自己的id_rsa.pub文件傀缩,把所有公鑰導(dǎo)入到/home/git/.ssh/authorized_keys文件里那先,一行一個(gè)。

第四步赡艰,初始化Git倉(cāng)庫(kù):

先選定一個(gè)目錄作為Git倉(cāng)庫(kù)售淡,假定是/srv/sample.git,在/srv目錄下輸入命令:

$ sudo git init --bare sample.git

Git就會(huì)創(chuàng)建一個(gè)裸倉(cāng)庫(kù)慷垮,裸倉(cāng)庫(kù)沒有工作區(qū)揖闸,因?yàn)榉?wù)器上的Git倉(cāng)庫(kù)純粹是為了共享,所以不讓用戶直接登錄到服務(wù)器上去改工作區(qū)料身,并且服務(wù)器上的Git倉(cāng)庫(kù)通常都以.git結(jié)尾汤纸。然后,把owner改為git

$ sudo chown -R git:git sample.git

第五步惯驼,禁用shell登錄:

出于安全考慮蹲嚣,第二步創(chuàng)建的git用戶不允許登錄shell递瑰,這可以通過編輯/etc/passwd文件完成。找到類似下面的一行:

git:x:1001:1001:,,,:/home/git:/bin/bash

改為:

git:x:1001:1001:,,,:/home/git:/usr/bin/git-shell

這樣隙畜,git用戶可以正常通過ssh使用git抖部,但無法登錄shell,因?yàn)槲覀優(yōu)?code>git用戶指定的git-shell每次一登錄就自動(dòng)退出议惰。

第六步慎颗,克隆遠(yuǎn)程倉(cāng)庫(kù):

現(xiàn)在,可以通過git clone命令克隆遠(yuǎn)程倉(cāng)庫(kù)了言询,在各自的電腦上運(yùn)行:

$ git clone git@server:/srv/sample.git
Cloning into 'sample'...
warning: You appear to have cloned an empty repository.

剩下的推送就簡(jiǎn)單了俯萎。

管理公鑰

如果團(tuán)隊(duì)很小,把每個(gè)人的公鑰收集起來放到服務(wù)器的/home/git/.ssh/authorized_keys文件里就是可行的运杭。如果團(tuán)隊(duì)有幾百號(hào)人夫啊,就沒法這么玩了,這時(shí)辆憔,可以用Gitosis來管理公鑰撇眯。

這里我們不介紹怎么玩Gitosis了,幾百號(hào)人的團(tuán)隊(duì)基本都在500強(qiáng)了虱咧,相信找個(gè)高水平的Linux管理員問題不大熊榛。

管理權(quán)限

有很多不但視源代碼如生命,而且視員工為竊賊的公司腕巡,會(huì)在版本控制系統(tǒng)里設(shè)置一套完善的權(quán)限控制玄坦,每個(gè)人是否有讀寫權(quán)限會(huì)精確到每個(gè)分支甚至每個(gè)目錄下。因?yàn)镚it是為L(zhǎng)inux源代碼托管而開發(fā)的绘沉,所以Git也繼承了開源社區(qū)的精神煎楣,不支持權(quán)限控制。不過车伞,因?yàn)镚it支持鉤子(hook)转质,所以,可以在服務(wù)器端編寫一系列腳本來控制提交等操作帖世,達(dá)到權(quán)限控制的目的休蟹。Gitolite就是這個(gè)工具。

這里我們也不介紹Gitolite了日矫,不要把有限的生命浪費(fèi)到權(quán)限斗爭(zhēng)中赂弓。

小結(jié)

  • 搭建Git服務(wù)器非常簡(jiǎn)單,通常10分鐘即可完成哪轿;

  • 要方便管理公鑰盈魁,用Gitosis

  • 要像SVN那樣變態(tài)地控制權(quán)限窃诉,用Gitolite杨耙。

Git工具

Tower赤套、SourceTree

總結(jié)

終于到了期末總結(jié)的時(shí)刻了!

經(jīng)過幾天的學(xué)習(xí)珊膜,相信你對(duì)Git已經(jīng)初步掌握容握。一開始,可能覺得Git上手比較困難车柠,尤其是已經(jīng)熟悉SVN的童鞋剔氏,沒關(guān)系,多操練幾次竹祷,就會(huì)越用越順手谈跛。

Git雖然極其強(qiáng)大,命令繁多塑陵,但常用的就那么十來個(gè)感憾,掌握好這十幾個(gè)常用命令,你已經(jīng)可以得心應(yīng)手地使用Git了令花。

友情附贈(zèng)Git Cheat Sheet吹菱,建議打印出來備用:

Git Cheat Sheet

現(xiàn)在告訴你Git的官方網(wǎng)站:http://git-scm.com,英文自我感覺不錯(cuò)的童鞋彭则,可以經(jīng)常去官網(wǎng)看看。

如果你學(xué)了Git后谍咆,工作效率大增宗侦,有更多的空閑時(shí)間健身看電影拨与,那我的教學(xué)目標(biāo)就達(dá)到了。

謝謝觀看芬萍!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市搔啊,隨后出現(xiàn)的幾起案子柬祠,更是在濱河造成了極大的恐慌,老刑警劉巖负芋,帶你破解...
    沈念sama閱讀 206,968評(píng)論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件漫蛔,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡旧蛾,警方通過查閱死者的電腦和手機(jī)莽龟,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來锨天,“玉大人毯盈,你說我怎么就攤上這事〔“溃” “怎么了搂赋?”我有些...
    開封第一講書人閱讀 153,220評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵赘阀,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我脑奠,道長(zhǎng)基公,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,416評(píng)論 1 279
  • 正文 為了忘掉前任捺信,我火速辦了婚禮酌媒,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘迄靠。我一直安慰自己秒咨,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,425評(píng)論 5 374
  • 文/花漫 我一把揭開白布掌挚。 她就那樣靜靜地躺著雨席,像睡著了一般。 火紅的嫁衣襯著肌膚如雪吠式。 梳的紋絲不亂的頭發(fā)上陡厘,一...
    開封第一講書人閱讀 49,144評(píng)論 1 285
  • 那天,我揣著相機(jī)與錄音特占,去河邊找鬼糙置。 笑死,一個(gè)胖子當(dāng)著我的面吹牛是目,可吹牛的內(nèi)容都是我干的谤饭。 我是一名探鬼主播,決...
    沈念sama閱讀 38,432評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼懊纳,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼揉抵!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起嗤疯,我...
    開封第一講書人閱讀 37,088評(píng)論 0 261
  • 序言:老撾萬榮一對(duì)情侶失蹤冤今,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后茂缚,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體戏罢,經(jīng)...
    沈念sama閱讀 43,586評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,028評(píng)論 2 325
  • 正文 我和宋清朗相戀三年脚囊,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了帖汞。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,137評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡凑术,死狀恐怖翩蘸,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情淮逊,我是刑警寧澤催首,帶...
    沈念sama閱讀 33,783評(píng)論 4 324
  • 正文 年R本政府宣布扶踊,位于F島的核電站,受9級(jí)特大地震影響郎任,放射性物質(zhì)發(fā)生泄漏秧耗。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,343評(píng)論 3 307
  • 文/蒙蒙 一舶治、第九天 我趴在偏房一處隱蔽的房頂上張望分井。 院中可真熱鬧,春花似錦霉猛、人聲如沸尺锚。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,333評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)瘫辩。三九已至,卻和暖如春坛悉,著一層夾襖步出監(jiān)牢的瞬間伐厌,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,559評(píng)論 1 262
  • 我被黑心中介騙來泰國(guó)打工裸影, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留挣轨,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,595評(píng)論 2 355
  • 正文 我出身青樓轩猩,卻偏偏與公主長(zhǎng)得像卷扮,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子界轩,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,901評(píng)論 2 345

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