創(chuàng)建版本庫(kù)
版本庫(kù)又名倉(cāng)庫(kù),英文名repository晌块,這個(gè)目錄里面的所有文件都可以被Git管理起來(lái)爱沟,每個(gè)文件的修改、刪除匆背,Git都能跟蹤呼伸,以便任何時(shí)刻都可以追蹤歷史,或者在將來(lái)某個(gè)時(shí)刻可以“還原”钝尸。
# 選擇一個(gè)合適的地方蜂大,創(chuàng)建一個(gè)空目錄
$ mkdir learngit
$ cd learngit
$ pwd
/Users/michael/learngit
# 通過(guò)git init命令把這個(gè)目錄變成Git可以管理的倉(cāng)庫(kù)
$ git init
Initialized empty Git repository in /Users/michael/learngit/.git/
# 當(dāng)前目錄下多了一個(gè).git的目錄,這個(gè)目錄是Git來(lái)跟蹤管理版本庫(kù)的
# 編寫(xiě)一個(gè)readme.txt文件蝶怔,內(nèi)容如下:
Git is a version control system.
Git is free software.
# 第一步,用命令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 100644 readme.txt
# git commit命令隙咸,-m后面輸入的是本次提交的說(shuō)明
# git commit命令執(zhí)行成功后會(huì)告訴你沐悦,1個(gè)文件被改動(dòng)(我們新添加的readme.txt文件),插入了兩行內(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."
小結(jié):
- 初始化一個(gè)Git倉(cāng)庫(kù),使用git init命令基矮。
- 添加文件到Git倉(cāng)庫(kù)淆储,分兩步:
- 第一步,使用命令git add <file>家浇,注意本砰,可反復(fù)多次使用,添加多個(gè)文件钢悲;
- 第二步点额,使用命令git commit,完成莺琳。
時(shí)光機(jī)穿梭
# 繼續(xù)修改readme.txt文件还棱,改成如下內(nèi)容:
Git is a distributed version control system.
Git is free software.
# 運(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被修改過(guò)了诱贿,但還沒(méi)有準(zhǔn)備提交的修改
# 看看具體修改了什么內(nèi)容,需要用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 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 ea34578] 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 directory clean)
# Git告訴我們當(dāng)前沒(méi)有需要提交的修改,而且孔厉,工作目錄是干凈(working directory clean)的拯钻。
小結(jié):
- 要隨時(shí)掌握工作區(qū)的狀態(tài),使用git status命令撰豺。
- 如果git status告訴你有文件被修改過(guò)粪般,用git diff可以查看修改內(nèi)容。
版本回退
# 回顧一下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.
# 在實(shí)際工作中污桦,我們腦子里怎么可能記得一個(gè)幾千行的文件每次都改了什么內(nèi)容亩歹,不然要版本控制系統(tǒng)干什么。版本控制系統(tǒng)肯定有某個(gè)命令可以告訴我們歷史記錄凡橱,在Git中小作,我們用git log命令查看:
$ git log
commit 3628164fb26d48395383f8f31179f24e0882e1e0
Author: Michael Liao <askxuefeng@gmail.com>
Date: Tue Aug 20 15:11:49 2013 +0800
append GPL
commit ea34578d5496d7dd233c827ed32a8cd576c5ee85
Author: Michael Liao <askxuefeng@gmail.com>
Date: Tue Aug 20 14:53:12 2013 +0800
add distributed
commit cb926e7ea50ad11b8f9e909c05226233bf755030
Author: Michael Liao <askxuefeng@gmail.com>
Date: Mon Aug 19 17:51:55 2013 +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
3628164fb26d48395383f8f31179f24e0882e1e0 append GPL
ea34578d5496d7dd233c827ed32a8cd576c5ee85 add distributed
cb926e7ea50ad11b8f9e909c05226233bf755030 wrote a readme file
# 看到的一大串類似3628164...882e1e0的是commit id(版本號(hào))巡李,是一個(gè)SHA1計(jì)算出來(lái)的一個(gè)非常大的數(shù)字诡宗,用十六進(jìn)制表示
# 在Git中,用HEAD表示當(dāng)前版本击儡,也就是最新的提交3628164...882e1e0(注意我的提交ID和你的肯定不一樣)塔沃,上一個(gè)版本就是HEAD^,上上一個(gè)版本就是HEAD^^阳谍,當(dāng)然往上100個(gè)版本寫(xiě)100個(gè)^比較容易數(shù)不過(guò)來(lái)蛀柴,所以寫(xiě)成HEAD~100。
# 現(xiàn)在矫夯,我們要把當(dāng)前版本“append GPL”回退到上一個(gè)版本“add distributed”鸽疾,就可以使用git reset命令:
$ git reset --hard HEAD^
HEAD is now at ea34578 add distributed
# 看看readme.txt的內(nèi)容是不是版本add distributed:
$ cat readme.txt
Git is a distributed version control system.
Git is free software.
# 我們用git log再看看現(xiàn)在版本庫(kù)的狀態(tài):
$ git log
commit ea34578d5496d7dd233c827ed32a8cd576c5ee85
Author: Michael Liao <askxuefeng@gmail.com>
Date: Tue Aug 20 14:53:12 2013 +0800
add distributed
commit cb926e7ea50ad11b8f9e909c05226233bf755030
Author: Michael Liao <askxuefeng@gmail.com>
Date: Mon Aug 19 17:51:55 2013 +0800
wrote a readme file
# 最新的那個(gè)版本append GPL已經(jīng)看不到了
# 辦法其實(shí)還是有的,只要上面的命令行窗口還沒(méi)有被關(guān)掉训貌,你就可以順著往上找啊找啊制肮,找到那個(gè)append GPL的commit id是3628164...冒窍,于是就可以指定回到未來(lái)的某個(gè)版本:
$ git reset --hard 3628164
HEAD is now at 3628164 append GPL
# 版本號(hào)沒(méi)必要寫(xiě)全,前幾位就可以了豺鼻,Git會(huì)自動(dòng)去找综液。當(dāng)然也不能只寫(xiě)前一兩位,因?yàn)镚it可能會(huì)找到多個(gè)版本號(hào)儒飒,就無(wú)法確定是哪一個(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井誉,改為指向add distributed蕉扮,然后順便把工作區(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用來(lái)記錄你的每一次命令:
$ git reflog
ea34578 HEAD@{0}: reset: moving to HEAD^
3628164 HEAD@{1}: commit: append GPL
ea34578 HEAD@{2}: commit: add distributed
cb926e7 HEAD@{3}: commit (initial): wrote a readme file
# 終于舒了口氣,第二行顯示append GPL的commit id是3628164尉咕,現(xiàn)在叠蝇,你又可以乘坐時(shí)光機(jī)回到未來(lái)了。
小結(jié)
- HEAD指向的版本就是當(dāng)前版本年缎,因此悔捶,Git允許我們?cè)诎姹镜臍v史之間穿梭,使用命令git reset --hard commit_id单芜。
- 穿梭前蜕该,用git log可以查看提交歷史,以便確定要回退到哪個(gè)版本洲鸠。
- 要重返未來(lái)堂淡,用git reflog查看命令歷史馋缅,以便確定要回到未來(lái)的哪個(gè)版本。
工作區(qū)和暫存區(qū)
工作區(qū)(Working Directory):就是你在電腦里能看到的目錄绢淀,比如我的learngit文件夾就是一個(gè)工作區(qū)
版本庫(kù)(Repository):工作區(qū)有一個(gè)隱藏目錄.git萤悴,這個(gè)不算工作區(qū),而是Git的版本庫(kù)更啄。
Git的版本庫(kù)里存了很多東西稚疹,其中最重要的就是稱為stage(或者叫index)的暫存區(qū),還有Git為我們自動(dòng)創(chuàng)建的第一個(gè)分支master祭务,以及指向master的一個(gè)指針叫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ū)的所有修改寺谤。
# 先對(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)容隨便寫(xiě))变屁。
# 先用git status查看一下?tīng)顟B(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還從來(lái)沒(méi)有被添加過(guò)粟关,所以它的狀態(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)就變成這樣了:
# 所以蛔垢,git add命令實(shí)際上就是把要提交的所有修改放到暫存區(qū)(Stage)击孩,然后,執(zhí)行g(shù)it commit就可以一次性把暫存區(qū)的所有修改提交到分支鹏漆。
$ git commit -m "understand how stage works"
[master 27c9860] understand how stage works
2 files changed, 675 insertions(+)
create mode 100644 LICENSE
# 一旦提交后巩梢,如果你又沒(méi)有對(duì)工作區(qū)做任何修改创泄,那么工作區(qū)就是“干凈”的:
$ git status
# On branch master
nothing to commit (working directory clean)
現(xiàn)在版本庫(kù)變成了這樣,暫存區(qū)就沒(méi)有任何內(nèi)容了:
小結(jié)
暫存區(qū)是Git非常重要的概念括蝠,弄明白了暫存區(qū)鞠抑,就弄明白了Git的很多操作到底干了什么。
管理修改
Git跟蹤并管理的是修改忌警,而非文件搁拙。
什么是修改?比如你新增了一行法绵,這就是一個(gè)修改箕速,刪除了一行,也是一個(gè)修改朋譬,更改了某些字符盐茎,也是一個(gè)修改,刪了一些又加了一些徙赢,也是一個(gè)修改字柠,甚至創(chuàng)建一個(gè)新文件,也算一個(gè)修改狡赐。
# 為什么說(shuō)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 d4f25b6] 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")
# Git管理的是修改哥倔,當(dāng)你用git add命令后,在工作區(qū)的第一次修改被放入暫存區(qū)揍庄,準(zhǔn)備提交咆蒿,但是,在工作區(qū)的第二次修改并沒(méi)有放入暫存區(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.
# 可見(jiàn),第二次修改確實(shí)沒(méi)有被提交别渔。
# 那怎么提交第二次修改呢附迷?你可以繼續(xù)git add再git commit惧互,也可以別著急提交第一次修改,先git add第二次修改喇伯,再git commit喊儡,就相當(dāng)于把兩次修改合并后一塊提交了
# 第一次修改 -> git add -> 第二次修改 -> git add -> git commit
撤銷修改
# 現(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.
# 既然錯(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自修改后還沒(méi)有被放到暫存區(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命令中的--很重要,沒(méi)有--稠鼻,就變成了“切換到另一個(gè)分支”的命令冈止,我們?cè)诤竺娴姆种Ч芾碇袝?huì)再次遇到git checkout命令。
# 現(xiàn)在假定是凌晨3點(diǎn)候齿,你不但寫(xiě)了一些胡話熙暴,還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è)問(wèn)題周霉。用git status查看一下,修改只是添加到了暫存區(qū)亚皂,還沒(méi)有提交:
$ 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
#
no changes added to commit (use "git add" and/or "git commit -a")
# 還記得如何丟棄工作區(qū)的修改嗎?
$ git checkout -- readme.txt
$ git status
# On branch master
nothing to commit (working directory clean)
# 現(xiàn)在与斤,假設(shè)你不但改錯(cuò)了東西肪康,還從暫存區(qū)提交到了版本庫(kù),怎么辦呢撩穿?還記得版本回退一節(jié)嗎磷支?可以回退到上一個(gè)版本。不過(guò)食寡,這是有條件的雾狈,就是你還沒(méi)有把自己的本地版本庫(kù)推送到遠(yuǎn)程。還記得Git是分布式版本控制系統(tǒng)嗎抵皱?我們后面會(huì)講到遠(yuǎn)程版本庫(kù)善榛,一旦你把“stupid boss”提交推送到遠(yuǎn)程版本庫(kù),你就真的慘了……
小結(jié)
場(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é)蚂四,不過(guò)前提是沒(méi)有推送到遠(yuǎn)程庫(kù)光戈。
刪除文件
# 在Git中哪痰,刪除也是一個(gè)修改操作遂赠,我們實(shí)戰(zhàn)一下,先添加一個(gè)新文件test.txt到Git并且提交:
$ git add test.txt
$ git commit -m "add test.txt"
[master 94cdc44] add test.txt
1 file changed, 1 insertion(+)
create mode 100644 test.txt
# 一般情況下晌杰,你通常直接在文件管理器中把沒(méi)用的文件刪了跷睦,或者用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 d17efd8] remove test.txt
1 file changed, 1 deletion(-)
delete mode 100644 test.txt
# 現(xiàn)在,文件就從版本庫(kù)中被刪除了层玲。
另一種情況是刪錯(cuò)了号醉,因?yàn)榘姹編?kù)里還有呢,所以可以很輕松地把誤刪的文件恢復(fù)到最新版本:
$ git checkout -- test.txt
# git checkout其實(shí)是用版本庫(kù)里的版本替換工作區(qū)的版本辛块,無(wú)論工作區(qū)是修改還是刪除畔派,都可以“一鍵還原”。
小結(jié)
命令git rm用于刪除一個(gè)文件润绵。如果一個(gè)文件已經(jīng)被提交到版本庫(kù)线椰,那么你永遠(yuǎn)不用擔(dān)心誤刪,但是要小心尘盼,你只能恢復(fù)文件到最新版本憨愉,你會(huì)丟失最近一次提交后你修改的內(nèi)容。
創(chuàng)建與合并分支
截止到目前卿捎,只有一條時(shí)間線莱衩,在Git里,這個(gè)分支叫主分支娇澎,即master分支笨蚁。HEAD嚴(yán)格來(lái)說(shuō)不是指向提交,而是指向master趟庄,master才是指向提交的括细,所以,HEAD指向的就是當(dāng)前分支戚啥。
一開(kāi)始的時(shí)候奋单,master分支是一條線,Git用master指向最新的提交猫十,再用HEAD指向master览濒,就能確定當(dāng)前分支,以及當(dāng)前分支的提交點(diǎn):
每次提交拖云,master分支都會(huì)向前移動(dòng)一步贷笛,這樣,隨著你不斷提交宙项,master分支的線也越來(lái)越長(zhǎng)
當(dāng)我們創(chuàng)建新的分支乏苦,例如dev時(shí),Git新建了一個(gè)指針叫dev,指向master相同的提交汇荐,再把HEAD指向dev洞就,就表示當(dāng)前分支在dev上:
你看,Git創(chuàng)建一個(gè)分支很快掀淘,因?yàn)槌嗽黾右粋€(gè)dev指針旬蟋,改改HEAD的指向,工作區(qū)的文件都沒(méi)有任何變化革娄!
不過(guò)咖为,從現(xiàn)在開(kāi)始,對(duì)工作區(qū)的修改和提交就是針對(duì)dev分支了稠腊,比如新提交一次后躁染,dev指針往前移動(dòng)一步,而master指針不變:
假如我們?cè)赿ev上的工作完成了架忌,就可以把dev合并到master上吞彤。Git怎么合并呢?最簡(jiǎn)單的方法叹放,就是直接把master指向dev的當(dāng)前提交饰恕,就完成了合并:
所以Git合并分支也很快!就改改指針井仰,工作區(qū)內(nèi)容也不變埋嵌!
合并完分支后,甚至可以刪除dev分支俱恶。刪除dev分支就是把dev指針給刪掉雹嗦,刪掉后贯被,我們就剩下了一條master分支:
# 首先西采,我們創(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 fec145a] branch test
1 file changed, 1 insertion(+)
# 現(xiàn)在,dev分支的工作完成蛾茉,我們就可以切換回master分支:
$ git checkout master
Switched to branch 'master'
切換回master分支后讼呢,再查看一個(gè)readme.txt文件,剛才添加的內(nèi)容不見(jiàn)了臀稚!因?yàn)槟莻€(gè)提交是在dev分支上吝岭,而master分支此刻的提交點(diǎn)并沒(méi)有變:
# 現(xiàn)在三痰,我們把dev分支的工作成果合并到master分支上:
$ git merge dev
Updating d17efd8..fec145a
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 fec145a).
# 刪除后茧泪,查看branch蜓氨,就只剩下master分支了:
$ git branch
* master
# 因?yàn)閯?chuàng)建、合并和刪除分支非扯游埃快穴吹,所以Git鼓勵(lì)你使用分支完成某個(gè)任務(wù),合并后再刪掉分支嗜侮,這和直接在master分支上工作效果是一樣的港令,但過(guò)程更安全。
小結(jié)
查看分支: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ù)我們的新分支開(kāi)發(fā):
$ git checkout -b 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 75a857c] 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.
# 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 400b400] & simple
1 file changed, 1 insertion(+), 1 deletion(-)
現(xiàn)在宜猜,master分支和feature1分支各自都分別有新的提交泼返,變成了這樣:
# 這種情況下,Git無(wú)法執(zhí)行“快速合并”姨拥,只能試圖把各自的修改合并起來(lái)绅喉,但這種合并就可能會(huì)有沖突,我們?cè)囋嚳矗?$ 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.
#
# Unmerged paths:
# (use "git add/rm <file>..." as appropriate 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 59bc1cb] conflict fixed
現(xiàn)在似芝,master分支和feature1分支變成了下圖所示:
# 用帶參數(shù)的git log也可以看到分支的合并情況:
$ git log --graph --pretty=oneline --abbrev-commit
* 59bc1cb conflict fixed
|\
| * 75a857c AND simple
* | 400b400 & simple
|/
* fec145a branch test
...
# 最后那婉,刪除feature1分支:
$ git branch -d feature1
Deleted branch feature1 (was 75a857c).
小結(jié)
當(dāng)Git無(wú)法自動(dòng)合并分支時(shí),就必須首先解決沖突党瓮。解決沖突后详炬,再提交,合并完成寞奸。
用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 checkout -b dev
Switched to a new branch 'dev'
# 修改readme.txt文件,并提交一個(gè)新的commit:
$ git add readme.txt
$ git commit -m "add merge"
[dev 6224937] add merge
1 file changed, 1 insertion(+)
# 現(xiàn)在饥伊,我們切換回master:
$ git checkout 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描述寫(xiě)進(jìn)去。
# 合并后茫因,我們用git log看看分支歷史:
$ git log --graph --pretty=oneline --abbrev-commit
* 7825a50 merge with no-ff
|\
| * 6224937 add merge
|/
* 59bc1cb conflict fixed
...
可以看到蚪拦,不使用Fast forward模式,merge后就像這樣:
分支策略
在實(shí)際開(kāi)發(fā)中冻押,我們應(yīng)該按照幾個(gè)基本原則進(jìn)行分支管理:
首先驰贷,master分支應(yīng)該是非常穩(wěn)定的,也就是僅用來(lái)發(fā)布新版本洛巢,平時(shí)不能在上面干活括袒;
那在哪干活呢?干活都在dev分支上稿茉,也就是說(shuō)锹锰,dev分支是不穩(wěn)定的芥炭,到某個(gè)時(shí)候,比如1.0版本發(fā)布時(shí)恃慧,再把dev分支合并到master上园蝠,在master分支發(fā)布1.0版本;
你和你的小伙伴們每個(gè)人都在dev分支上干活糕伐,每個(gè)人都有自己的分支砰琢,時(shí)不時(shí)地往dev分支上合并就可以了蘸嘶。
所以良瞧,團(tuán)隊(duì)合作的分支看起來(lái)就像這樣:
小結(jié)
Git分支十分強(qiáng)大,在團(tuán)隊(duì)開(kāi)發(fā)中應(yīng)該充分應(yīng)用训唱。
合并分支時(shí)褥蚯,加上--no-ff參數(shù)就可以用普通模式合并,合并后的歷史有分支况增,能看出來(lái)曾經(jīng)做過(guò)合并赞庶,而fast forward合并就看不出來(lái)曾經(jīng)做過(guò)合并。
Bug分支
當(dāng)你接到一個(gè)修復(fù)一個(gè)代號(hào)101的bug的任務(wù)時(shí)澳骤,很自然地歧强,你想創(chuàng)建一個(gè)分支issue-101來(lái)修復(fù)它,但是为肮,等等摊册,當(dāng)前正在dev上進(jìn)行的工作還沒(méi)有提交:
$ 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)行到一半颊艳,還沒(méi)法提交茅特,預(yù)計(jì)完成還需1天時(shí)間。但是棋枕,必須在兩個(gè)小時(shí)內(nèi)修復(fù)該bug白修,怎么辦?
幸好重斑,Git還提供了一個(gè)stash功能兵睛,可以把當(dāng)前工作現(xiàn)場(chǎng)“儲(chǔ)藏”起來(lái),等以后恢復(fù)現(xiàn)場(chǎng)后繼續(xù)工作:
$ git stash
Saved working directory and index state WIP on dev: 6224937 add merge
HEAD is now at 6224937 add merge
# 現(xiàn)在窥浪,用git status查看工作區(qū)卤恳,就是干凈的(除非有沒(méi)有被Git管理的文件),因此可以放心地創(chuàng)建分支來(lái)修復(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.
$ 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 cc17032] 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 2 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(-)
$ git branch -d issue-101
Deleted branch issue-101 (was cc17032).
# 太棒了傲须,原計(jì)劃兩個(gè)小時(shí)的bug修復(fù)只花了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: 6224937 add merge
# 工作現(xiàn)場(chǎng)還在已卸,Git把stash內(nèi)容存在某個(gè)地方了,但是需要恢復(fù)一下硼一,有兩個(gè)辦法:
一是用git stash apply恢復(fù)累澡,但是恢復(fù)后,stash內(nèi)容并不刪除般贼,你需要用git stash drop來(lái)刪除愧哟;
另一種方式是用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} (f624f8e5f082f2df2bed8a4e09c12fd2943bdd40)
# 再用git stash list查看哼蛆,就看不到任何stash內(nèi)容了:
$ git stash list
# 你可以多次stash蕊梧,恢復(fù)的時(shí)候,先用git stash list查看腮介,然后恢復(fù)指定的stash肥矢,用命令:
$ git stash apply stash@{0}
小結(jié)
修復(fù)bug時(shí),我們會(huì)通過(guò)創(chuàng)建新的bug分支進(jìn)行修復(fù)萤厅,然后合并橄抹,最后刪除;
當(dāng)手頭工作沒(méi)有完成時(shí)惕味,先把工作現(xiàn)場(chǎng)git stash一下楼誓,然后去修復(fù)bug,修復(fù)后名挥,再git stash pop疟羹,回到工作現(xiàn)場(chǎng)。
Feature分支
添加一個(gè)新功能時(shí)禀倔,你肯定不希望因?yàn)橐恍?shí)驗(yàn)性質(zhì)的代碼榄融,把主分支搞亂了,所以救湖,每添加一個(gè)新功能愧杯,最好新建一個(gè)feature分支,在上面開(kāi)發(fā)鞋既,完成后力九,合并耍铜,最后,刪除該feature分支跌前。
現(xiàn)在棕兼,你終于接到了一個(gè)新任務(wù):開(kāi)發(fā)代號(hào)為Vulcan的新功能,該功能計(jì)劃用于下一代星際飛船抵乓。
# 于是準(zhǔn)備開(kāi)發(fā):
$ git checkout -b feature-vulcan
Switched to a new branch 'feature-vulcan'
# 5分鐘后伴挚,開(kāi)發(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 756d4af] add feature vulcan
1 file changed, 2 insertions(+)
create mode 100644 vulcan.py
# 切回dev,準(zhǔn)備合并:
$ git checkout 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友情提醒沈自,feature-vulcan分支還沒(méi)有被合并酌儒,如果刪除,將丟失掉修改枯途,如果要強(qiáng)行刪除忌怎,需要使用命令git branch -D feature-vulcan。
# 現(xiàn)在我們強(qiáng)行刪除:
$ git branch -D feature-vulcan
Deleted branch feature-vulcan (was 756d4af).
小結(jié)
開(kāi)發(fā)一個(gè)新feature酪夷,最好新建一個(gè)分支榴啸;
如果要丟棄一個(gè)沒(méi)有被合并過(guò)的分支,可以通過(guò)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)起來(lái)了,并且坦报,遠(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的地址潜的。如果沒(méi)有推送權(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分支是開(kāi)發(fā)分支,團(tuán)隊(duì)所有成員都需要在上面工作谢谦,所以也需要與遠(yuǎn)程同步释牺;
bug分支只用于在本地修復(fù)bug,就沒(méi)必要推到遠(yuǎn)程了回挽,除非老板要看看你每周到底修復(fù)了幾個(gè)bug没咙;
feature分支是否推到遠(yuǎn)程,取決于你是否和你的小伙伴合作在上面開(kāi)發(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: 46, done.
remote: Compressing objects: 100% (26/26), done.
remote: Total 46 (delta 16), reused 45 (delta 15)
Receiving objects: 100% (46/46), 15.69 KiB | 6 KiB/s, done.
Resolving deltas: 100% (16/16), done.
# 當(dāng)你的小伙伴從遠(yuǎn)程庫(kù)clone時(shí),默認(rèn)情況下焰情,你的小伙伴只能看到本地的master分支陌凳。不信可以用git branch命令看看:
$ git branch
* master
# 現(xiàn)在,你的小伙伴要在dev分支上開(kāi)發(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 commit -m "add /usr/bin/env"
[dev 291bea8] add /usr/bin/env
1 file changed, 1 insertion(+)
$ git push origin dev
Counting objects: 5, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (2/2), done.
Writing objects: 100% (3/3), 349 bytes, done.
Total 3 (delta 0), reused 0 (delta 0)
To git@github.com:michaelliao/learngit.git
fc38031..291bea8 dev -> dev
# 你的小伙伴已經(jīng)向origin/dev分支推送了他的提交保檐,而碰巧你也對(duì)同樣的文件作了修改,并試圖推送:
$ git add hello.py
$ git commit -m "add coding: utf-8"
[dev bd6ae48] add coding: utf-8
1 file changed, 1 insertion(+)
$ git push origin dev
To git@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. Merge the remote changes (e.g. 'git pull')
hint: 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抓下來(lái)扔亥,然后,在本地合并谈为,解決沖突旅挤,再推送:
$ git pull
remote: Counting objects: 5, done.
remote: Compressing objects: 100% (2/2), done.
remote: Total 3 (delta 0), reused 3 (delta 0)
Unpacking objects: 100% (3/3), done.
From github.com:michaelliao/learngit
fc38031..291bea8 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 dev origin/<branch>
# git pull也失敗了,原因是沒(méi)有指定本地dev分支與遠(yuǎn)程origin/dev分支的鏈接伞鲫,根據(jù)提示粘茄,設(shè)置dev和origin/dev的鏈接:
$ git branch --set-upstream dev origin/dev
Branch dev set up to track remote branch dev from origin.
# 再pull:
$ git pull
Auto-merging hello.py
CONFLICT (content): Merge conflict in hello.py
Automatic merge failed; fix conflicts and then commit the result.
# 這回git pull成功,但是合并有沖突秕脓,需要手動(dòng)解決柒瓣,解決的方法和分支管理中的解決沖突完全一樣。解決后撒会,提交嘹朗,再push:
$ git commit -m "merge & fix hello.py"
[dev adca45d] merge & fix hello.py
$ git push origin dev
Counting objects: 10, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (5/5), done.
Writing objects: 100% (6/6), 747 bytes, done.
Total 6 (delta 0), reused 0 (delta 0)
To git@github.com:michaelliao/learngit.git
291bea8..adca45d dev -> dev
因此师妙,多人協(xié)作的工作模式通常是這樣:
首先诵肛,可以試圖用git push origin branch-name推送自己的修改;
如果推送失敗默穴,則因?yàn)檫h(yuǎn)程分支比你的本地更新怔檩,需要先用git pull試圖合并;
如果合并有沖突蓄诽,則解決沖突薛训,并在本地提交;
沒(méi)有沖突或者解決掉沖突后仑氛,再用git push origin branch-name推送就能成功乙埃!
如果git pull提示“no tracking information”,則說(shuō)明本地分支和遠(yuǎn)程分支的鏈接關(guān)系沒(méi)有創(chuàng)建锯岖,用命令git branch --set-upstream branch-name origin/branch-name介袜。
這就是多人協(xié)作的工作模式,一旦熟悉了出吹,就非常簡(jiǎn)單遇伞。
小結(jié)
查看遠(yuǎn)程庫(kù)信息,使用git remote -v捶牢;
本地新建的分支如果不推送到遠(yuǎn)程鸠珠,對(duì)其他人就是不可見(jiàn)的巍耗;
從本地推送分支,使用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城看,如果有沖突女气,要先處理沖突。
標(biāo)簽管理
發(fā)布一個(gè)版本時(shí)测柠,我們通常先在版本庫(kù)中打一個(gè)標(biāo)簽(tag)炼鞠,這樣,就唯一確定了打標(biāo)簽時(shí)刻的版本轰胁。將來(lái)無(wú)論什么時(shí)候谒主,取某個(gè)標(biāo)簽的版本,就是把那個(gè)打標(biāo)簽的時(shí)刻的歷史版本取出來(lái)赃阀。所以霎肯,標(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)簽沒(méi)有打,怎么辦鸵贬?
# 方法是找到歷史提交的commit id俗他,然后打上就可以了:
$ git log --pretty=oneline --abbrev-commit
6a5819e merged bug fix 101
cc17032 fix bug 101
7825a50 merge with no-ff
6224937 add merge
59bc1cb conflict fixed
400b400 & simple
75a857c AND simple
fec145a branch test
d17efd8 remove test.txt
...
# 比方說(shuō)要對(duì)add merge這次提交打標(biāo)簽,它對(duì)應(yīng)的commit id是6224937阔逼,敲入命令:
$ git tag v0.9 6224937
# 再用命令git tag查看標(biāo)簽:
$ git tag
v0.9
v1.0
# 注意兆衅,標(biāo)簽不是按時(shí)間順序列出,而是按字母排序的嗜浮∠勰叮可以用git show <tagname>查看標(biāo)簽信息:
$ git show v0.9
commit 622493706ab447b6bb37e4e2a2f276a20fed2ab4
Author: Michael Liao <askxuefeng@gmail.com>
Date: Thu Aug 22 11:22:08 2013 +0800
add merge
...
# 可以看到,v0.9確實(shí)打在add merge這次提交上危融。
# 還可以創(chuàng)建帶有說(shuō)明的標(biāo)簽畏铆,用-a指定標(biāo)簽名,-m指定說(shuō)明文字:
$ git tag -a v0.1 -m "version 0.1 released" 3628164
# 用命令git show <tagname>可以看到說(shuō)明文字:
$ git show v0.1
tag v0.1
Tagger: Michael Liao <askxuefeng@gmail.com>
Date: Mon Aug 26 07:28:11 2013 +0800
version 0.1 released
commit 3628164fb26d48395383f8f31179f24e0882e1e0
Author: Michael Liao <askxuefeng@gmail.com>
Date: Tue Aug 20 15:11:49 2013 +0800
append GPL
小結(jié)
命令git tag <name>用于新建一個(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 e078af9)
因?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 git@github.com:michaelliao/learngit.git
* [new tag] v1.0 -> v1.0
或者捅僵,一次性推送全部尚未推送到遠(yuǎn)程的本地標(biāo)簽:
$ git push origin --tags
Counting objects: 1, done.
Writing objects: 100% (1/1), 554 bytes, done.
Total 1 (delta 0), reused 0 (delta 0)
To git@github.com:michaelliao/learngit.git
* [new tag] v0.2 -> v0.2
* [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 6224937)
# 然后庙楚,從遠(yuǎn)程刪除上荡。刪除命令也是push,但是格式如下:
$ git push origin :refs/tags/v0.9
To git@github.com:michaelliao/learngit.git
- [deleted] v0.9
小結(jié)
命令git push origin <tagname>可以推送一個(gè)本地標(biāo)簽;
命令git push origin --tags可以推送全部未推送過(guò)的本地標(biāo)簽酪捡;
命令git tag -d <tagname>可以刪除一個(gè)本地標(biāo)簽叁征;
命令git push origin :refs/tags/<tagname>可以刪除一個(gè)遠(yuǎn)程標(biāo)簽。
自定義Git
在安裝Git一節(jié)中逛薇,我們已經(jīng)配置了user.name和user.email捺疼,實(shí)際上,Git還有很多可配置項(xiàng)永罚。
比如啤呼,讓Git顯示顏色,會(huì)讓命令輸出看起來(lái)更醒目:
$ git config --global color.ui true
這樣呢袱,Git會(huì)適當(dāng)?shù)仫@示不同的顏色官扣,比如git status命令:
文件名就會(huì)標(biāo)上顏色。