什么是git分支?
什么是git分支织狐?首先讓我們回顧一下提交對(duì)象斑举,一個(gè)提交對(duì)象(commit objects)包括:
- 一系列文件在某個(gè)時(shí)間的快照。
- 一系列指向父提交對(duì)象的索引助泽。
- 一個(gè)SHA-1名字,這個(gè)名字40個(gè)字符長(zhǎng)嚎京,是獨(dú)一無(wú)二的嗡贺。
- 作者的姓名和郵箱,以及提交時(shí)對(duì)提交的描述鞍帝。
事實(shí)上诫睬,“一系列文件在某個(gè)時(shí)間的快照”并不是直接存在于提交對(duì)象。在git中帕涌,blob對(duì)象保存著文件的快照摄凡,樹(shù)對(duì)象保存著目錄結(jié)構(gòu)和blob對(duì)象的索引,而提交對(duì)象保存指向樹(shù)對(duì)象的指針蚓曼。下圖是一個(gè)這三者關(guān)系的示意圖:
那么git中的分支是什么呢亲澡?
git中的分支就像是你的文件的一份副本,你可以在需要的時(shí)候拷貝一份出來(lái)纫版,這樣你就得到了一個(gè)“分支”床绪,你可以在上面修改,修改完成之后再合并回去其弊。在一些版本控制軟件中實(shí)際情況確實(shí)是這樣会涎,然而在git中并非如此。
在git中瑞凑,對(duì)分支的操作大部分只是在修改指向提交對(duì)象的heads。我們知道概页,heads是一個(gè)指向提交對(duì)象的指針籽御,分支操作中的大部分操作只需要修改heads的指向,即向heads文件中寫(xiě)入41個(gè)字符即可(40個(gè)SHA-1字符串和1個(gè)換行符)。與其他一些版本控制軟件采用的復(fù)制文件策略相比較技掏,git分支操作與文件大小無(wú)關(guān)铃将,操作迅速快捷。
創(chuàng)建分支
現(xiàn)在先來(lái)看看我們?cè)谀膫€(gè)分支哑梳,使用git branch
命令查看當(dāng)前分支劲阎,命令選項(xiàng)-v
顯示分支指向提交對(duì)象的校驗(yàn)和及其描述:
$ git branch
* master
$ git branch -v
* master 57b75e6 Add GitHub description.
從結(jié)果中看到,現(xiàn)在只有一個(gè)分支鸠真,叫做master
悯仙。*
表示當(dāng)前所在的分支,即HEAD的指向吠卷。
用圖簡(jiǎn)略表示如下:
現(xiàn)在創(chuàng)建一條dev
分支锡垄,使用git branch <branchname>
命令:
$ git branch dev
$ git branch
dev
* master
現(xiàn)在有了兩條分支:master
和dev
,目前我們?cè)?code>master分支祭隔。圖示如下:
可見(jiàn)货岭,事實(shí)上只是創(chuàng)建了一個(gè)指向圖中提交對(duì)象C3
的指針,使用git log --decorate
可以查看heads的指向:
$ git log --oneline --decorate -3
57b75e6 (HEAD -> master, origin/master, dev) Add GitHub description.
beac1f4 make README.md more friendly.
14bd627 add two wrong line to README.md
master
疾渴、遠(yuǎn)程origin
的master
和dev
指向57b75e6
提交對(duì)象千贯,HEAD
指向master
。
切換分支
現(xiàn)在切換到dev
分支搞坝,使用git checkout <branchname>
命令搔谴,在切換前請(qǐng)確保你的工作目錄是干凈的:
$ git checkout dev
Switched to branch 'dev'
這樣就切換到了dev
分支,查看一下:
$ git branch
* dev
master
$ git log --oneline --decorate -3
57b75e6 (HEAD -> dev, origin/master, master) Add GitHub description.
beac1f4 make README.md more friendly.
14bd627 add two wrong line to README.md
可以看到我們確實(shí)在dev
分支瞄沙,HEAD
確實(shí)指向了dev
分支己沛。在切換分支時(shí),git會(huì)將分支所指向的提交對(duì)象的文件快照檢出到工作目錄距境,并且更改HEAD
的指向申尼。目前分支情況圖示如下:
git checkout -b <branchname>
可以創(chuàng)建<branchname>
分支并且切換到它,相當(dāng)于執(zhí)行下面兩條命令:
$ git branch <branchname>
$ git checkout <branchname>
“快進(jìn)”合并
現(xiàn)在在dev
分支垫桂,我們創(chuàng)建一個(gè)dev.md
文件并且提交:
$ touch dev.md
$ git add dev.md
$ git commit -m "add dev.md"
[dev fd2e1cb] add dev.md
1 file changed, 0 insertions(+), 0 deletions(-)
create mode 100644 dev.md
查看一下各個(gè)分支所指:
$ git log --oneline --decorate -3
fd2e1cb (HEAD -> dev) add dev.md
57b75e6 (origin/master, master) Add GitHub description.
beac1f4 make README.md more friendly.
dev
前進(jìn)了一個(gè)提交對(duì)象师幕,HEAD
指向dev
,其他分支并沒(méi)有更改诬滩,圖式如下:
現(xiàn)在切換到master
霹粥,使用$ git checkout master
命令,HEAD
會(huì)指向master
疼鸟,工作目錄中的文件將會(huì)被替換:
合并分支使用git merge <branchname>
命令后控,這個(gè)命令將<branchname>
分支合并到當(dāng)前分支,現(xiàn)在我們?cè)?code>master分支空镜,執(zhí)行下面的命令將dev
分支合并到master
分支:
$ git merge dev
Updating 57b75e6..fd2e1cb
Fast-forward
dev.md | 0
1 file changed, 0 insertions(+), 0 deletions(-)
create mode 100644 dev.md
git告訴我們此次合并的方式是Fast-forward
(快進(jìn))浩淘,此時(shí)分支情況如下:
現(xiàn)在dev
分支已經(jīng)被合并到master
分支了捌朴。從上圖可以看出git僅僅是簡(jiǎn)單的更新了master
和HEAD
的指向,這是由于合并前master
指向dev
的直接上游张抄,這種合并方式叫做快進(jìn)(Fast-forward)砂蔽。
可以使用--no-ff
選項(xiàng)避免使用“快進(jìn)”合并,這樣會(huì)形成一個(gè)新的合并提交署惯,類似下節(jié)講到的分之合并:
$ git merge --no-ff dev
現(xiàn)在dev
分支已經(jīng)充分得發(fā)揮了自己的作用左驾,讓我們刪除它:
$ git branch -d dev
Deleted branch dev (was fd2e1cb).
如果一個(gè)分支沒(méi)有完全合并到當(dāng)前分支,那么git會(huì)阻止你刪除它极谊,如果確實(shí)要?jiǎng)h除它诡右,使用-D
命令選項(xiàng):
$ git branch -D <branchname>
如果想要知道那些分支被合并了或者沒(méi)有合并,使用下面的命令:
$ git branch --merged # 查看已經(jīng)被合并的分支
$ git branch --no-merged # 查看還沒(méi)有被合并的分支
目前分支情況如下:
本文所講的例子整體過(guò)程圖示如下:
分支合并
現(xiàn)在創(chuàng)建一個(gè)testing
分支并且切換到該分支:
$ git checkout -b testing
Switched to a new branch 'testing'
添加testing.md
并提交怀酷,修改tesing.md
并提交:
$ touch testing.md
$ git add testing.md
$ git commit -m "add testing.md"
[testing dd4555e] add testing.md
1 file changed, 0 insertions(+), 0 deletions(-)
create mode 100644 testing.md
$ echo "A file added in testing branch." > testing.md
$ git commit -a -m "add description of testing.md"
[testing 40a00ae] add description of testing.md
1 file changed, 1 insertion(+)
回到master
分支并且修改dev.md
:
$ git checkout master
Switched to branch 'master'
$ echo "A dev file." > dev.md
$ ls
dev.md README.md
$ git commit -a -m "add description of dev.md"
[master 1b63c87] add description of dev.md
1 file changed, 1 insertion(+)
現(xiàn)在兩條分支在分叉后都有新的提交:testing
有兩個(gè)新的提交论悴,master
有一個(gè)新的提交猪杭。怎樣在命令行查看呢乏冀?
$ git log --oneline --decorate --graph --all
* 1b63c87 (HEAD -> master) add description of dev.md
| * 40a00ae (testing) add description of testing.md
| * dd4555e add testing.md
|/
* fd2e1cb add dev.md
* 57b75e6 (origin/master) Add GitHub description.
# 省略
可以看到劫哼,在fd2e1cb
分支分叉,testing
之后進(jìn)行了兩次提交样眠,master
進(jìn)行了一次提交友瘤,目前我們?cè)?code>master分支。圖示如下:
現(xiàn)在將testing
分支合并到master
分支:
$ git merge testing
Merge made by the 'recursive' strategy.
testing.md | 1 +
1 file changed, 1 insertion(+)
create mode 100644 testing.md
$ git log --oneline --decorate --graph --all
* 8425ef2 (HEAD -> master) Merge branch 'testing'
|\
| * 40a00ae (testing) add description of testing.md
| * dd4555e add testing.md
* | 1b63c87 add description of dev.md
|/
* fd2e1cb add dev.md
* 57b75e6 (origin/master) Add GitHub description.
# 省略
現(xiàn)在git幫我們合并了master
和testing
檐束,并且生成了一個(gè)新的提交(你可能需要填寫(xiě)提交描述)辫秧,這個(gè)新提交的SHA-1校驗(yàn)和前七位是8425ef2。
git能夠幫我們自動(dòng)合并被丧,而不會(huì)產(chǎn)生沖突的原因是我們?cè)诓煌姆种е行薷牧瞬煌奈募讼罚藭r(shí)git會(huì)參考兩個(gè)分支所指的快照(testing
的40a00ae
和master
的1b63c87
)和兩個(gè)分支的共同祖先(fd2e1cb
),自動(dòng)合并甥桂。參考的三個(gè)快照分別相當(dāng)于下圖的C6柿究、C7和C4.
新生成的提交叫做合并提交,相當(dāng)于下圖的C8.這個(gè)新提交擁有兩個(gè)父提交黄选。
好了蝇摸,現(xiàn)在刪掉testing
分支吧:
$ git branch -d testing
Deleted branch testing (was 40a00ae).
本文所講的分支合并的整體過(guò)程圖示如下:
沖突解決
如果在不同分支中同一個(gè)文件的同一個(gè)地方做了修改,git就無(wú)法干凈利落地合并它們办陷。
創(chuàng)建一個(gè)新的分支iss1
貌夕,在iss1
分支中將README.md
修改如下并且提交:
$ git checkout -b iss1
Switched to a new branch 'iss1'
$ vim README.md
$ cat README.md
# Hi, Git!
This is my first git project and i use it to learn git.
Git is a free and open source distributed version control system.
$ git commit -a -m "change README.md in iss1"
[iss1 d6801d6] change README.md in iss1
1 file changed, 6 deletions(-)
切換到master
分支,將README.md
修改如下并且提交:
$ git checkout master
Switched to branch 'master'
$ vim README.md
$ cat README.md
# Hi, Git!
This is my first git project and i use it to learn git.
I LOVE GIT.
$ git commit -a -m "change README.md in master."
[master 63172f9] change README.md in master.
1 file changed, 1 insertion(+), 7 deletions(-)
$ git log --oneline --decorate --graph --all
* 63172f9 (HEAD -> master) change README.md in master.
| * d6801d6 (iss1) change README.md in iss1
|/
* 8425ef2 Merge branch 'testing'
# 省略
現(xiàn)在將iss1
分支合并到master
分支:
$ git merge iss1
Auto-merging README.md
CONFLICT (content): Merge conflict in README.md
Automatic merge failed; fix conflicts and then commit the result.
git告訴我們說(shuō)自動(dòng)合并失敗民镜,原因是在README.md
文件中有沖突啡专,并且提醒我們解決沖突后提交結(jié)果。
也就是說(shuō)制圈,git在遇到?jīng)_突時(shí)植旧,并不會(huì)創(chuàng)建一個(gè)合并提交辱揭,而是暫停下來(lái),等用戶解決沖突之后病附,由用戶提交。
含有沖突的文件被標(biāo)記為“未合并”(unmerged)狀態(tài)亥鬓,隨時(shí)可以使用git status
來(lái)查看:
$ git status
On branch master
You have unmerged paths.
(fix conflicts and run "git commit")
Unmerged paths:
(use "git add <file>..." to mark resolution)
both modified: README.md
no changes added to commit (use "git add" and/or "git commit -a")
現(xiàn)在讓我們解決README.md
中的沖突完沪,首先來(lái)看一看git剛剛所做的工作:
$ cat README.md
# Hi, Git!
This is my first git project and i use it to learn git.
<<<<<<< HEAD
I LOVE GIT.
=======
Git is a free and open source distributed version control system.
>>>>>>> iss1
其中的一部分是git為我們標(biāo)記的沖突的部分:
<<<<<<< HEAD
I LOVE GIT.
=======
Git is a free and open source distributed version control system.
>>>>>>> iss1
在=======
的上半部分的是HEAD
分支中的文件內(nèi)容,在其下半部分的是iss1
分支中文件的內(nèi)容嵌戈。
現(xiàn)在讓我們將這部分修改如下:
I LOVE GIT.
這表示將丟棄iss1
中的修改覆积,當(dāng)然你可以根據(jù)自己的喜好更改,你可以改成任意你需要的內(nèi)容熟呛。
現(xiàn)在將文件添加到暫存區(qū)宽档,并且查看狀態(tài):
$ git add README.md
$ git status
On branch master
All conflicts fixed but you are still merging.
(use "git commit" to conclude merge)
nothing to commit, working directory clean
可見(jiàn),一旦沖突文件被添加到暫存區(qū)庵朝,它的“未合并”狀態(tài)就會(huì)被解除吗冤,即表示沖突已經(jīng)解決。
現(xiàn)在提交即可:
$ git commit -m "merge iss1"
[master 11f0f7a] merge iss1
$ git log --oneline --decorate --graph --all
* 11f0f7a (HEAD -> master) merge iss1
|\
| * d6801d6 (iss1) change README.md in iss1
* | 63172f9 change README.md in master.
|/
* 8425ef2 Merge branch 'testing'
# 省略
最后刪除iss1
分支:
$ git branch -d iss1
Deleted branch iss1 (was d6801d6).
儲(chǔ)藏與清理
git在切換分支時(shí)必須保證當(dāng)前工作目錄是干凈的九府,如果現(xiàn)在做了一點(diǎn)更改椎瘟,不至于提交一次新的更新,但是卻必須更換到另一條分支上侄旬,怎么辦呢肺蔚?
git為我們提供了stash
(儲(chǔ)藏)工具。
現(xiàn)在在master
分支上對(duì)README.md
作一些更改儡羔,并且將它儲(chǔ)藏起來(lái):
$ git status -s
M README.md
$ git stash
Saved working directory and index state WIP on master: 11f0f7a merge iss1
HEAD is now at 11f0f7a merge iss1
$ git status -s
$
在運(yùn)行git stash
之后工作目錄就變干凈了宣羊,現(xiàn)在就可以切換到其他分支工作啦。
在其他分支工作完之后汰蜘,又回到master
仇冯,怎樣繼續(xù)工作呢?
使用git stash list
命令可以查看儲(chǔ)藏的列表:
$ git stash list
stash@{0}: WIP on master: 11f0f7a merge iss1
使用git stash apply <stashname>
即可應(yīng)用鉴扫,如果<stashname>
為空赞枕,則會(huì)應(yīng)用最新的儲(chǔ)藏:
$ git stash apply
$ git stash list
stash@{0}: WIP on master: 11f0f7a merge iss1
$ git stash apply
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.md
no changes added to commit (use "git add" and/or "git commit -a")
$ git status -s
M README.md
我們的更改又回來(lái)了,使用git stash drop <stashname>
刪除相應(yīng)的儲(chǔ)藏坪创,如果<stashname>
為空炕婶,則會(huì)刪除最新的儲(chǔ)藏:
$ git stash drop
Dropped refs/stash@{0} (939ab1d7c4f88fe2dd9b3420d0cf919a668eff23)
$ git stash list
$
可以使用git stash pop
直接應(yīng)用最新的儲(chǔ)藏,同時(shí)刪除該儲(chǔ)藏莱预。
在git中柠掂,可以進(jìn)行多次儲(chǔ)藏,也可以在不同的分支應(yīng)用儲(chǔ)藏依沮。