標籤:

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

現在有了兩條分支:masterdev,目前我們在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、遠程originmasterdev指向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僅僅是簡單的更新了masterHEAD的指向,這是由於合併前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幫我們合併了mastertesting,並且生成了一個新的提交(你可能需要填寫提交描述),這個新提交的SHA-1校驗和前七位是8425ef2。

git能夠幫我們自動合併,而不會產生衝突的原因是我們在不同的分支中修改了不同的文件,此時git會參考兩個分支所指的快照(testing40a00aemaster1b63c87)和兩個分支的共同祖先(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 |