git乾貨系列:(五)多人協同工作之分支管理 | 嘟嘟獨立博客
- 1. 前言
- 2. 正文
- 3. 分支簡介
- 4. 分支創建
- 5. 分支切換
- 6. 合併分支(快速合併)
- 7. 刪除分支
- 8. 分支合併衝突
- 9. 合併分支(普通合併)
- 10. 分支管理策略
- 11. 團隊多人開發協作
- 11.1. 推送分支
- 11.2. 抓取分支
前言
分支就是科幻電影裡面的平行宇宙,當你正在電腦前努力學習Git
的時候,另一個你正在另一個平行宇宙里努力學習SVN
。如果兩個平行宇宙互不干擾,那對現在的你也沒啥影響。不過,在某個時間點,兩個平行宇宙合併了,結果,你既學會了Git
又學會了SVN
!
正文分支簡介
為了真正理解 Git
處理分支的方式,我們需要回顧一下Git
是如何保存數據的。Git 保存的不是文件的變化或者差異,而是一系列不同時刻的文件快照。在進行提交操作時,Git
會保存一個提交對象(commit object
)。知道了Git
保存數據的方式,我們可以很自然的想到——該提交對象會包含一個指向暫存內容快照的指針。 但不僅僅是這樣,該提交對象還包含了作者的姓名和郵箱、提交時輸入的信息以及指向它的父對象的指針。首次提交產生的提交對象沒有父對象,普通提交操作產生的提交對象有一個父對象,而由多個分支合併產生的提交對象有多個父對象。
Git
的分支,其實本質上僅僅是指向提交對象的可變指針。 Git
的默認分支名字是 master
。 在多次提交操作之後,你其實已經有一個指向最後那個提交對象的 master
分支。 它會在每次的提交操作中自動向前移動。
Git
的 「master」 分支並不是一個特殊分支。它就跟其它分支完全沒有區別。 之所以幾乎每一個倉庫> 都有 master 分支,是因為git init
命令默認創建它,並且大多數人都懶得去改動它。
分支在實際中有什麼用呢?假設你準備開發一個新功能,但是需要兩周才能完成,第一周你寫了50%的代碼,如果立刻提交,由於代碼還沒寫完,不完整的代碼庫會導致別人不能幹活了。如果等代碼全部寫完再一次提交,又存在丟失每天進度的巨大風險。現在有了分支,就不用怕了。你創建了一個屬於你自己的分支,別人看不到,還繼續在原來的分支上正常工作,而你在自己的分支上幹活,想提交就提交,直到開發完畢後,再一次性合併到原來的分支上,這樣,既安全,又不影響別人工作。其他版本控制系統如SVN等都有分支管理,但是用過之後你會發現,這些版本控制系統創建和切換分支比蝸牛還慢,簡直讓人無法忍受,結果分支功能成了擺設,大家都不去用。但Git
的分支是與眾不同的,無論創建、切換和刪除分支,Git
在1秒鐘之內就能完成!無論你的版本庫是1個文件還是1萬個文件。
分支創建
Git
是怎麼創建新分支的呢? 很簡單,它只是為你創建了一個可以移動的新的指針。 比如,創建一個 testing
分支, 你需要使用 git branch
命令:
1 |
$ git branch testing |
這會在當前所在的提交對象上創建一個指針。
兩個指向相同提交歷史的分支。
那麼,Git
又是怎麼知道當前在哪一個分支上呢? 也很簡單,它有一個名為 HEAD
的特殊指針。 請注意它和許多其它版本控制系統(如 Subversion 或 CVS)里的 HEAD
概念完全不同。 在 Git
中,它是一個指針,指向當前所在的本地分支(譯註:將 HEAD
想像為當前分支的別名)。 在本例中,你仍然在master
分支上。 因為 git branch
命令僅僅 創建 一個新分支,並不會自動切換到新分支中去。
HEAD 指向當前所在的分支.
你可以簡單地使用 git log
命令查看各個分支當前所指的對象。 提供這一功能的參數是 --decorate
。
1234 |
$ git log --oneline --decoratef30ab (HEAD, master, testing) add feature #32 - ability to add new34ac2 fixed bug #1328 - stack overflow under certain conditions98ca9 initial commit of my project |
正如你所見,當前 「master」 和 「testing」 分支均指向校驗和以 f30ab
開頭的提交對象。
要切換到一個已存在的分支,你需要使用git checkout
命令。 我們現在切換到新創建的 testing
分支去:
1 |
$ git checkout testing |
這樣 HEAD
就指向 testing
分支了。
HEAD 指向當前所在的分支.
上面的創建分支和切換分支命令可以合起來用下面這個命令來替代。
1 |
$ git checkout -b testing |
那麼,這樣的實現方式會給我們帶來什麼好處呢? 現在不妨再提交一次:
12 |
$ vim test.rb$ git commit -a -m "made a change" |
HEAD 分支隨著提交操作自動向前移動.如圖所示,你的 testing
分支向前移動了,但是 master
分支卻沒有,它仍然指向運行 git checkout
時所指的對象。 這就有意思了,現在我們切換回 master
分支看看:
1 |
$ git checkout master |
檢出時 HEAD 隨之移動.這條命令做了兩件事。 一是使 HEAD 指回 master
分支,二是將工作目錄恢復成 master
分支所指向的快照內容。 也就是說,你現在做修改的話,項目將始於一個較舊的版本。 本質上來講,這就是忽略testing
分支所做的修改,以便於向另一個方向進行開發。可以使用 git branch
命令查看當前分支,注意前面帶*
的表示當前分支
Note分支切換會改變你工作目錄中的文件在切換分支時,一定要注意你工作目錄里的文件會被改變。 如果是切換到一個較舊的分支,你的工作目> 錄會恢復到該分支最後一次提交時的樣子。 如果
Git
不能幹凈利落地完成這個任務,它將禁止切換分支。
合併分支(快速合併)
假如我們在testing
上的工作完成了,就可以把testing
合併到master
上。Git
怎麼合併呢?最簡單的方法,就是直接把master
指向testing
的當前提交,就完成了合併,這裡你需要使用git merge
命令
123456 |
$ git merge testingUpdating 64ba18a..760118bFast-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
命令來刪除分支
12 |
$ git branch -d testingDeleted branch testing (was 760118b). |
分支合併衝突
人生不如意之事十之八九,合併分支往往也不是一帆風順的。準備新的dev
分支,繼續我們的新分支開發:
12 |
$ git checkout -b devSwitched to a new branch "dev" |
修改README.md
內容,添加一樣內容」day day up~」,在dev
分支上提交:
123 |
$ git commit -am "one commit"[dev 6a6a08e] one commit 1 file changed, 1 insertion(+) |
切換到master
分支:
123 |
$ git checkout masterSwitched to branch "master"Your branch is up-to-date with "origin/master". |
Git
還會自動提示我們當前master
分支比遠程的master
分支要超前1個提交。在master
分支上把README.md
文件的最後改為 good good study
,然後提價
123 |
$ git commit -am "two commit"[master 75d6f25] two commit 1 file changed, 1 insertion(+) |
現在,master
分支和dev
分支各自都分別有新的提交,變成了這樣:
這種情況下,Git無法執行「快速合併」,只能試圖把各自的修改合併起來,但這種合併就可能會有衝突,我們試試看:
1234 |
$ git merge devAuto-merging README.mdCONFLICT (content): Merge conflict in README.mdAutomatic merge failed; fix conflicts and then commit the result. |
果然衝突了!Git告訴我們, README.md文件存在衝突,必須手動解決衝突後再提交。git status
也可以告訴我們衝突的文件:
1234567891011121314151617181920212223 |
$ git statusOn branch masterYour 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.mdno changes added to commit (use "git add" and/or "git commit -a")``` 我們可以直接查看`README.md`的內容: ``` bash$ cat README.md#gitLearn<<<<<<< HEADgood good study=======day day up>>>>>>> dev |
Git用<<<<<<<
,=======
,>>>>>>>
標記出不同分支的內容,我們修改如下後保存:
1 |
#gitLearngood good studyday day up |
再提交:
12 |
$ git commit -am "merge commit"[master 9a4d00b] merge commit |
現在,master
分支和dev
分支變成了下圖所示:
用帶參數的git log
也可以看到分支的合併情況:
123456789101112131415161718192021222324 |
$ 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`分支: ``` bash$ git branch -d devDeleted branch dev (was 6a6a08e). |
合併分支(普通合併)
通常,合併分支時,如果可能,Git會用Fast forward
模式,但這種模式下,刪除分支後,會丟掉分支信息。如果要強制禁用Fast forward
模式,Git
就會在merge
時生成一個新的commit
,這樣,從分支歷史上就可以看出分支信息。下面我們實戰一下--no-ff
方式的git merge
:首先,仍然創建並切換dev
分支:
12 |
$ git checkout -b devSwitched to a new branch "dev" |
修改README.md文件,並提交一個新的commit:
123 |
$ git commit -am "submit"[dev fee6025] submit 1 file changed, 1 insertion(+) |
現在,我們切換回master
:
12 |
$ git checkout masterSwitched to branch "master" |
目前來說流程圖是這樣:
準備合併dev
分支,請注意--no-ff
參數,表示禁用Fast forward
:
1234 |
$ git merge --no-ff -m "merge with no-ff" devMerge made by the "recursive" strategy. README.md | 1 + 1 file changed, 1 insertion(+) |
因為本次合併要創建一個新的commit,所以加上-m
參數,把commit描述寫進去。
合併後,我們用git log
看看分支歷史:
1234567 |
$ git log --graph --pretty=oneline --abbrev-commit* b98f802 merge with no-ff|| * fee6025 submit|/* 9a4d00b merge commit... |
可以看到,不使用Fast forward
模式,merge後就像這樣:
分支管理策略
實際公司開發的時候一般3個分支就可以了:
- mster 主分支用來發布
- dev 日常開發用的分支
- bug 修改bug用的分支
首先,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
:
12 |
$ git remoteorigin |
或者,用git remote -v
顯示更詳細的信息:
123 |
$ git remote -vorigin git@git.coding.net:tengj/gitLearn.git (fetch)origin git@git.coding.net:tengj/gitLearn.git (push) |
上面顯示了可以抓取和推送的origin
的地址。如果沒有推送許可權,就看不到push的地址。
推送分支,就是把該分支上的所有本地提交推送到遠程庫。推送時,要指定本地分支,這樣,Git
就會把該分支推送到遠程庫對應的遠程分支上:
1 |
$ git push origin master |
如果要推送其他分支,比如dev
,就改成:
1 |
$ git push origin dev |
多人協作時,大家都會往master
和dev
分支上推送各自的修改。現在,模擬一個你的小夥伴,可以在另一台電腦(注意要把SSH Key
添加到GitHub
)或者同一台電腦的另一個目錄下克隆:
1234567891011 |
$ git clone git@git.coding.net:tengj/gitStudy.gitCloning 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`命令看看:``` bash$ git branch* master |
現在,你的小夥伴要在dev
分支上開發,就必須創建遠程origin
的dev
分支到本地,於是他用這個命令創建本地dev
分支(程分支dev要先創建)。
12 |
$ git checkout -b devgit |
創建dev分之後,先同步遠程伺服器上的數據到本地
123 |
$ git fetch originFrom git.coding.net:tengj/gitStudy * [new branch] dev -> origin/dev |
現在,他就可以在dev
上繼續修改,然後,時不時地把dev
分支push
到遠程:
1 |
$ git commit -am "test"[dev c120ad6] test 1 file changed, 1 insertion(+)$ git push origin devCounting 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
分支推送了他的提交,而碰巧你也對同樣的文件作了修改,並試圖推送:
123456789 |
$ git push origin devTo 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 dohint: not have locally. This is usually caused by another repository pushinghint: to the same ref. You may want to first integrate the remote changeshint: (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
抓下來,然後,在本地合併,解決衝突,再推送:
1234567891011 |
$ git pull origin devremote: 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/devAuto-merging a.txtCONFLICT (content): Merge conflict in a.txtAutomatic 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
分支管理就學完了,整理一下所學的命令,大體如下:
1234567891011 |
git branch 查看當前分支git branch -v 查看每一個分支的最後一次提交git branch -a 查看本地和遠程分支的情況git branch --merged 查看已經與當前分支合併的分支git branch --no-merged 查看已經與當前分支未合併的分支git branch -r 查看遠程分支git branch dev 創建分支 devgit checkout dev 切換到分支devgit checkout -b dev 創建並切換分支devgit merge dev 名稱為dev的分支與當前分支合併git branch -d dev 刪除分支dev |
最近擼了個java的公眾號,學習資源超級多,視頻,電子書,最新開發工具一個都不能少,已全部分享到百度雲盤,求資源共享,打造一個學習方便,工作方便的java公眾號,開源開源,有需求的可以關注~撒花
推薦閱讀:
※禮包|極市分享資源大整理
※有了Ta,你的圖片也可以下起雨來
※視頻轉場技巧大分享【PR學習乾貨】
※藝術乾貨:畫個可怕、不懷好意的吻,是種什麼體驗?|張小玉
※【乾貨碼頭】| 「易」神探:家中養魚的12條禁忌!