本來地址:git乾貨系列:(五)多人協同工做之分支管理
博客地址:tengj.top/javascript
分支就是科幻電影裏面的平行宇宙,當你正在電腦前努力學習Git
的時候,另外一個你正在另外一個平行宇宙裏努力學習SVN
。若是兩個平行宇宙互不干擾,那對如今的你也沒啥影響。不過,在某個時間點,兩個平行宇宙合併了,結果,你既學會了Git
又學會了SVN
!
java
爲了真正理解 Git
處理分支的方式,咱們須要回顧一下Git
是如何保存數據的。
Git 保存的不是文件的變化或者差別,而是一系列不一樣時刻的文件快照。在進行提交操做時,Git
會保存一個提交對象(commit object
)。知道了Git
保存數據的方式,咱們能夠很天然的想到——該提交對象會包含一個指向暫存內容快照的指針。 但不只僅是這樣,該提交對象還包含了做者的姓名和郵箱、提交時輸入的信息以及指向它的父對象的指針。首次提交產生的提交對象沒有父對象,普通提交操做產生的提交對象有一個父對象,而由多個分支合併產生的提交對象有多個父對象。 git
Git
的分支,其實本質上僅僅是指向提交對象的可變指針。 Git
的默認分支名字是 master
。 在屢次提交操做以後,你其實已經有一個指向最後那個提交對象的 master
分支。 它會在每次的提交操做中自動向前移動。 vim
Git
的 「master」 分支並非一個特殊分支。它就跟其它分支徹底沒有區別。 之因此幾乎每個倉庫> 都有 master 分支,是由於git init
命令默認建立它,而且大多數人都懶得去改動它。安全
分支在實際中有什麼用呢?假設你準備開發一個新功能,可是須要兩週才能完成,第一週你寫了50%的代碼,若是馬上提交,因爲代碼還沒寫完,不完整的代碼庫會致使別人不能幹活了。若是等代碼所有寫完再一次提交,又存在丟失天天進度的巨大風險。
如今有了分支,就不用怕了。你建立了一個屬於你本身的分支,別人看不到,還繼續在原來的分支上正常工做,而你在本身的分支上幹活,想提交就提交,直到開發完畢後,再一次性合併到原來的分支上,這樣,既安全,又不影響別人工做。
其餘版本控制系統如SVN等都有分支管理,可是用過以後你會發現,這些版本控制系統建立和切換分支比蝸牛還慢,簡直讓人沒法忍受,結果分支功能成了擺設,你們都不去用。
但Git
的分支是不同凡響的,不管建立、切換和刪除分支,Git
在1秒鐘以內就能完成!不管你的版本庫是1個文件仍是1萬個文件。bash
Git
是怎麼建立新分支的呢? 很簡單,它只是爲你建立了一個能夠移動的新的指針。 好比,建立一個 testing
分支, 你須要使用 git branch
命令: 服務器
$ git branch testing複製代碼
這會在當前所在的提交對象上建立一個指針。 學習
那麼,Git
又是怎麼知道當前在哪個分支上呢? 也很簡單,它有一個名爲 HEAD
的特殊指針。 請注意它和許多其它版本控制系統(如 Subversion 或 CVS)裏的 HEAD
概念徹底不一樣。 在 Git
中,它是一個指針,指向當前所在的本地分支(譯註:將 HEAD
想象爲當前分支的別名)。 在本例中,你仍然在master
分支上。 由於 git branch
命令僅僅 建立 一個新分支,並不會自動切換到新分支中去。 fetch
你能夠簡單地使用 git log
命令查看各個分支當前所指的對象。 提供這一功能的參數是 --decorate
。ui
$ git log --oneline --decorate
f30ab (HEAD, master, testing) add feature #32 - ability to add new
34ac2 fixed bug #1328 - stack overflow under certain conditions
98ca9 initial commit of my project複製代碼
正如你所見,當前 「master」 和 「testing」 分支均指向校驗和以 f30ab
開頭的提交對象。
要切換到一個已存在的分支,你須要使用git checkout
命令。 咱們如今切換到新建立的 testing
分支去:
$ git checkout testing複製代碼
這樣 HEAD
就指向 testing
分支了。
上面的建立分支和切換分支命令能夠合起來用下面這個命令來替代。
$ git checkout -b testing複製代碼
那麼,這樣的實現方式會給咱們帶來什麼好處呢? 如今不妨再提交一次:
$ vim test.rb
$ git commit -a -m 'made a change'複製代碼
testing
分支向前移動了,可是
master
分支卻沒有,它仍然指向運行
git checkout
時所指的對象。 這就有意思了,如今咱們切換回
master
分支看看:
$ git checkout master複製代碼
master
分支,二是將工做目錄恢復成
master
分支所指向的快照內容。 也就是說,你如今作修改的話,項目將始於一個較舊的版本。 本質上來說,這就是忽略
testing
分支所作的修改,以便於向另外一個方向進行開發。
git branch
命令查看當前分支,注意前面帶
*
的表示當前分支
Note
分支切換會改變你工做目錄中的文件
在切換分支時,必定要注意你工做目錄裏的文件會被改變。 若是是切換到一個較舊的分支,你的工做目> 錄會恢復到該分支最後一次提交時的樣子。 若是Git
不能幹淨利落地完成這個任務,它將禁止切換分支。
假如咱們在testing
上的工做完成了,就能夠把testing
合併到master
上。Git
怎麼合併呢?最簡單的方法,就是直接把master
指向testing
的當前提交,就完成了合併,這裏你須要使用git merge
命令
$ git merge testing
Updating 64ba18a..760118b
Fast-forward
hello.txt | 1 +
1 file changed, 1 insertion(+)
create mode 100644 hello.txt複製代碼
git merge
命令用於合併指定分支到當前分支。合併後,再查看內容,就能夠看到,和testing
分支的最新提交是徹底同樣的。
注意到上面的Fast-forward
信息,Git
告訴咱們,此次合併是「快進模式」,也就是直接把master
指向testing
的當前提交,因此合併速度很是快。
固然,也不是每次合併都能Fast-forward
,咱們後面會講其餘方式的合併。
合併完分支後,甚至能夠刪除dev
分支。刪除dev
分支就是把dev
指針給刪掉,刪掉後,咱們就剩下了一條master
分支,這裏須要使用git branch -d
命令來刪除分支
$ git branch -d testing
Deleted branch testing (was 760118b).複製代碼
人生不如意之事十之八九,合併分支每每也不是一路順風的。
準備新的dev
分支,繼續咱們的新分支開發:
$ git checkout -b dev
Switched to a new branch 'dev'複製代碼
修改README.md
內容,添加同樣內容"day day up~",在dev
分支上提交:
$ git commit -am "one commit"
[dev 6a6a08e] one commit
1 file changed, 1 insertion(+)複製代碼
切換到master
分支:
$ git checkout master
Switched to branch 'master'
Your branch is up-to-date with 'origin/master'.複製代碼
Git
還會自動提示咱們當前master
分支比遠程的master
分支要超前1個提交。
在master
分支上把README.md
文件的最後改成 good good study
,而後提價
$ git commit -am "two commit"
[master 75d6f25] two commit
1 file changed, 1 insertion(+)複製代碼
如今,master
分支和dev
分支各自都分別有新的提交,變成了這樣:
$ git merge dev
Auto-merging README.md
CONFLICT (content): Merge conflict in README.md
Automatic merge failed; fix conflicts and then commit the result.複製代碼
果真衝突了!Git告訴咱們, README.md文件存在衝突,必須手動解決衝突後再提交。git status
也能夠告訴咱們衝突的文件:
$ git status
On branch master
Your branch is ahead of 'origin/master' by 1 commit.
(use "git push" to publish your local commits)
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")複製代碼
咱們能夠直接查看README.md
的內容:
$ cat README.md
#gitLearn
<<<<<<< HEAD
good good study
=======
day day up
>>>>>>> dev複製代碼
Git用<<<<<<<
,=======
,>>>>>>>
標記出不一樣分支的內容,咱們修改以下後保存:
#gitLearn
good good study
day day up複製代碼
再提交:
$ git commit -am 'merge commit'
[master 9a4d00b] merge commit複製代碼
如今,master
分支和dev
分支變成了下圖所示:
用帶參數的git log
也能夠看到分支的合併狀況:
$ git log --graph --pretty=oneline --abbrev-commit
* 9a4d00b merge commit
|\
| * 6a6a08e one commit
* | 75d6f25 two commit
|/
* ae06dcf 123
* 760118b test
* 64ba18a test
|\
| * 4392848 Accept Merge Request #1 test : (dev -> master)
| |\
| | * a430c4b update README.md
| |/
| * 88ec6d7 Initial commit
* 32d11c8 update README.md
* 8d5acc1 new file README
* e02f115 Initial commit複製代碼
最後,刪除feature1
分支:
$ git branch -d dev
Deleted branch dev (was 6a6a08e).複製代碼
一般,合併分支時,若是可能,Git會用Fast forward
模式,但這種模式下,刪除分支後,會丟掉分支信息。
若是要強制禁用Fast forward
模式,Git
就會在merge
時生成一個新的commit
,這樣,從分支歷史上就能夠看出分支信息。
下面咱們實戰一下--no-ff
方式的git merge
:
首先,仍然建立並切換dev
分支:
$ git checkout -b dev
Switched to a new branch 'dev'複製代碼
修改README.md文件,並提交一個新的commit:
$ git commit -am 'submit'
[dev fee6025] submit
1 file changed, 1 insertion(+)複製代碼
如今,咱們切換回master
:
$ git checkout master
Switched to branch 'master'複製代碼
目前來講流程圖是這樣:
準備合併dev
分支,請注意--no-ff
參數,表示禁用Fast forward
:
$ git merge --no-ff -m "merge with no-ff" dev
Merge made by the 'recursive' strategy.
README.md | 1 +
1 file changed, 1 insertion(+)複製代碼
由於本次合併要建立一個新的commit,因此加上-m
參數,把commit描述寫進去。
合併後,咱們用git log
看看分支歷史:
$ git log --graph --pretty=oneline --abbrev-commit
* b98f802 merge with no-ff
|\
| * fee6025 submit
|/
* 9a4d00b merge commit
...複製代碼
能夠看到,不使用Fast forward
模式,merge後就像這樣:
實際公司開發的時候通常3個分支就能夠了:
首先,master
分支應該是很是穩定的,也就是僅用來發布新版本,平時不能在上面幹活;
幹活都在dev
分支上,也就是說,dev
分支是不穩定的,到某個時候,好比1.0版本發佈時,再把dev
分支合併到master
上,在master
分支發佈1.0版本,你和你的小夥伴們每一個人都在dev
分支上幹活,每一個人都有本身的分支,時不時地往dev
分支上合併就能夠了;bug
分支用來處理平常bug,搞定後合到dev分支便可;
假設遠程公共倉庫,有一個master
和一個dev
分支,進行多人協做開發時候(每一個人的公鑰必須加入到遠程帳號下,不然沒法push
), 每一個人都應該clone
一份到本地。 可是clone
的只是master
,若是遠程的master
和dev
同樣,不要緊;若是不一致,則須要clone
出dev
分支 git checkout -b dev origin/dev
以後每一個人在本地的dev
分支上獨自開發(最好不要在mast
上開發), 開發完成以後push
到遠程dev
, git push origin dev
。 以後審覈人再肯定是否合併dev
到master
。
當你從遠程倉庫克隆時,實際上Git自動把本地的master
分支和遠程的master
分支對應起來了,而且,遠程倉庫的默認名稱是origin
。
要查看遠程庫的信息,用git remote
:
$ git remote
origin複製代碼
或者,用git remote -v
顯示更詳細的信息:
$ git remote -v
origin git@git.coding.net:tengj/gitLearn.git (fetch)
origin git@git.coding.net:tengj/gitLearn.git (push)複製代碼
上面顯示了能夠抓取和推送的origin
的地址。若是沒有推送權限,就看不到push的地址。
推送分支,就是把該分支上的全部本地提交推送到遠程庫。推送時,要指定本地分支,這樣,Git
就會把該分支推送到遠程庫對應的遠程分支上:
$ git push origin master複製代碼
若是要推送其餘分支,好比dev
,就改爲:
$ git push origin dev複製代碼
多人協做時,你們都會往master
和dev
分支上推送各自的修改。
如今,模擬一個你的小夥伴,能夠在另外一臺電腦(注意要把SSH Key
添加到GitHub
)或者同一臺電腦的另外一個目錄下克隆:
$ git clone git@git.coding.net:tengj/gitStudy.git
Cloning into 'gitStudy'...
remote: Counting objects: 3, done.
remote: Total 3 (delta 0), reused 0 (delta 0)
Receiving objects: 100% (3/3), done.
Checking connectivity... done.複製代碼
當你的小夥伴從遠程庫clone時,默認狀況下,你的小夥伴只能看到本地的master
分支。不信能夠用git branch
命令看看:
$ git branch
* master複製代碼
如今,你的小夥伴要在dev
分支上開發,就必須建立遠程origin
的dev
分支到本地,因而他用這個命令建立本地dev
分支(程分支dev要先建立)。
$ git checkout -b dev
git複製代碼
建立dev分以後,先同步遠程服務器上的數據到本地
$ git fetch origin
From git.coding.net:tengj/gitStudy
* [new branch] dev -> origin/dev複製代碼
如今,他就能夠在dev
上繼續修改,而後,時不時地把dev
分支push
到遠程:
$ git commit -am 'test'
[dev c120ad6] test
1 file changed, 1 insertion(+)
$ git push origin dev
Counting objects: 3, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (2/2), done.
Writing objects: 100% (3/3), 262 bytes | 0 bytes/s, done.
Total 3 (delta 0), reused 0 (delta 0)
To git@git.coding.net:tengj/gitStudy.git
65c05aa..c120ad6 dev -> dev複製代碼
你的小夥伴已經向origin/dev
分支推送了他的提交,而碰巧你也對一樣的文件做了修改,並試圖推送:
$ git push origin dev
To git@git.coding.net:tengj/gitStudy.git
! [rejected] dev -> dev (fetch first)
error: failed to push some refs to 'git@git.coding.net:tengj/gitStudy.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.複製代碼
推送失敗,由於你的小夥伴的最新提交和你試圖推送的提交有衝突,解決辦法也很簡單,Git已經提示咱們,先用git pull
把最新的提交從origin/dev
抓下來,而後,在本地合併,解決衝突,再推送:
$ git pull origin dev
remote: Counting objects: 3, done.
remote: Compressing objects: 100% (2/2), done.
remote: Total 3 (delta 0), reused 0 (delta 0)
Unpacking objects: 100% (3/3), done.
From git.coding.net:tengj/gitStudy
* branch dev -> FETCH_HEAD
b7b87f4..f636337 dev -> origin/dev
Auto-merging a.txt
CONFLICT (content): Merge conflict in a.txt
Automatic merge failed; fix conflicts and then commit the result.複製代碼
所以,多人協做的工做模式一般是這樣:
git push origin branch-name
推送本身的修改;git pull
試圖合併;git push origin branch-name
推送就能成功!若是git pull
提示「no tracking information」,則說明本地分支和遠程分支的連接關係沒有建立,用命令git branch --set-upstream-to branch-name origin/branch-name
。
這就是多人協做的工做模式,一旦熟悉了,就很是簡單。
到此,Git
分支管理就學完了,整理一下所學的命令,大致以下:
git branch 查看當前分支
git branch -v 查看每個分支的最後一次提交
git branch -a 查看本地和遠程分支的狀況
git branch --merged 查看已經與當前分支合併的分支
git branch --no-merged 查看已經與當前分支未合併的分支
git branch -r 查看遠程分支
git branch dev 建立分支 dev
git checkout dev 切換到分支dev
git checkout -b dev 建立並切換分支dev
git merge dev 名稱爲dev的分支與當前分支合併
git branch -d dev 刪除分支dev複製代碼
一直以爲本身寫的不是技術,而是情懷,一篇篇文章是本身這一路走來的痕跡。靠專業技能的成功是最具可複製性的,但願個人這條路能讓你少走彎路,但願我能幫你抹去知識的蒙塵,但願我能幫你理清知識的脈絡,但願將來技術之巔上有你也有我。
更多幹貨內容,盡在嘟爺java超神學堂(javaLearn),您不掃一下麼