git干貨系列:(五)多人協(xié)同工作之分支管理

前言


分支就是科幻電影里面的平行宇宙,當(dāng)你正在電腦前努力學(xué)習(xí)Git的時(shí)候,另一個(gè)你正在另一個(gè)平行宇宙里努力學(xué)習(xí)SVN。如果兩個(gè)平行宇宙互不干擾,那對(duì)現(xiàn)在的你也沒啥影響。不過,在某個(gè)時(shí)間點(diǎn),兩個(gè)平行宇宙合并了,結(jié)果,你既學(xué)會(huì)了Git又學(xué)會(huì)了SVN

正文


分支簡(jiǎn)介


為了真正理解 Git 處理分支的方式,我們需要回顧一下Git是如何保存數(shù)據(jù)的。
Git 保存的不是文件的變化或者差異,而是一系列不同時(shí)刻的文件快照。在進(jìn)行提交操作時(shí),Git會(huì)保存一個(gè)提交對(duì)象(commit object)。知道了Git保存數(shù)據(jù)的方式,我們可以很自然的想到——該提交對(duì)象會(huì)包含一個(gè)指向暫存內(nèi)容快照的指針。 但不僅僅是這樣,該提交對(duì)象還包含了作者的姓名和郵箱、提交時(shí)輸入的信息以及指向它的父對(duì)象的指針。首次提交產(chǎn)生的提交對(duì)象沒有父對(duì)象,普通提交操作產(chǎn)生的提交對(duì)象有一個(gè)父對(duì)象,而由多個(gè)分支合并產(chǎn)生的提交對(duì)象有多個(gè)父對(duì)象。

Git的分支,其實(shí)本質(zhì)上僅僅是指向提交對(duì)象的可變指針。 Git的默認(rèn)分支名字是 master。 在多次提交操作之后,你其實(shí)已經(jīng)有一個(gè)指向最后那個(gè)提交對(duì)象的 master 分支。 它會(huì)在每次的提交操作中自動(dòng)向前移動(dòng)。

Git 的 “master” 分支并不是一個(gè)特殊分支。它就跟其它分支完全沒有區(qū)別。 之所以幾乎每一個(gè)倉庫> 都有 master 分支,是因?yàn)?git init 命令默認(rèn)創(chuàng)建它,并且大多數(shù)人都懶得去改動(dòng)它。

分支在實(shí)際中有什么用呢?假設(shè)你準(zhǔn)備開發(fā)一個(gè)新功能,但是需要兩周才能完成,第一周你寫了50%的代碼,如果立刻提交,由于代碼還沒寫完,不完整的代碼庫會(huì)導(dǎo)致別人不能干活了。如果等代碼全部寫完再一次提交,又存在丟失每天進(jìn)度的巨大風(fēng)險(xiǎn)。
現(xiàn)在有了分支,就不用怕了。你創(chuàng)建了一個(gè)屬于你自己的分支,別人看不到,還繼續(xù)在原來的分支上正常工作,而你在自己的分支上干活,想提交就提交,直到開發(fā)完畢后,再一次性合并到原來的分支上,這樣,既安全,又不影響別人工作。
其他版本控制系統(tǒng)如SVN等都有分支管理,但是用過之后你會(huì)發(fā)現(xiàn),這些版本控制系統(tǒng)創(chuàng)建和切換分支比蝸牛還慢,簡(jiǎn)直讓人無法忍受,結(jié)果分支功能成了擺設(shè),大家都不去用。
Git的分支是與眾不同的,無論創(chuàng)建、切換和刪除分支,Git在1秒鐘之內(nèi)就能完成!無論你的版本庫是1個(gè)文件還是1萬個(gè)文件。

分支創(chuàng)建


Git是怎么創(chuàng)建新分支的呢? 很簡(jiǎn)單,它只是為你創(chuàng)建了一個(gè)可以移動(dòng)的新的指針。 比如,創(chuàng)建一個(gè) testing分支, 你需要使用 git branch 命令:

$ git branch testing

這會(huì)在當(dāng)前所在的提交對(duì)象上創(chuàng)建一個(gè)指針。

兩個(gè)指向相同提交歷史的分支。

那么,Git又是怎么知道當(dāng)前在哪一個(gè)分支上呢? 也很簡(jiǎn)單,它有一個(gè)名為 HEAD 的特殊指針。 請(qǐng)注意它和許多其它版本控制系統(tǒng)(如 Subversion 或 CVS)里的 HEAD 概念完全不同。 在 Git中,它是一個(gè)指針,指向當(dāng)前所在的本地分支(譯注:將 HEAD 想象為當(dāng)前分支的別名)。 在本例中,你仍然在master 分支上。 因?yàn)?git branch 命令僅僅 創(chuàng)建 一個(gè)新分支,并不會(huì)自動(dòng)切換到新分支中去。

HEAD 指向當(dāng)前所在的分支.

你可以簡(jiǎn)單地使用 git log 命令查看各個(gè)分支當(dāng)前所指的對(duì)象。 提供這一功能的參數(shù)是 --decorate。

$ 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

正如你所見,當(dāng)前 “master” 和 “testing” 分支均指向校驗(yàn)和以 f30ab 開頭的提交對(duì)象。

分支切換


要切換到一個(gè)已存在的分支,你需要使用git checkout命令。 我們現(xiàn)在切換到新創(chuàng)建的 testing 分支去:

$ git checkout testing

這樣 HEAD 就指向 testing 分支了。

HEAD 指向當(dāng)前所在的分支.

上面的創(chuàng)建分支和切換分支命令可以合起來用下面這個(gè)命令來替代。

$ git checkout -b testing 

那么,這樣的實(shí)現(xiàn)方式會(huì)給我們帶來什么好處呢? 現(xiàn)在不妨再提交一次:

$ vim test.rb
$ git commit -a -m 'made a change'

HEAD 分支隨著提交操作自動(dòng)向前移動(dòng).

如圖所示,你的 testing 分支向前移動(dòng)了,但是 master 分支卻沒有,它仍然指向運(yùn)行 git checkout 時(shí)所指的對(duì)象。 這就有意思了,現(xiàn)在我們切換回 master 分支看看:

$ git checkout master

檢出時(shí) HEAD 隨之移動(dòng).

這條命令做了兩件事。 一是使 HEAD 指回 master 分支,二是將工作目錄恢復(fù)成 master 分支所指向的快照內(nèi)容。 也就是說,你現(xiàn)在做修改的話,項(xiàng)目將始于一個(gè)較舊的版本。 本質(zhì)上來講,這就是忽略testing 分支所做的修改,以便于向另一個(gè)方向進(jìn)行開發(fā)。
可以使用 git branch命令查看當(dāng)前分支,注意前面帶*的表示當(dāng)前分支


Note
分支切換會(huì)改變你工作目錄中的文件
在切換分支時(shí),一定要注意你工作目錄里的文件會(huì)被改變。 如果是切換到一個(gè)較舊的分支,你的工作目> 錄會(huì)恢復(fù)到該分支最后一次提交時(shí)的樣子。 如果Git不能干凈利落地完成這個(gè)任務(wù),它將禁止切換分支。

合并分支(快速合并)


假如我們?cè)?code>testing上的工作完成了,就可以把testing合并到master上。Git怎么合并呢?最簡(jiǎn)單的方法,就是直接把master指向testing的當(dāng)前提交,就完成了合并,這里你需要使用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命令用于合并指定分支到當(dāng)前分支。合并后,再查看內(nèi)容,就可以看到,和testing分支的最新提交是完全一樣的。
注意到上面的Fast-forward信息,Git告訴我們,這次合并是“快進(jìn)模式”,也就是直接把master指向testing的當(dāng)前提交,所以合并速度非常快。
當(dāng)然,也不是每次合并都能Fast-forward,我們后面會(huì)講其他方式的合并。

刪除分支


合并完分支后,甚至可以刪除dev分支。刪除dev分支就是把dev指針給刪掉,刪掉后,我們就剩下了一條master分支,這里需要使用git branch -d命令來刪除分支

$ git branch -d testing
Deleted branch testing (was 760118b).

分支合并沖突


人生不如意之事十之八九,合并分支往往也不是一帆風(fēng)順的。
準(zhǔn)備新的dev分支,繼續(xù)我們的新分支開發(fā):

$ git checkout -b dev
Switched to a new branch 'dev'

修改README.md內(nèi)容,添加一樣內(nèi)容"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還會(huì)自動(dòng)提示我們當(dāng)前master分支比遠(yuǎn)程的master分支要超前1個(gè)提交。
master分支上把README.md文件的最后改為 good good study,然后提價(jià)

$ git commit -am "two commit"
[master 75d6f25] two commit
 1 file changed, 1 insertion(+)

現(xiàn)在,master分支和dev分支各自都分別有新的提交,變成了這樣:


這種情況下,Git無法執(zhí)行“快速合并”,只能試圖把各自的修改合并起來,但這種合并就可能會(huì)有沖突,我們?cè)囋嚳矗?p>

$ 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文件存在沖突,必須手動(dòng)解決沖突后再提交。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的內(nèi)容:

$ cat README.md
#gitLearn
<<<<<<< HEAD
good good study
=======
day day up
>>>>>>> dev

Git用<<<<<<<=======,>>>>>>>標(biāo)記出不同分支的內(nèi)容,我們修改如下后保存:

#gitLearn
good good study
day day up

再提交:

$ git commit -am 'merge commit'
[master 9a4d00b] merge commit

現(xiàn)在,master分支和dev分支變成了下圖所示:

用帶參數(shù)的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).

合并分支(普通合并)


通常,合并分支時(shí),如果可能,Git會(huì)用Fast forward模式,但這種模式下,刪除分支后,會(huì)丟掉分支信息。
如果要強(qiáng)制禁用Fast forward模式,Git就會(huì)在merge時(shí)生成一個(gè)新的commit,這樣,從分支歷史上就可以看出分支信息。
下面我們實(shí)戰(zhàn)一下--no-ff方式的git merge
首先,仍然創(chuàng)建并切換dev分支:

$ git checkout -b dev
Switched to a new branch 'dev'

修改README.md文件,并提交一個(gè)新的commit:

$ git commit -am 'submit'
[dev fee6025] submit
 1 file changed, 1 insertion(+)

現(xiàn)在,我們切換回master

$ git checkout master
Switched to branch 'master'

目前來說流程圖是這樣:

準(zhǔn)備合并dev分支,請(qǐng)注意--no-ff參數(shù),表示禁用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(+)

因?yàn)楸敬魏喜⒁獎(jiǎng)?chuàng)建一個(gè)新的commit,所以加上-m參數(shù),把commit描述寫進(jìn)去。

合并后,我們用git log看看分支歷史:

$ git log --graph --pretty=oneline --abbrev-commit
*   b98f802 merge with no-ff
|\
| * fee6025 submit
|/
*   9a4d00b merge commit
...

可以看到,不使用Fast forward模式,merge后就像這樣:

分支管理策略


實(shí)際公司開發(fā)的時(shí)候一般3個(gè)分支就可以了:

  1. mster 主分支用來發(fā)布
  2. dev 日常開發(fā)用的分支
  3. bug 修改bug用的分支

首先,master分支應(yīng)該是非常穩(wěn)定的,也就是僅用來發(fā)布新版本,平時(shí)不能在上面干活;
干活都在dev分支上,也就是說,dev分支是不穩(wěn)定的,到某個(gè)時(shí)候,比如1.0版本發(fā)布時(shí),再把dev分支合并到master上,在master分支發(fā)布1.0版本,你和你的小伙伴們每個(gè)人都在dev分支上干活,每個(gè)人都有自己的分支,時(shí)不時(shí)地往dev分支上合并就可以了;
bug分支用來處理日常bug,搞定后合到dev分支即可;

假設(shè)遠(yuǎn)程公共倉庫,有一個(gè)master和一個(gè)dev分支,進(jìn)行多人協(xié)作開發(fā)時(shí)候(每個(gè)人的公鑰必須加入到遠(yuǎn)程賬號(hào)下,否則無法push), 每個(gè)人都應(yīng)該clone一份到本地。 但是clone的只是master,如果遠(yuǎn)程的masterdev一樣,沒關(guān)系;如果不一致,則需要clonedev分支 git checkout -b dev origin/dev 之后每個(gè)人在本地的dev分支上獨(dú)自開發(fā)(最好不要在mast上開發(fā)), 開發(fā)完成之后push到遠(yuǎn)程dev, git push origin dev。 之后審核人再確定是否合并devmaster。

團(tuán)隊(duì)多人開發(fā)協(xié)作


當(dāng)你從遠(yuǎn)程倉庫克隆時(shí),實(shí)際上Git自動(dòng)把本地的master分支和遠(yuǎn)程的master分支對(duì)應(yīng)起來了,并且,遠(yuǎn)程倉庫的默認(rèn)名稱是origin
要查看遠(yuǎn)程庫的信息,用git remote

$ git remote
origin

或者,用git remote -v顯示更詳細(xì)的信息:

$ git remote -v
origin  git@git.coding.net:tengj/gitLearn.git (fetch)
origin  git@git.coding.net:tengj/gitLearn.git (push)

上面顯示了可以抓取和推送的origin的地址。如果沒有推送權(quán)限,就看不到push的地址。

推送分支


推送分支,就是把該分支上的所有本地提交推送到遠(yuǎn)程庫。推送時(shí),要指定本地分支,這樣,Git就會(huì)把該分支推送到遠(yuǎn)程庫對(duì)應(yīng)的遠(yuǎn)程分支上:

$ git push origin master

如果要推送其他分支,比如dev,就改成:

$ git push origin dev

抓取分支


多人協(xié)作時(shí),大家都會(huì)往masterdev分支上推送各自的修改。
現(xiàn)在,模擬一個(gè)你的小伙伴,可以在另一臺(tái)電腦(注意要把SSH Key添加到GitHub)或者同一臺(tái)電腦的另一個(gè)目錄下克?。?/p>

$ 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.

當(dāng)你的小伙伴從遠(yuǎn)程庫clone時(shí),默認(rèn)情況下,你的小伙伴只能看到本地的master分支。不信可以用git branch命令看看:

$ git branch
* master

現(xiàn)在,你的小伙伴要在dev分支上開發(fā),就必須創(chuàng)建遠(yuǎn)程origindev分支到本地,于是他用這個(gè)命令創(chuàng)建本地dev分支(程分支dev要先創(chuàng)建)。

$ git checkout -b dev
git

創(chuàng)建dev分之后,先同步遠(yuǎn)程服務(wù)器上的數(shù)據(jù)到本地

$ git fetch origin
From git.coding.net:tengj/gitStudy
 * [new branch]      dev        -> origin/dev

現(xiàn)在,他就可以在dev上繼續(xù)修改,然后,時(shí)不時(shí)地把dev分支push到遠(yuǎn)程:

$ 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

你的小伙伴已經(jīng)向origin/dev分支推送了他的提交,而碰巧你也對(duì)同樣的文件作了修改,并試圖推送:

$ 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.

推送失敗,因?yàn)槟愕男』锇榈淖钚绿峤缓湍阍噲D推送的提交有沖突,解決辦法也很簡(jiǎn)單,Git已經(jīng)提示我們,先用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.

因此,多人協(xié)作的工作模式通常是這樣:

  1. 首先,可以試圖用git push origin branch-name推送自己的修改;
  2. 如果推送失敗,則因?yàn)檫h(yuǎn)程分支比你的本地更新,需要先用git pull試圖合并;
  3. 如果合并有沖突,則解決沖突,并在本地提交;
  4. 沒有沖突或者解決掉沖突后,再用git push origin branch-name推送就能成功!

如果git pull提示“no tracking information”,則說明本地分支和遠(yuǎn)程分支的鏈接關(guān)系沒有創(chuàng)建,用命令git branch --set-upstream-to branch-name origin/branch-name。
這就是多人協(xié)作的工作模式,一旦熟悉了,就非常簡(jiǎn)單。

總結(jié)


到此,Git分支管理就學(xué)完了,整理一下所學(xué)的命令,大體如下:

git branch           查看當(dāng)前分支
git branch -v        查看每一個(gè)分支的最后一次提交
git branch -a        查看本地和遠(yuǎn)程分支的情況
git branch --merged  查看已經(jīng)與當(dāng)前分支合并的分支
git branch --no-merged 查看已經(jīng)與當(dāng)前分支未合并的分支
git branch -r        查看遠(yuǎn)程分支
git branch dev       創(chuàng)建分支 dev
git checkout dev     切換到分支dev
git checkout -b dev  創(chuàng)建并切換分支dev
git merge dev        名稱為dev的分支與當(dāng)前分支合并
git branch -d dev    刪除分支dev

一直覺得自己寫的不是技術(shù),而是情懷,一篇篇文章是自己這一路走來的痕跡??繉I(yè)技能的成功是最具可復(fù)制性的,希望我的這條路能讓你少走彎路,希望我能幫你抹去知識(shí)的蒙塵,希望我能幫你理清知識(shí)的脈絡(luò),希望未來技術(shù)之巔上有你也有我。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 本系列教程來自廖雪峰的官方網(wǎng)站,現(xiàn)在搬運(yùn)過來,目的幫助自己和小白學(xué)習(xí)收藏!附贈(zèng):常用git命令清單 目錄 前言 創(chuàng)...
    Blizzard_liu閱讀 1,201評(píng)論 0 4
  • 1. 安裝 Github 查看是否安裝git: $ git config --global user.name "...
    Albert_Sun閱讀 13,835評(píng)論 9 163
  • 一、色情業(yè)的發(fā)展 1、1839年銀版照相術(shù)--色情業(yè) 達(dá)蓋爾 2、人類第一部電影 之后兩年就播出了一部裸體片 3、...
    laoqin閱讀 440評(píng)論 0 0
  • 《秋雨》濕透了張愛玲的生命 前不久,偶然...
    梁右閱讀 2,048評(píng)論 0 2
  • 轟! 當(dāng)牧塵穿過那扭曲的空間時(shí),他能夠清晰的感覺到前方的光線瞬間暗沉了下來,緊接著,轟隆隆的雷鳴聲,鋪天蓋地的響徹...
    混沌天書閱讀 145評(píng)論 0 0

友情鏈接更多精彩內(nèi)容