git分支
什麼是git分支?
什麼是git分支?首先讓我們回顧一下提交對象,一個提交對象(commit objects)包括:
- 一系列文件在某個時間的快照。
- 一系列指向父提交對象的索引。
- 一個SHA-1名字,這個名字40個字元長,是獨一無二的。
- 作者的姓名和郵箱,以及提交時對提交的描述。
事實上,「一系列文件在某個時間的快照」並不是直接存在於提交對象。在git中,blob對象保存著文件的快照,樹對象保存著目錄結構和blob對象的索引,而提交對象保存指向樹對象的指針。下圖是一個這三者關係的示意圖:
那麼git中的分支是什麼呢?
git中的分支就像是你的文件的一份副本,你可以在需要的時候拷貝一份出來,這樣你就得到了一個「分支」,你可以在上面修改,修改完成之後再合併回去。在一些版本控制軟體中實際情況確實是這樣,然而在git中並非如此。
在git中,對分支的操作大部分只是在修改指向提交對象的heads。我們知道,heads是一個指向提交對象的指針,分支操作中的大部分操作只需要修改heads的指向,即向heads文件中寫入41個字元即可(40個SHA-1字元串和1個換行符)。與其他一些版本控制軟體採用的複製文件策略相比較,git分支操作與文件大小無關,操作迅速快捷。
創建分支
現在先來看看我們在哪個分支,使用git branch
命令查看當前分支,命令選項-v
顯示分支指向提交對象的校驗和及其描述:
$ git branch* master$ git branch -v* master 57b75e6 Add GitHub description.
從結果中看到,現在只有一個分支,叫做master
。*
表示當前所在的分支,即HEAD的指向。
用圖簡略表示如下:
現在創建一條dev
分支,使用git branch <branchname>
命令:
$ git branch dev$ git branch dev* master
現在有了兩條分支:master
和dev
,目前我們在master
分支。圖示如下:
可見,事實上只是創建了一個指向圖中提交對象C3
的指針,使用git log --decorate
可以查看heads的指向:
$ git log --oneline --decorate -357b75e6 (HEAD -> master, origin/master, dev) Add GitHub description.beac1f4 make README.md more friendly.14bd627 add two wrong line to README.md
master
、遠程origin
的master
和dev
指向57b75e6
提交對象,HEAD
指向master
。
切換分支
現在切換到dev
分支,使用git checkout <branchname>
命令,在切換前請確保你的工作目錄是乾淨的:
$ git checkout devSwitched to branch dev
這樣就切換到了dev
分支,查看一下:
$ git branch* dev master$ git log --oneline --decorate -357b75e6 (HEAD -> dev, origin/master, master) Add GitHub description.beac1f4 make README.md more friendly.14bd627 add two wrong line to README.md
可以看到我們確實在dev
分支,HEAD
確實指向了dev
分支。在切換分支時,git會將分支所指向的提交對象的文件快照檢出到工作目錄,並且更改HEAD
的指向。目前分支情況圖示如下:
git checkout -b <branchname>
可以創建<branchname>
分支並且切換到它,相當於執行下面兩條命令:
$ git branch <branchname>$ git checkout <branchname>
「快進」合併
現在在dev
分支,我們創建一個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
查看一下各個分支所指:
$ git log --oneline --decorate -3fd2e1cb (HEAD -> dev) add dev.md57b75e6 (origin/master, master) Add GitHub description.beac1f4 make README.md more friendly.
dev
前進了一個提交對象,HEAD
指向dev
,其他分支並沒有更改,圖式如下:
現在切換到master
,使用$ git checkout master
命令,HEAD
會指向master
,工作目錄中的文件將會被替換:
合併分支使用git merge <branchname>
命令,這個命令將<branchname>
分支合併到當前分支,現在我們在master
分支,執行下面的命令將dev
分支合併到master
分支:
$ git merge devUpdating 57b75e6..fd2e1cbFast-forward dev.md | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 dev.md
git告訴我們此次合併的方式是Fast-forward
(快進),此時分支情況如下:
現在dev
分支已經被合併到master
分支了。從上圖可以看出git僅僅是簡單的更新了master
和HEAD
的指向,這是由於合併前master
指向dev
的直接上游,這種合併方式叫做快進(Fast-forward)。
可以使用--no-ff
選項避免使用「快進」合併,這樣會形成一個新的合併提交,類似下節講到的分之合併:
$ git merge --no-ff dev
現在dev
分支已經充分得發揮了自己的作用,讓我們刪除它:
$ git branch -d devDeleted branch dev (was fd2e1cb).
如果一個分支沒有完全合併到當前分支,那麼git會阻止你刪除它,如果確實要刪除它,使用-D
命令選項:
$ git branch -D <branchname>
如果想要知道那些分支被合併了或者沒有合併,使用下面的命令:
$ git branch --merged # 查看已經被合併的分支$ git branch --no-merged # 查看還沒有被合併的分支
目前分支情況如下:
本文所講的例子整體過程圖示如下:
分支合併
現在創建一個testing
分支並且切換到該分支:
$ git checkout -b testingSwitched 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$ lsdev.md README.md$ git commit -a -m "add description of dev.md"[master 1b63c87] add description of dev.md 1 file changed, 1 insertion(+)
現在兩條分支在分叉後都有新的提交:testing
有兩個新的提交,master
有一個新的提交。怎樣在命令行查看呢?
$ 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
之後進行了兩次提交,master
進行了一次提交,目前我們在master
分支。圖示如下:
現在將testing
分支合併到master
分支:
$ git merge testingMerge 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.# 省略
現在git幫我們合併了master
和testing
,並且生成了一個新的提交(你可能需要填寫提交描述),這個新提交的SHA-1校驗和前七位是8425ef2。
git能夠幫我們自動合併,而不會產生衝突的原因是我們在不同的分支中修改了不同的文件,此時git會參考兩個分支所指的快照(testing
的40a00ae
和master
的1b63c87
)和兩個分支的共同祖先(fd2e1cb
),自動合併。參考的三個快照分別相當於下圖的C6、C7和C4.
新生成的提交叫做合併提交,相當於下圖的C8.這個新提交擁有兩個父提交。
好了,現在刪掉testing
分支吧:
$ git branch -d testing Deleted branch testing (was 40a00ae).
本文所講的分支合併的整體過程圖示如下:
衝突解決
如果在不同分支中同一個文件的同一個地方做了修改,git就無法乾淨利落地合併它們。
創建一個新的分支iss1
,在iss1
分支中將README.md
修改如下並且提交:
$ git checkout -b iss1Switched 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 masterSwitched 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# 省略
現在將iss1
分支合併到master
分支:
$ git merge iss1Auto-merging README.mdCONFLICT (content): Merge conflict in README.mdAutomatic merge failed; fix conflicts and then commit the result.
git告訴我們說自動合併失敗,原因是在README.md
文件中有衝突,並且提醒我們解決衝突後提交結果。
也就是說,git在遇到衝突時,並不會創建一個合併提交,而是暫停下來,等用戶解決衝突之後,由用戶提交。
含有衝突的文件被標記為「未合併」(unmerged)狀態,隨時可以使用git status
來查看:
$ git statusOn branch masterYou have unmerged paths. (fix conflicts and run "git commit")Unmerged paths: (use "git add <file>..." to mark resolution) both modified: README.mdno changes added to commit (use "git add" and/or "git commit -a")
現在讓我們解決README.md
中的衝突,首先來看一看git剛剛所做的工作:
$ cat README.md # Hi, Git!This is my first git project and i use it to learn git.<<<<<<< HEADI LOVE GIT.=======Git is a free and open source distributed version control system.>>>>>>> iss1
其中的一部分是git為我們標記的衝突的部分:
<<<<<<< HEADI LOVE GIT.=======Git is a free and open source distributed version control system.>>>>>>> iss1
在=======
的上半部分的是HEAD
分支中的文件內容,在其下半部分的是iss1
分支中文件的內容。
現在讓我們將這部分修改如下:
I LOVE GIT.
這表示將丟棄iss1
中的修改,當然你可以根據自己的喜好更改,你可以改成任意你需要的內容。
現在將文件添加到暫存區,並且查看狀態:
$ git add README.md $ git statusOn branch masterAll conflicts fixed but you are still merging. (use "git commit" to conclude merge)nothing to commit, working directory clean
可見,一旦衝突文件被添加到暫存區,它的「未合併」狀態就會被解除,即表示衝突已經解決。
現在提交即可:
$ 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 iss1Deleted branch iss1 (was d6801d6).
儲藏與清理
git在切換分支時必須保證當前工作目錄是乾淨的,如果現在做了一點更改,不至於提交一次新的更新,但是卻必須更換到另一條分支上,怎麼辦呢?
git為我們提供了stash
(儲藏)工具。
現在在master
分支上對README.md
作一些更改,並且將它儲藏起來:
$ git status -s M README.md$ git stash Saved working directory and index state WIP on master: 11f0f7a merge iss1HEAD is now at 11f0f7a merge iss1$ git status -s$
在運行git stash
之後工作目錄就變乾淨了,現在就可以切換到其他分支工作啦。
在其他分支工作完之後,又回到master
,怎樣繼續工作呢?
使用git stash list
命令可以查看儲藏的列表:
$ git stash liststash@{0}: WIP on master: 11f0f7a merge iss1
使用git stash apply <stashname>
即可應用,如果<stashname>
為空,則會應用最新的儲藏:
$ git stash liststash@{0}: WIP on master: 11f0f7a merge iss1$ git stash applyOn branch masterChanges 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.mdno changes added to commit (use "git add" and/or "git commit -a")$ git status -s M README.md
我們的更改又回來了,使用git stash drop <stashname>
刪除相應的儲藏,如果<stashname>
為空,則會刪除最新的儲藏:
$ git stash dropDropped refs/stash@{0} (939ab1d7c4f88fe2dd9b3420d0cf919a668eff23)$ git stash list$
可以使用git stash pop
直接應用最新的儲藏,同時刪除該儲藏。
在git中,可以進行多次儲藏,也可以在不同的分支應用儲藏。
推薦閱讀:
※聊聊 Git 「改變歷史」
※關於 Git SSH 使用的項目實踐
※如何優雅地使用 Git?
※Git~
TAG:Git |