分佈式版本控制系統 Git | 七

分佈式版本控制系統 Git | 七


Git 分支


分支簡介

說起 Git 分支的內容,咱們要先理解 Git 保存的並非文件的變化或者差別,而是一系列的不一樣時刻的文件快照。git

Git 在進行提交操做的時候,會保存一個提交對象(commit object)。這個提交對象包含了一個指向暫存內容快照的指針,還包括提交者的姓名,郵箱,提交的信息以及指向它的父對象的指針。固然,首次提交產生的提交對象沒有父對象。其餘普通提交操做產生的對象都有一個父對象,當多個分支合併產生的提交對象則有多個父對象。github

分支建立與合併

分佈式版本控制系統 Git | 三vim

上面這篇文章中,咱們提到每次提交,Git 會將它們串成一條時間線,這時間線就是一個分支。未新建分支以前,這個就是主分支,便是 master 分支。segmentfault

Git 如何建立新分支?使用 git branch <branch_name>,這裏它會建立一個能夠移動的新指針。好比建立一個 dev 分支:微信

$ git branch dev

這時,便會在提交對象上建立一個指針,以下圖:分佈式

圖 1

如今建立了一個分支,可是 Git 如何分辨兩個分支,如何知道目前在哪一個分支?這裏要說起一個特殊指針 HEAD學習

HEAD 嚴格來講不是指向提交,默認狀況下,而是指向 master,而 master 或者新建立的 dev 纔是指向提交,HEAD 指向的就是當前分支。剛纔 git branch dev 僅僅是建立了一個新分支,並不會自動切換到新分支上去。fetch

圖 2

HEAD 指向的是當前所在的分支,可使用命令 git log 查看各個分支當前所指的對象。配合參數 --decoratespa

$ git log --oneline --decorate
861b17e (HEAD -> master, dev) Initial commit of project

此時,masterdev 分支均指向校驗和爲 861b17e 開頭的提交對象。.net

切換分支

切換已經存在的分支,可使用 git checkout <branch> 或者 git switch <branch>,這裏使用的是 git switch 更容易理解:

$ git switch dev
Switched to branch 'dev'

這樣 HEAD 就指向了 dev 分支。

圖 3

上面是將建立跟切換操做分開。Git 還提供一個命令可以建立的同時切換到分支:

$ git switch -c iss007

或者

$ git checkout -b iss007

使用帶 -c 參數的 git switch 命令,或者使用帶 -b 參數的 checkout 命令。

其實上面的命令能夠分解爲:

$ git branch iss007
$ git switch iss007

$ git branch iss007
$ git checkout iss007

假設如今在 iss007 分支上工做,並作了提交。在這個過程當中,iss007 分支會不斷向前推動。

$ vim ISSUE
$ git commit -a -m "Fix issue 007 [issue 007]"

圖 4

假設,這個時候有另一個緊急的問題須要解決。使用 Git 這個時候不須要將這個問題跟 issue 007 混合在一塊兒。也不用去還原關於 iss007 的修改,再添加關於這個緊急問題的修改,只須要須要回到 master 分支,建立新分支解決緊急問題。

這裏須要注意,切換到 master 分支以前,須要留意工做目錄和暫存區有沒有未提交的文件,它會檢測出分支衝突阻止切換分支(待會介紹這種狀況)。這裏假設工做目錄是乾淨的:

$ git switch master
Switched to branch 'master'

$ git switch -c hotfix
Switched to a new branch 'hotfix'
$ vi ISSUE
$ git commit -a -m "fixed the hot issue"

圖 5

如今緊急問題已經修復,這個時候就能夠合併回 master 分支部署到線上,以下:

$ git switch master
$ git merge hotfix
Updating 861b17e..48a6cfa
Fast-forward
 ISSUE | 1 +
 1 file changed, 1 insertion(+)
 create mode 100644 ISSUE

branch_img_6.jpg

注意 Fast-forward,這個表示的是「快速模式」,也就是直接將 master 指向 hotfix 的當前提交,合併速度快。

注意:,並非全部的合併都能 Fast-forward

同時,咱們能夠刪除 hotfix 分支,由於已經完成任務了:

$ git branch -d hotfix
Deleted branch hotfix (was 48a6cfa).

衝突解決

如今緊急的問題已經修復部署上線,能夠回到 iss007 分支繼續修改文件。假設 iss007 分支也通過修改解決了問題。

圖 7

這個時候也考慮合併會 master 部署到線上:

$ git switch master
$ git merge iss007
CONFLICT (add/add): Merge conflict in ISSUE
Auto-merging ISSUE
Automatic merge failed; fix conflicts and then commit the result.

這個時候會出現合併衝突,由於 iss007 分支與 hotfix 分支修改的是同一個文件。Git 不會自動建立新的合併提交。須要手動解決衝突以後再提交。

使用 git status 查看處於未合併狀態的文件:

$ git status
On branch master
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 added:      ISSUE

no changes added to commit (use "git add" and/or "git commit -a")

如今查看衝突的文件:

<<<<<<< HEAD
Fix hot issue.
=======
Fix issue 007.
Fix issue continue and done.
>>>>>>> iss007

Git 用 <<<<<<<=======>>>>>>> 標記出不一樣分支的內容,修改內容以下:

Fix hot issue.
Fix issue 007.
Fix issue continue and done.

將標記的部分刪除,根據需求解決衝突,而後用 git add 將其標記爲衝突已解決。

$ git add ISSUE

$ git status
On branch master
All conflicts fixed but you are still merging.
  (use "git commit" to conclude merge)

Changes to be committed:
        modified:   ISSUE

這個時候就能夠提交部署了:

git commit -a -m "Merge branch 'iss007'"
[master 7059dd0] Merge branch 'iss007'

分支管理策略

git branch 除了建立和刪除分支。不加參數時,命令可以獲得全部分支的列表:

$ git branch
  dev
  iss007
* master

* 號表示當前的分支(即便 HEAD 指針指向的分支)。

加上 -v 參數也能夠查看每一個分支最後的提交信息:

$ git branch -v
  dev    861b17e Initial commit of project
  iss007 5267039 Fix issue 007 again
* master 7059dd0 Merge branch 'iss007'

git branch 命令還有兩個選項 --merged--no-merged。這兩個選項分別過濾合併或還沒有合併到當前的分支。

例如:查看已經合併到當前的分支:

$ git branch --merged
  iss007
* master

由於已經合併過 ·iss007`,因此這個時候也能夠刪除該分支,這裏並不會失去任何東西:

$ git branch -d iss007
Deleted branch iss007 (was 5267039).

查看全部包含未合併工做的分支,可使用 git branch --no-merged:

$ git branch --no-merged
  dev

這裏顯示了 dev 分支,它包含了還未合併的工做,如今嘗試使用刪除命令會運行失敗:

$ git branch -d dev
error: The branch 'dev' is not fully merged.
If you are sure you want to delete it, run 'git branch -D dev'.

若是真的想要刪除這些未合併的工做,能夠根據下面的提示,使用 -D 選項強制刪除。

抓取分支

多人協做的狀況下,你們都會往 masterdev 分支推送各自的修改。

可是當協做者修改的是同個文件,且先一部提交到遠程倉庫時,這個時候,當你修改完問題提交的時候,就會提示衝突。

解決辦法:用 git pull 抓取文件,在本地合併解決衝突再推送便可。

這裏參考上面衝突解決的步驟就能夠。

變基(Rebase)

每次合併再 push 後,分支會變得混亂。

Git 有一種成爲 rebase 的操做,可以讓 Git 的提交歷史變成直線。

這裏我用以前學習的例子來加以說明。

和遠程分支同步後,對文件作了兩次提交,用 git log 查看:

* 24be579 (HEAD -> master) add author
* 94448fe add comment
* 6a7291a store Git Learn
* e36f4e9 (origin/master) add content about co-operative and update catalog
* 6cd4087 add knowledge about feature branch

注意到 Git 用 (Head -> master)(origin/master) 標識當前分支的 HEAD 和遠程 origin 的位置分別是 24be579 add authore36f4e9 add content..,本地比遠程分支快 3 個提交

如今嘗試推送本地分支:

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

失敗了,說明有人先往遠程庫推送了分支。先 pull 一下

$ git pull
remote: Enumerating objects: 4, done.
remote: Counting objects: 100% (4/4), 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:damengsanqianqiu/git_learn
   e36f4e9..538bd7c  master     -> origin/master
CONFLICT (add/add): Merge conflict in hello.py
Auto-merging hello.py
Automatic merge failed; fix conflicts and then commit the result.

這裏自動合併失敗,手動解決衝突(詳情略)。

解決衝突,再提交。再用 git status 查看狀態

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

加上剛纔合併的提交,如今咱們分支比遠程分支超前 4 個提交。

git log 看看

$ git log  --graph --pretty=oneline --abbrev-commit
*   2027993 (HEAD -> master) fix conflict of hello.py
|\  
| * 538bd7c (origin/master) set exit = 1
* | 24be579 add author
* | 94448fe add comment
* | 6a7291a store Git Learn
|/  
* e36f4e9 add content about co-operative and update catalog

如今分支比較亂,這個時候,rebase 就派上用場了,用 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
* 2027993 (HEAD -> master) fix conflict of hello.py
* 24be579 add author
* 94448fe add comment
* 6a7291a store Git Learn
* 538bd7c (origin/master) set exit = 1

原來分叉的提交如今變成一條直線。咱們注意到 Git 把咱們本地的提交「挪動」了位置,放到了 538bd7c (origin/master) set exit = 1以後,這樣整個提交歷史就成了一條直線。rebase 操做先後,最終的提交內容是一直的,可是,本地的 commit 修改內容已經變化了,他們的修改再也不基於 e36f4e9 add content...,而是基於 538bd7c (origin/master) set exit = 1,但最後的提交 2027993 內容是一致的。

這就是 rebase 操做的特色:把分叉的提交歷史「整理」成一條直線,看上去更直觀。缺點就是本地的分叉提交已經被修改過了。

關於變基與合併,這裏會有不一樣的觀點,有的以爲提交歷史就是記錄發生過什麼,使用變基會將痕跡抹除,另外的觀點會以爲提交歷史就是項目過程發生的事情,沒人在乎初版的手稿,大部分手冊也是屢次修訂後才能方便使用。

這裏並無必要追究一個完整簡單的答案。須要根據實際狀況去判斷做出選擇。

不過,整體的原則是,還沒有推送或分享的本地修改,能夠根據狀況執行變基清理歷史,已推送到別處的提交,不要執行變基操做。


以上就是本篇的主要內容。

目前,關於 Git 的介紹就已經算是更新完成。可是,這些內容還遠遠沒有將 Git 講透徹。可是這些內容可以讓你簡單瞭解 Git,也但願這些內容可以讓你上手學習,慢慢了解。

這裏如果但願更深刻了解 Git 的使用能夠查看官方文檔:

https://git-scm.com/book/en/v2

歡迎關注微信公衆號《書所集錄》
相關文章
相關標籤/搜索