什么是Git分支?
看看git官網(wǎng)是怎么說的插佛,有人把 Git 的分支模型稱為它的“必殺技特性”杠巡,也正因?yàn)檫@一特性,使得 Git 從眾多版本控制系統(tǒng)中脫穎而出......吧啦吧啦說了一大堆雇寇,看得是一臉懵逼氢拥。
簡(jiǎn)單的說,分支就是火影忍者中的影分身之術(shù)锨侯,可以從本體分出好多個(gè)分身兄一,本體可以看做是主干,分身可以看做為主干的分支识腿,能夠同時(shí)獨(dú)立的工作出革,也可以在需要的時(shí)候合為一體。不知道火影忍者的渡讼,可以參考孫悟空骂束,他也會(huì)分身。
這只是簡(jiǎn)單的打個(gè)比方成箫,很多版本控制系統(tǒng)支持分支展箱,唯獨(dú) git 的分支模型獨(dú)領(lǐng)風(fēng)騷,那么 git 是的分支是如何實(shí)現(xiàn)的蹬昌?
接下來(lái)會(huì)用圖文并茂(文字也請(qǐng)認(rèn)真看混驰,讓自己有個(gè)印象)的方式來(lái)幫你理解git分支以及它的強(qiáng)大之處。
git 保存數(shù)據(jù)皂贩,保存的不是文件的變化或者差異栖榨,而是一系列不同時(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ì)象。
為了更加形象地說明鞍陨,我們假設(shè)現(xiàn)在有一個(gè)工作目錄沟饥,里面包含了三個(gè)將要被暫存和提交的文件。 暫存操作會(huì)為每一個(gè)文件計(jì)算校驗(yàn)和(使用SHA-1 哈希算法)湾戳,然后會(huì)把當(dāng)前版本的文件快照保存到 git 倉(cāng)庫(kù)中 (git 使用 blob 對(duì)象來(lái)保存它們),最終將校驗(yàn)和加入到暫存區(qū)域等待提交:
$ git add README test.rb LICENSE
$ git commit -m 'The initial commit of my project'
當(dāng)使用 git commit
進(jìn)行提交操作時(shí)广料,git 會(huì)先計(jì)算每一個(gè)子目錄(本例中只有項(xiàng)目根目錄)的校驗(yàn)和砾脑, 然后在 git 倉(cāng)庫(kù)中這些校驗(yàn)和保存為樹對(duì)象。隨后艾杏,git 便會(huì)創(chuàng)建一個(gè)提交對(duì)象韧衣, 它除了包含上面提到的那些信息外,還包含指向這個(gè)樹對(duì)象(項(xiàng)目根目錄)的指針购桑。 如此一來(lái)畅铭,git 就可以在需要的時(shí)候重現(xiàn)此次保存的快照。
別方勃蜘,現(xiàn)在我們來(lái)看看首次提交對(duì)象及其樹結(jié)構(gòu):
現(xiàn)在硕噩,git 倉(cāng)庫(kù)中有五個(gè)對(duì)象:三個(gè) blob 對(duì)象(保存著文件快照)、一個(gè) 樹 對(duì)象 (記錄著目錄結(jié)構(gòu)和 blob 對(duì)象索引)以及一個(gè) 提交 對(duì)象(包含著指向樹對(duì)象的指針和所有提交信息)缭贡。
注:這里為什么是三個(gè)blob對(duì)象炉擅,是因?yàn)榍懊嫖覀冇玫?code>git add README test.rb LICENSE命令暫存了這三個(gè)文件,隨后就提交了阳惹,所以產(chǎn)生了三個(gè)blob對(duì)象谍失。
做些修改后再次提交,那么這次產(chǎn)生的提交對(duì)象會(huì)包含一個(gè)指向上次提交對(duì)象(父對(duì)象)的指針莹汤。
git 的分支快鱼,其實(shí)本質(zhì)上僅僅是指向提交對(duì)象的可變指針。 git 的默認(rèn)分支名字是 master
纲岭。 在多次提交操作之后抹竹,你其實(shí)已經(jīng)有一個(gè)指向最后那個(gè)提交對(duì)象的 master
分支。 master
分支會(huì)在每次提交時(shí)自動(dòng)向前移動(dòng)止潮。
注:git 的 master
分支并不是一個(gè)特殊分支柒莉。 它就跟其它分支完全沒有區(qū)別。 之所以幾乎每一個(gè)倉(cāng)庫(kù)都有 master 分支沽翔,是因?yàn)?git init
命令默認(rèn)創(chuàng)建它兢孝,并且大多數(shù)人都懶得去改動(dòng)它窿凤。
今年,也就是2020年10月1日起跨蟹,所有在GitHub上新建的倉(cāng)庫(kù)默認(rèn)名字都將改為main
而不是master
雳殊。此前的所有倉(cāng)庫(kù)不受影響
分支創(chuàng)建
git 創(chuàng)建一個(gè)分支,只是為你創(chuàng)建了一個(gè)可以移動(dòng)的新的指針窗轩,比如夯秃,創(chuàng)建一個(gè)testing分支,你需要使用git branch
命令:
$ git branch testing
通常我們會(huì)在創(chuàng)建一個(gè)新分支后立即切換過去痢艺,這可以用 git checkout -b <newbranchname>
一條命令搞定仓洼。
這會(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)切換到新分支中去环础。
你可以簡(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 formats to the central interface
34ac2 Fixed bug #1328 - stack overflow under certain conditions
98ca9 The 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
分支了框都。
這樣的實(shí)現(xiàn)方式有什么好處搬素?當(dāng)再次進(jìn)行提交時(shí):
testing
分支(HEAD
所在分支)隨著提交操作自動(dòng)向前移動(dòng),但是master
分支卻沒有移動(dòng)魏保,他仍然指向運(yùn)行git checkout
時(shí)所指的對(duì)象熬尺。再切回master
分支看看:
$ git checkout master
可以看到HEAD
指回了master
分支。這條命令做了兩件事谓罗,一是使HEAD
指回master
分支粱哼,二是將工作目錄恢復(fù)成master
分支所指向的快照內(nèi)容。也就是說檩咱,現(xiàn)在做修改的話揭措,項(xiàng)目將始于一個(gè)較舊的版本胯舷。 本質(zhì)上來(lái)講,這就是忽略 testing
分支所做的修改绊含,以便于向另一個(gè)方向進(jìn)行開發(fā)桑嘶。
此時(shí)HEAD
指向master
分支,如果此時(shí)進(jìn)行修改并提交躬充,而之前在testing
分支上也進(jìn)行了修改并提交逃顶,兩次改動(dòng)針對(duì)的是不同分支,此時(shí)提交歷史就產(chǎn)生了分叉:你可以在不同的分支來(lái)回切換進(jìn)行工作充甚,在有需要的時(shí)候?qū)⑺麄兒喜⑵饋?lái)以政,這些工作,需要的命令只有branch
伴找、checkout
盈蛮、commit
。
你可以簡(jiǎn)單地使用 git log
命令查看分叉歷史疆瑰。 運(yùn)行 git log --oneline --decorate --graph --all
,它會(huì)輸出你的提交歷史昙啄、各個(gè)分支的指向以及項(xiàng)目的分支分叉情況穆役。
由于 git 的分支實(shí)質(zhì)上僅是包含所指對(duì)象校驗(yàn)和(長(zhǎng)度為 40 的 SHA-1 值字符串)的文件,所以它的創(chuàng)建和銷毀都異常高效梳凛。 創(chuàng)建一個(gè)新分支就相當(dāng)于往一個(gè)文件中寫入 41 個(gè)字節(jié)(40 個(gè)字符和 1 個(gè)換行符)耿币,如此的簡(jiǎn)單能不快嗎?
這與過去大多數(shù)版本控制系統(tǒng)形成了鮮明的對(duì)比韧拒,它們?cè)趧?chuàng)建分支時(shí)淹接,將所有的項(xiàng)目文件都復(fù)制一遍,并保存到一個(gè)特定的目錄叛溢。 完成這樣繁瑣的過程通常需要好幾秒鐘塑悼,有時(shí)甚至需要好幾分鐘。所需時(shí)間的長(zhǎng)短楷掉,完全取決于項(xiàng)目的規(guī)模厢蒜。 而在 git 中,任何規(guī)模的項(xiàng)目都能在瞬間創(chuàng)建新分支烹植。 同時(shí)斑鸦,由于每次提交都會(huì)記錄父對(duì)象,所以尋找恰當(dāng)?shù)暮喜⒒A(chǔ)(即共同祖先)也是同樣的簡(jiǎn)單和高效草雕。 這些高效的特性使得 Git 鼓勵(lì)開發(fā)人員頻繁地創(chuàng)建和使用分支巷屿。
分支的合并
在實(shí)際工作中,可能會(huì)有這樣的經(jīng)歷:
- 開發(fā)某個(gè)網(wǎng)站墩虹。
- 為實(shí)現(xiàn)某個(gè)新的用戶需求嘱巾,創(chuàng)建一個(gè)分支憨琳。
- 在這個(gè)分支上開展工作。
這時(shí)浓冒,出現(xiàn)一個(gè)很嚴(yán)重的bug需要緊急修補(bǔ)栽渴。那么這時(shí)候的流程應(yīng)該是這樣的:
- 切換到你的線上分支(production branch)。
- 為這個(gè)緊急任務(wù)新建一個(gè)分支稳懒,并在其中修復(fù)它闲擦。
- 在測(cè)試通過之后,切換回線上分支场梆,然后合并這個(gè)修補(bǔ)分支墅冷,最后將改動(dòng)推送到線上分支。
- 切換回你最初工作的分支上或油,繼續(xù)工作寞忿。
假設(shè)在master
分支上已經(jīng)有了一些提交:
這個(gè)時(shí)候,領(lǐng)導(dǎo)要你解決公司的bug追蹤系統(tǒng)上的#53號(hào)問題顶岸,這個(gè)時(shí)候就需要新建一個(gè)分支并同時(shí)切換到那個(gè)分支上去腔彰,你可以使用帶有 -b
參數(shù)的 git checkout
命令:
# 這里我們就使用我們的learngit本地倉(cāng)庫(kù) 進(jìn)行演示
# 先確認(rèn)工作區(qū)是干凈的,然后新建分支
$ git checkout -b iss53
Switched to a new branch 'iss53'
它是這兩條命令的簡(jiǎn)寫:
$ git branch iss53
$ git checkout iss53
這時(shí)已經(jīng)創(chuàng)建了一個(gè)新的分支iss53
你繼續(xù)在 iss53
分支上工作辖佣,并且做了一些提交霹抛。 在此過程中,iss53
分支在不斷的向前推進(jìn)卷谈,因?yàn)槟阋呀?jīng)檢出到該分支 (也就是說杯拐,你的 HEAD
指針指向了 iss53
分支)
# 修改readme.txt文件,然后提交
$ git commit -a -m 'modify readme.txt add content study git branch'
[iss53 0526e3d] modify readme.txt add content study git branch
1 file changed, 2 insertions(+), 1 deletion(-)
分支隨著工作的進(jìn)展向前推進(jìn):
這時(shí)世蔗,master
分支上又有一個(gè)緊急問題需要你來(lái)解決端逼,那么這個(gè)時(shí)候,你可以直接切換回master
分支解決問題即可污淋,這里有一個(gè)前提:在你切換分支之前顶滩,保持好一個(gè)干凈的狀態(tài),也就是確保你的工作目錄和暫存區(qū)里沒有未被提交的修改寸爆,避免和master
分支產(chǎn)生沖突導(dǎo)致git不讓你切換分支诲祸。切換至master
:
$ git checkout master
Switched to branch 'master'
切換回master
分支之后,git會(huì)重置你的工作目錄而昨,使其恢復(fù)到你在這個(gè)分支最后一次提交的狀態(tài)救氯,當(dāng)然你不用擔(dān)心之前在iss53
分支上的修改會(huì)消失,因?yàn)椴煌种Р粫?huì)相互影響歌憨。接下來(lái)着憨,要修復(fù)master
分支上的問題,只需要再建立一個(gè)分支务嫡,在該分支上解決問題即可:
$ git checkout -b hotfix
Switched to a new branch 'hotfix'
# 修改 google.txt甲抖,并提交
$ git commit -a -m 'modify google.txt add content google voice'
[hotfix d6c79ae] modify google.txt add content google voice
1 file changed, 2 insertions(+), 1 deletion(-)
可以在hotfix
分支上進(jìn)行測(cè)試漆改,確保自己的修改無(wú)誤,然后將hotfix
分支合并到master
分支部署到線上准谚。合并分支:
$ git checkout master
Switched to branch 'master'
$ git merge hotfix
Updating 6680bd4..d6c79ae
Fast-forward
google.txt | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
上述的合并信息中有 “快進(jìn)(Fast-forward)”這個(gè)詞挫剑。由于你想要合并的分支 hotfix
所指向的提交 C4
是你所在的提交 C2
的直接后繼, 因此 Git 會(huì)直接將指針向前移動(dòng)柱衔。換句話說樊破,當(dāng)你試圖合并兩個(gè)分支時(shí), 如果順著一個(gè)分支走下去能夠到達(dá)另一個(gè)分支唆铐,那么 Git 在合并兩者的時(shí)候哲戚, 只會(huì)簡(jiǎn)單的將指針向前推進(jìn)(指針右移),因?yàn)檫@種情況下的合并操作沒有需要解決的分歧——這就叫做 “快進(jìn)(fast-forward)”艾岂。
master
被快進(jìn)到 hotfix
:
合并完成之后顺少,hotfix
分支就可以刪除了,已經(jīng)不需要它了( master
分支和它已經(jīng)指向了同一個(gè)位置)王浴,可以使用帶 -d
選項(xiàng)的 git branch
命令來(lái)刪除分支:
# 如果未合并蹲嚣,但是想要強(qiáng)制刪除分支祠斧,-d換成-D即可
$ git branch -d hotfix
Deleted branch hotfix (was d6c79ae).
現(xiàn)在可以回到iss53
分支繼續(xù)之前的工作:
$ git checkout iss53
Switched to branch 'iss53'
# 再次修改readme.txt 文件饥侵,并提交
$ git commit -a -m 'modify readme.txt add content merge branch'
[iss53 4418bd6] modify readme.txt add content merge branch
1 file changed, 1 insertion(+), 1 deletion(-)
假設(shè)你已經(jīng)修正了 #53 問題辣之,并且打算將你的工作合并入 master
分支俯萎。 這和之前合并 hotfix
分支所做的工作差不多琳轿。 你只需要檢出到你想合并入的分支户侥,然后運(yùn)行 git merge
命令:
$ git checkout master
$ git merge iss53 # 如果按照我的步驟來(lái)剔猿,肯定會(huì)出現(xiàn)下面的窗口
Merge made by the 'recursive' strategy.
readme.txt | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
這和之前合并 hotfix
分支的時(shí)候看起來(lái)有一點(diǎn)不一樣癞松。 在這種情況下爽撒,你的開發(fā)歷史從一個(gè)更早的地方開始分叉開來(lái)(diverged)。 因?yàn)椋?code>master 分支所在提交并不是 iss53
分支所在提交的直接祖先响蓉,git 不得不做一些額外的工作硕勿。 出現(xiàn)這種情況的時(shí)候,git 會(huì)使用兩個(gè)分支的末端所指的快照(C4
和 C5
)以及這兩個(gè)分支的公共祖先(C2
)枫甲,做一個(gè)簡(jiǎn)單的三方合并源武。
和之前將分支指針向前推進(jìn)所不同的是,git 將此次三方合并的結(jié)果做了一個(gè)新的快照并且自動(dòng)創(chuàng)建一個(gè)新的提交指向它想幻。 這個(gè)被稱作一次合并提交粱栖,它的特別之處在于他有不止一個(gè)父提交。
既然你的修改已經(jīng)合并進(jìn)來(lái)了脏毯,就不再需要 iss53
分支了闹究。 可以在任務(wù)追蹤系統(tǒng)中關(guān)閉此項(xiàng)任務(wù),并刪除這個(gè)分支食店。這里暫時(shí)不刪除渣淤。
合并分支遇到?jīng)_突
現(xiàn)在我們?cè)俅蝿?chuàng)建一個(gè)分支赏寇,對(duì)application.txt
文件進(jìn)行修改:
$ git checkout -b hotfix
Switched to a new branch 'hotfix'
# 對(duì)application.txt文件進(jìn)行修改并提交
$ git commit -a -m 'modify application.txt add android app'
[hotfix 92f4c86] modify application.txt add android app
1 file changed, 2 insertions(+), 1 deletion(-)
# 切換回master分支,合并hotfix分支
$ git checkout master
Switched to branch 'master'
$ git merge hotfix
Updating 110aa9d..92f4c86
Fast-forward
application.txt | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
切換到iss53
分支价认,同樣對(duì)application.txt
文件進(jìn)行修改:
$ git checkout iss53
Switched to branch 'iss53'
# 對(duì)application.txt文件進(jìn)行修改并提交
$ git commit -a -m 'modify application.txt add iphone'
[iss53 cb202c2] modify application.txt add iphone
1 file changed, 2 insertions(+), 1 deletion(-)
$ git checkout master
Switched to branch 'master'
這個(gè)時(shí)候把iss53
合并到master
分支嗅定,就會(huì)產(chǎn)生合并沖突:
$ git merge iss53
Auto-merging application.txt
CONFLICT (content): Merge conflict in application.txt
Recorded preimage for 'application.txt'
Automatic merge failed; fix conflicts and then commit the result.
此時(shí) git 做了合并,但是沒有自動(dòng)地創(chuàng)建一個(gè)新的合并提交用踩。 git 會(huì)暫停下來(lái)渠退,等待你去解決合并產(chǎn)生的沖突。 你可以在合并沖突后的任意時(shí)刻使用 git status
命令來(lái)查看那些因包含合并沖突而處于未合并(unmerged)狀態(tài)的文件:
$ 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 modified: application.txt
no changes added to commit (use "git add" and/or "git commit -a")
任何因包含合并沖突而有待解決的文件捶箱,都會(huì)以未合并狀態(tài)標(biāo)識(shí)出來(lái)智什。 git 會(huì)在有沖突的文件中加入標(biāo)準(zhǔn)的沖突解決標(biāo)記,這樣你可以打開這些包含沖突的文件然后手動(dòng)解決沖突丁屎。 出現(xiàn)沖突的文件會(huì)包含一些特殊區(qū)段荠锭,看起來(lái)像下面這個(gè)樣子(打開application.txt
文件):
application
<<<<<<< HEAD
android app
=======
iphone
>>>>>>> iss53
這表示 HEAD
所指示的版本(也就是你的 master
分支,因?yàn)槟阍谶\(yùn)行 merge 命令的時(shí)候已經(jīng)切換到了這個(gè)分支)在這個(gè)區(qū)段的上半部分(=======
的上半部分)晨川,而 iss53
分支所指示的版本在 =======
的下半部分证九。 為了解決沖突,你必須選擇使用由 =======
分割的兩部分中的一個(gè)共虑,或者你也可以自行合并這些內(nèi)容愧怜。 例如,你可以通過把這段內(nèi)容修改成下面的樣子來(lái)解決沖突:
application
android app
iphone
這里解決沖突的方案是保留全部?jī)?nèi)容(當(dāng)然實(shí)際工作中妈拌,要看自己的取舍拥坛,這里只是舉個(gè)例子),并且 <<<<<<<
, =======
, 和 >>>>>>>
這些行被完全刪除了尘分。 在解決了所有文件里的沖突之后猜惋,對(duì)每個(gè)文件使用 git add
命令來(lái)將其標(biāo)記為沖突已解決。 一旦暫存這些原本有沖突的文件培愁,git 就會(huì)將它們標(biāo)記為沖突已解決著摔。
如果你想使用圖形化工具來(lái)解決沖突,你可以運(yùn)行 git mergetool
定续,該命令會(huì)為你啟動(dòng)一個(gè)合適的可視化合并工具(默認(rèn)的很丑谍咆,可以到網(wǎng)上找一個(gè)),并帶領(lǐng)你一步一步解決這些沖突私股。
沖突解決之后摹察,git會(huì)暫存那些文件以表明沖突已解決: 你可以再次運(yùn)行 git status
來(lái)確認(rèn)所有的合并沖突都已被解決:
$ git status
On branch master
All conflicts fixed but you are still merging.
(use "git commit" to conclude merge)
Changes to be committed:
modified: application.txt
如果覺得沒有問題了,并且所有有沖突的文件都已經(jīng)暫存了倡鲸,這時(shí)可以輸入 git commit
來(lái)完成合并提交供嚎。 默認(rèn)情況下提交信息看起來(lái)像下面這個(gè)樣子:
# 沒有加 -m 的情況下
Merge branch 'iss53'
# Conflicts:
# application.txt
#
# It looks like you may be committing a merge.
# If this is not correct, please remove the file
# .git/MERGE_HEAD
# and try again.
# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
#
# On branch master
# All conflicts fixed but you are still merging.
#
# Changes to be committed:
# modified: application.txt
#
如果覺得上述的信息不夠充分,不能完全體現(xiàn)分支合并的過程,你可以修改上述信息查坪, 添加一些細(xì)節(jié)給未來(lái)檢視這個(gè)合并的讀者一些幫助寸宏,告訴他們是如何解決合并沖突的,以及理由是什么
Git分支管理
之前已經(jīng)創(chuàng)建偿曙、合并氮凝、刪除了一些分支,現(xiàn)在來(lái)看看一些常用的分支管理工具望忆。
git branch
命令不只是可以創(chuàng)建與刪除分支罩阵。 如果不加任何參數(shù)運(yùn)行它,會(huì)得到當(dāng)前所有分支的一個(gè)列表:
$ git branch
hotfix
iss53
* master
master
分支前的 *
字符:它代表現(xiàn)在檢出的那一個(gè)分支(也就是說启摄,當(dāng)前 HEAD
指針?biāo)赶虻姆种В?這意味著如果在這時(shí)候提交稿壁,master
分支將會(huì)隨著新的工作向前移動(dòng)。 如果需要查看每一個(gè)分支的最后一次提交歉备,可以運(yùn)行 git branch -v
命令:
$ git branch -v
hotfix 92f4c86 modify application.txt add android app
iss53 cb202c2 modify application.txt add iphone
* master 27bc1f2 Merge branch 'iss53'
--merged
與 --no-merged
這兩個(gè)有用的選項(xiàng)可以過濾這個(gè)列表中已經(jīng)合并或尚未合并到當(dāng)前分支的分支傅是。 如果要查看哪些分支已經(jīng)合并到當(dāng)前分支,可以運(yùn)行 git branch --merged
:
$ git branch --merged
hotfix
iss53
* master
因?yàn)橹耙呀?jīng)合并了 iss53
分支和hotfix
分支蕾羊,所以現(xiàn)在看到它們?cè)诹斜碇小?在這個(gè)列表中分支名字前沒有 *
號(hào)的分支通承剩可以使用 git branch -d
刪除掉;你已經(jīng)將它們的工作整合到了另一個(gè)分支龟再,所以并不會(huì)失去任何東西书闸。
如果存在未合并的分支,而你又想刪除它利凑,這個(gè)時(shí)候可以使用git branch -D <branchname>
命令強(qiáng)制刪除浆劲。
Git遠(yuǎn)程分支
先來(lái)了解兩個(gè)名詞:
- 遠(yuǎn)程引用
- 遠(yuǎn)程跟蹤分支
遠(yuǎn)程引用:
遠(yuǎn)程引用是對(duì)遠(yuǎn)程倉(cāng)庫(kù)的引用(指針),包括分支哀澈、標(biāo)簽等等牌借。可以通過 git ls-remote <remote>
來(lái)顯式地獲得遠(yuǎn)程引用的完整列表日丹, 或者通過 git remote show <remote>
獲得遠(yuǎn)程分支的更多信息走哺。 然而蚯嫌,更常見的做法是利用遠(yuǎn)程跟蹤分支哲虾。
遠(yuǎn)程跟蹤分支:
遠(yuǎn)程跟蹤分支是遠(yuǎn)程分支狀態(tài)的引用。它們是你無(wú)法移動(dòng)的本地引用择示。一旦你進(jìn)行了網(wǎng)絡(luò)通信束凑, Git 就會(huì)為你移動(dòng)它們以精確反映遠(yuǎn)程倉(cāng)庫(kù)的狀態(tài)。請(qǐng)將它們看做書簽栅盲, 這樣可以提醒你該分支在遠(yuǎn)程倉(cāng)庫(kù)中的位置就是你最后一次連接到它們的位置汪诉。
遠(yuǎn)程跟蹤分支 <remote>/<branch>
的形式命名。例如,如果你想要看你最后一次與遠(yuǎn)程倉(cāng)庫(kù) origin
通信時(shí) master
分支的狀態(tài)扒寄,你可以查看 origin/master
分支鱼鼓。 你與同事合作解決一個(gè)問題并且他們推送了一個(gè) iss53
分支,你可能有自己的本地 iss53
分支该编, 然而在服務(wù)器上的分支會(huì)以 origin/iss53
來(lái)表示迄本。簡(jiǎn)單來(lái)說就是本地分支沒有倉(cāng)庫(kù)名,遠(yuǎn)程分支是帶有倉(cāng)庫(kù)名的课竣。
我們?cè)賮?lái)看一個(gè)例子嘉赎,假設(shè)你的網(wǎng)絡(luò)里有一個(gè)在 git.ourcompany.com
的 git 服務(wù)器。 如果你從這里克隆于樟,git 的 clone
命令會(huì)為你自動(dòng)將其命名為 origin
公条,拉取它的所有數(shù)據(jù), 創(chuàng)建一個(gè)指向它的 master
分支的指針迂曲,并且在本地將其命名為 origin/master
靶橱。 git 也會(huì)給你一個(gè)與 origin 的 master
分支在指向同一個(gè)地方的本地 master
分支,這樣你就有工作的基礎(chǔ)路捧。
如果你在本地的 master
分支做了一些工作抓韩,在同一段時(shí)間內(nèi)有其他人推送提交到 git.ourcompany.com
并且更新了它的 master
分支,這就是說你們的提交歷史已走向不同的方向鬓长。 即便這樣谒拴,只要你保持不與 origin
服務(wù)器連接(并拉取數(shù)據(jù)),你的 origin/master
指針就不會(huì)移動(dòng)涉波。
如果要與給定的遠(yuǎn)程倉(cāng)庫(kù)同步數(shù)據(jù)英上,運(yùn)行 git fetch <remote>
命令(在本例中為 git fetch origin
)。 這個(gè)命令查找 “origin” 是哪一個(gè)服務(wù)器(在本例中啤覆,它是 git.ourcompany.com
)苍日, 從中抓取本地沒有的數(shù)據(jù),并且更新本地?cái)?shù)據(jù)庫(kù)窗声,移動(dòng) origin/master
指針到更新之后的位置相恃。
為了演示有多個(gè)遠(yuǎn)程倉(cāng)庫(kù)與遠(yuǎn)程分支的情況,假定你有另一個(gè)內(nèi)部 git 服務(wù)器笨觅,僅服務(wù)于你的某個(gè)敏捷開發(fā)團(tuán)隊(duì)拦耐。 這個(gè)服務(wù)器位于 git.team1.ourcompany.com
。 你可以運(yùn)行 git remote add
命令添加一個(gè)新的遠(yuǎn)程倉(cāng)庫(kù)引用到當(dāng)前的項(xiàng)目见剩。 將這個(gè)遠(yuǎn)程倉(cāng)庫(kù)命名為 teamone
杀糯,作為完整 URL 的縮寫。
現(xiàn)在苍苞,可以運(yùn)行 git fetch teamone
來(lái)抓取遠(yuǎn)程倉(cāng)庫(kù) teamone
有而本地沒有的數(shù)據(jù)固翰。 因?yàn)槟桥_(tái)服務(wù)器上現(xiàn)有的數(shù)據(jù)是 origin
服務(wù)器上的一個(gè)子集, 所以 Git 并不會(huì)抓取數(shù)據(jù)而是會(huì)設(shè)置遠(yuǎn)程跟蹤分支 teamone/master
指向 teamone
的 master
分支。
推送
在推送本地分支到遠(yuǎn)程倉(cāng)庫(kù)前骂际,我們先把遠(yuǎn)程倉(cāng)庫(kù)克隆一份到本地備用:
$ git clone https://github.com/tiangoubot/learngit.git learngit2
把本地分支推送到遠(yuǎn)程倉(cāng)庫(kù)疗琉,可以使用git push <remote> <branch>
:
$ git push origin iss53
Enumerating objects: 12, done.
Counting objects: 100% (12/12), done.
Delta compression using up to 4 threads
Compressing objects: 100% (8/8), done.
Writing objects: 100% (9/9), 851 bytes | 283.00 KiB/s, done.
Total 9 (delta 5), reused 0 (delta 0), pack-reused 0
remote: Resolving deltas: 100% (5/5), completed with 2 local objects.
remote:
remote: Create a pull request for 'iss53' on GitHub by visiting:
remote: https://github.com/tiangoubot/learngit/pull/new/iss53
remote:
To https://github.com/tiangoubot/learngit.git
* [new branch] iss53 -> iss53
這里有些工作被簡(jiǎn)化了。 git 自動(dòng)將 iss53
分支名字展開為 refs/heads/iss53:refs/heads/iss53
歉铝, 那意味著没炒,“推送本地的 iss53
分支來(lái)更新遠(yuǎn)程倉(cāng)庫(kù)上的 iss53
分支》赶罚” 你也可以運(yùn)行 git push origin iss53:iss53
送火, 它會(huì)做同樣的事——也就是說“推送本地的 iss53
分支,將其作為遠(yuǎn)程倉(cāng)庫(kù)的 iss53
分支” 可以通過這種格式來(lái)推送本地分支到一個(gè)命名不相同的遠(yuǎn)程分支先匪。 如果并不想讓遠(yuǎn)程倉(cāng)庫(kù)上的分支叫做 iss53
种吸,可以運(yùn)行 git push origin iss53:devTest
來(lái)將本地的 iss53
分支推送到遠(yuǎn)程倉(cāng)庫(kù)上的 devTest
分支。
下一次其他協(xié)作者從服務(wù)器上抓取數(shù)據(jù)時(shí)呀非,他們會(huì)在本地生成一個(gè)遠(yuǎn)程分支 origin/iss53
坚俗,指向服務(wù)器的 iss53
分支的引用:
# 剛才我們把iss53分支推送到遠(yuǎn)程倉(cāng)庫(kù)了,現(xiàn)在岸裙,我們先進(jìn)入剛才克隆下來(lái)備份的倉(cāng)庫(kù)猖败,在根目錄下打開git bash,然后執(zhí)行
$ git fetch origin
remote: Enumerating objects: 27, done.
remote: Counting objects: 100% (27/27), done.
remote: Compressing objects: 100% (9/9), done.
remote: Total 23 (delta 9), reused 23 (delta 9), pack-reused 0
Unpacking objects: 100% (23/23), 2.12 KiB | 1024 bytes/s, done.
From https://github.com/tiangoubot/learngit
* [new branch] iss53 -> origin/iss53
要特別注意的一點(diǎn)是當(dāng)抓取到新的遠(yuǎn)程跟蹤分支時(shí)降允,本地不會(huì)自動(dòng)生成一份可編輯的副本(拷貝)恩闻。 換一句話說,這種情況下剧董,不會(huì)有一個(gè)新的 iss53
分支——只有一個(gè)不可以修改的 origin/iss53
指針幢尚。
$ git branch # 可以看到并沒有iss53分支
* master
你可以運(yùn)行 git merge origin/iss53
將這些工作合并到當(dāng)前所在的分支。 如果想要在自己的 iss53
分支上工作翅楼,可以將其建立在遠(yuǎn)程跟蹤分支之上:
$ git checkout -b iss53 origin/iss53
Switched to a new branch 'iss53'
Branch 'iss53' set up to track remote branch 'iss53' from 'origin'.
這會(huì)給你一個(gè)用于工作的本地分支尉剩,并且起點(diǎn)位于 origin/iss53
。
跟蹤分支
從一個(gè)遠(yuǎn)程跟蹤分支檢出一個(gè)本地分支會(huì)自動(dòng)創(chuàng)建“跟蹤分支”(它跟蹤的分支叫做“上游分支(例如: origin/iss53
)”)毅臊。 跟蹤分支是與遠(yuǎn)程分支有直接關(guān)系的本地分支理茎。 如果在一個(gè)跟蹤分支上輸入 git pull
,Git 能自動(dòng)地識(shí)別去哪個(gè)服務(wù)器上抓取管嬉、合并到哪個(gè)分支皂林。
當(dāng)克隆一個(gè)倉(cāng)庫(kù)時(shí),它通常會(huì)自動(dòng)地創(chuàng)建一個(gè)跟蹤 origin/master
的 master
分支宠蚂。 然而式撼,如果你愿意的話可以設(shè)置其他的跟蹤分支童社,或是一個(gè)在其他遠(yuǎn)程倉(cāng)庫(kù)上的跟蹤分支求厕,又或者不跟蹤 origin/master
分支。 最簡(jiǎn)單的實(shí)例就是像之前看到的那樣,運(yùn)行 git checkout -b <branch> <remote>/<branch>
呀癣。 這是一個(gè)十分常用的操作所以 git 提供了 --track
快捷方式:
先在GitHub上創(chuàng)建一個(gè)新的分支:
$ git fetch origin # 從遠(yuǎn)程倉(cāng)庫(kù)拉取數(shù)據(jù)
From https://github.com/tiangoubot/learngit
* [new branch] serverfix -> origin/serverfix
# 設(shè)置跟蹤分支
$ git checkout --track origin/serverfix
Switched to a new branch 'serverfix'
Branch 'serverfix' set up to track remote branch 'serverfix' from 'origin'.
由于這個(gè)操作太常用了美浦,該捷徑本身還有一個(gè)捷徑。 如果你嘗試檢出的分支 (a) 不存在且 (b) 剛好只有一個(gè)名字與之匹配的遠(yuǎn)程分支项栏,那么 git 就會(huì)為你創(chuàng)建一個(gè)跟蹤分支:
$ git checkout serverfix
Branch serverfix set up to track remote branch serverfix from origin.
Switched to a new branch 'serverfix'
如果想要將本地分支與遠(yuǎn)程分支設(shè)置為不同的名字浦辨,你可以輕松地使用上一個(gè)命令增加一個(gè)不同名字的本地分支:
$ git checkout -b sf origin/serverfix
Branch sf set up to track remote branch serverfix from origin.
Switched to a new branch 'sf'
此時(shí),本地分支 sf
會(huì)自動(dòng)從 origin/serverfix
拉取沼沈。
設(shè)置已有的本地分支跟蹤一個(gè)剛剛拉取下來(lái)的遠(yuǎn)程分支流酬,或者想要修改正在跟蹤的上游分支, 你可以在任意時(shí)間使用 -u
或 --set-upstream-to
選項(xiàng)運(yùn)行 git branch
來(lái)顯式地設(shè)置列另。
$ git branch -u origin/serverfix
Branch serverfix set up to track remote branch serverfix from origin.
上游快捷方式
當(dāng)設(shè)置好跟蹤分支后芽腾,可以通過簡(jiǎn)寫@{upstream}
或@{u}
來(lái)引用它的上游分支。 所以在master
分支時(shí)并且它正在跟蹤origin/master
時(shí)页衙,可以使用git merge @{u}
命令來(lái)取代git merge origin/master
摊滔。
如果想要查看設(shè)置的所有跟蹤分支,可以使用 git branch
的 -vv
選項(xiàng)店乐。 這會(huì)將所有的本地分支列出來(lái)并且包含更多的信息艰躺,如每一個(gè)分支正在跟蹤哪個(gè)遠(yuǎn)程分支與本地分支是否是領(lǐng)先、落后或是都有眨八。
假如你看到了如下信息:
$ git branch -vv
iss53 7e424c3 [origin/iss53: ahead 2] forgot the brackets
master 1ae2a45 [origin/master] deploying index fix
* serverfix f8674d9 [teamone/server-fix-good: ahead 3, behind 1] this should do it
testing 5ea463a trying something new
這里可以看到 iss53
分支正在跟蹤 origin/iss53
并且 “ahead” 是 2腺兴,意味著本地有兩個(gè)提交還沒有推送到服務(wù)器上。 也能看到 master
分支正在跟蹤 origin/master
分支并且是最新的廉侧。 接下來(lái)可以看到 serverfix
分支正在跟蹤 teamone
服務(wù)器上的 server-fix-good
分支并且領(lǐng)先 3 落后 1含长, 意味著服務(wù)器上有一次提交還沒有合并入本地,同時(shí)本地有三次提交還沒有推送伏穆。 最后看到 testing
分支并沒有跟蹤任何遠(yuǎn)程分支拘泞。
需要重點(diǎn)注意的一點(diǎn)是這些數(shù)字的值來(lái)自于你從每個(gè)服務(wù)器上最后一次抓取的數(shù)據(jù)。 這個(gè)命令并沒有連接服務(wù)器枕扫,它只會(huì)告訴你關(guān)于本地緩存的服務(wù)器數(shù)據(jù)陪腌。 如果想要統(tǒng)計(jì)最新的領(lǐng)先與落后數(shù)字,需要在運(yùn)行此命令前抓取所有的遠(yuǎn)程倉(cāng)庫(kù)烟瞧。 可以像這樣做:
$ git fetch --all; git branch -vv
拉取
當(dāng) git fetch
命令從服務(wù)器上抓取本地沒有的數(shù)據(jù)時(shí)诗鸭,它并不會(huì)修改工作目錄中的內(nèi)容。 它只會(huì)獲取數(shù)據(jù)然后讓你自己合并参滴。 然而强岸,有一個(gè)命令叫作 git pull
在大多數(shù)情況下它的含義是一個(gè) git fetch
緊接著一個(gè) git merge
命令。 如果有一個(gè)設(shè)置好的跟蹤分支砾赔,不管它是顯式地設(shè)置還是通過 clone
或 checkout
命令為你創(chuàng)建的蝌箍,git pull
都會(huì)查找當(dāng)前分支所跟蹤的服務(wù)器與分支青灼, 從服務(wù)器上抓取數(shù)據(jù)然后嘗試合并入那個(gè)跟蹤分支。
git pull
命令會(huì)將本地倉(cāng)庫(kù)代碼更新至遠(yuǎn)程倉(cāng)庫(kù)中最新的版本妓盲,簡(jiǎn)單粗暴杂拨。而 fetch
命令只是將本地倉(cāng)庫(kù)沒有而遠(yuǎn)程倉(cāng)庫(kù)有的代碼拉取到本地,你需要使用 merge
命令進(jìn)行合并悯衬,如果本地代碼與遠(yuǎn)程倉(cāng)庫(kù)的代碼有沖突弹沽,你需要解決沖突之后才能進(jìn)行合并,這樣可以避免一些莫名其妙的問題筋粗。
刪除遠(yuǎn)程分支
假如你想要?jiǎng)h除遠(yuǎn)程分支策橘,可以運(yùn)行帶有 --delete
選項(xiàng)的 git push
命令來(lái)刪除一個(gè)遠(yuǎn)程分支。 如果想要從服務(wù)器上刪除 serverfix
分支娜亿,運(yùn)行下面的命令:
$ git push origin --delete serverfix
To https://github.com/tiangoubot/learngit.git
- [deleted] serverfix
基本上這個(gè)命令做的只是從服務(wù)器上移除這個(gè)指針役纹。 git 服務(wù)器通常會(huì)保留數(shù)據(jù)一段時(shí)間直到垃圾回收運(yùn)行,所以如果不小心刪除掉了暇唾,通常是很容易恢復(fù)的促脉。
如果誤刪了遠(yuǎn)程分支,可以按照以下步驟恢復(fù):
-
查看reflog策州,找到最后一次commitid
$ git reflog --date=iso
reflog
是reference log的意思瘸味,也就是引用log,記錄HEAD在各個(gè)分支上的移動(dòng)軌跡够挂。選項(xiàng)--date=iso
旁仿,表示以標(biāo)準(zhǔn)時(shí)間格式展示。這里你肯定會(huì)問孽糖,為什么不用git log枯冈?git log是用來(lái)記錄當(dāng)前分支的commit log,分支都刪除了办悟,找不到commit log了尘奏。找到目標(biāo)分支最后一次的commitid,例如:
$ git reflog --date=iso 5ddc9cb (HEAD -> testing) HEAD@{2020-09-22 11:27:29 +0800}: checkout: moving from master to testing aaf35a8 (master) HEAD@{2020-09-22 11:23:15 +0800}: clone: from https://github.com/tiangoubot/gitTest.git
-
檢出分支
$ git checkout -b recovery_branch_name commitid
檢出分支病蛉,本地就有分支了炫加。
-
push到遠(yuǎn)程倉(cāng)庫(kù)
$ git push origin recovery_branch_name
至此,恢復(fù)完成铺然。
變基
在 Git 中整合來(lái)自不同分支的修改主要有兩種方法:merge
以及 rebase
俗孝。rebase
有人把它翻譯成“變基”。
變基的基本操作
之前的分支合并中有一個(gè)例子魄健,你會(huì)看到開發(fā)任務(wù)分叉到兩個(gè)不同的分支赋铝,又各自提交了更新:
之前介紹過,整合分支最容易的方法是 merge
命令沽瘦。 它會(huì)把兩個(gè)分支的最新快照(C3
和 C4
)以及二者最近的共同祖先(C2
)進(jìn)行三方合并革骨,合并的結(jié)果是生成一個(gè)新的快照(并提交):
另一種方法:提取在C4
中打的補(bǔ)丁和修改农尖,然后在 C3
的基礎(chǔ)上應(yīng)用一次。這個(gè)操作苛蒲,在git中就叫做變基(rebase)卤橄。你可以使用 rebase
命令將提交到某一分支上的所有修改都移到另一分支上绿满。
在這個(gè)例子中臂外,你可以切換到experiment
分支上,然后將它變基到master
分支上:
$ git checkout experiment
$ git rebase master
它的原理是首先找到這兩個(gè)分支(即當(dāng)前分支 experiment
喇颁、變基操作的目標(biāo)基底分支 master
) 的最近共同祖先 C2
漏健,然后對(duì)比當(dāng)前分支相對(duì)于該祖先的歷次提交,提取相應(yīng)的修改并存為臨時(shí)文件橘霎, 然后將當(dāng)前分支指向目標(biāo)基底 C3
, 最后以此將之前另存為臨時(shí)文件的修改依序應(yīng)用蔫浆。
現(xiàn)在回到 master
分支,進(jìn)行一次快進(jìn)合并姐叁。
$ git checkout master
$ git merge experiment
這兩種整合方法的最終結(jié)果沒有任何區(qū)別瓦盛,但是變基使得提交歷史更加整潔。merge
會(huì)保留更多的提交歷史外潜,所以查看提交歷史(使用git log --pretty=formt:"%h %s" --graph
命令可以看到分叉情況)原环,會(huì)看到很多的分叉,而使用rebase
处窥,提交歷史就只是一條直線嘱吗。
一般我們這樣做的目的是為了確保在向遠(yuǎn)程分支推送時(shí)能保持提交歷史的整潔——例如向某個(gè)其他人維護(hù)的項(xiàng)目貢獻(xiàn)代碼時(shí)。 在這種情況下滔驾,你首先在自己的分支里進(jìn)行開發(fā)谒麦,當(dāng)開發(fā)完成時(shí)你需要先將你的代碼變基到 origin/master
上,然后再向主項(xiàng)目提交修改哆致。 這樣的話绕德,該項(xiàng)目的維護(hù)者就不再需要進(jìn)行整合工作,只需要快進(jìn)合并便可摊阀。
請(qǐng)注意迁匠,無(wú)論是通過變基,還是通過三方合并驹溃,整合的最終結(jié)果所指向的快照始終是一樣的城丧,只不過提交歷史不同罷了。 變基是將一系列提交按照原有次序依次應(yīng)用到另一分支上豌鹤,而合并是把最終結(jié)果合在一起亡哄。
有趣的變基例子
假如你創(chuàng)建了一個(gè)分支 server
,為服務(wù)端添加了一些功能布疙,提交了 C3
和 C4
蚊惯。 然后從 C3
上創(chuàng)建了分支 client
愿卸,為客戶端添加了一些功能,提交了 C8
和 C9
截型。 最后趴荸,你回到 server
分支,又提交了 C10
宦焦。
你希望將 client
中的修改合并到主分支并發(fā)布发钝,但暫時(shí)并不想合并 server
中的修改, 因?yàn)樗鼈冞€需要經(jīng)過更全面的測(cè)試波闹。這時(shí)酝豪,你就可以使用 git rebase
命令的 --onto
選項(xiàng), 選中在 client
分支里但不在 server
分支里的修改(即 C8
和 C9
)精堕,將它們?cè)?master
分支上整合:
$ git rebase --onto master server client
以上命令的意思是:“取出 client
分支孵淘,找出它從 server
分支分歧之后的補(bǔ)丁, 然后把這些補(bǔ)丁在 master
分支上整合歹篓,讓 client
看起來(lái)像直接基于 master
修改一樣”瘫证。
現(xiàn)在可以快進(jìn)合并 master
分支了。(如圖快進(jìn)合并 master
分支庄撮,使之包含來(lái)自 client
分支的修改):
$ git checkout master
$ git merge client
接下來(lái)你決定將 server
分支中的修改也整合進(jìn)來(lái)背捌。 使用 git rebase <basebranch> <topicbranch>
命令可以直接將主題分支 (即本例中的 server
)變基到目標(biāo)分支(即 master
)上。 這樣做能省去你先切換到 server
分支重窟,再對(duì)其執(zhí)行變基命令的多個(gè)步驟载萌。
$ git rebase master server
如圖將 server
中的修改變基到 master
上 所示,server
中的代碼被“續(xù)”到了 master
后面巡扇。
然后就可以快進(jìn)合并主分支 master
了:
$ git checkout master
$ git merge server
# 合并后 client和server分支都不需要了扭仁,可以刪除
$ git branch -d client
$ git branch -d server
變基的風(fēng)險(xiǎn)
要使用變基,就要遵守一條準(zhǔn)則:只對(duì)尚未推送或分享給別人的本地修改執(zhí)行變基操作清理提交歷史厅翔,不對(duì)已推送至別處的提交執(zhí)行變基操作乖坠。
變基操作的實(shí)質(zhì)是丟棄一些現(xiàn)有的提交,然后相應(yīng)地新建一些內(nèi)容一樣但實(shí)際上不同的提交刀闷。 如果你已經(jīng)將提交推送至某個(gè)倉(cāng)庫(kù)熊泵,而其他人也已經(jīng)從該倉(cāng)庫(kù)拉取提交并進(jìn)行了后續(xù)工作,此時(shí)甸昏,如果你用 git rebase
命令重新整理了提交并再次推送顽分,你的同事因此將不得不再次將他們手頭的工作與你的提交進(jìn)行整合,如果接下來(lái)你還要拉取并整合他們修改過的提交施蜜,事情就會(huì)變得一團(tuán)糟卒蘸。
讓我們來(lái)看一個(gè)在公開的倉(cāng)庫(kù)上執(zhí)行變基操作所帶來(lái)的問題。 假設(shè)你從一個(gè)中央服務(wù)器克隆然后在它的基礎(chǔ)上進(jìn)行了一些開發(fā)。 你的提交歷史如圖所示:
然后恰起,某人又向中央服務(wù)器提交了一些修改,其中還包括一次合并趾牧。 你抓取了這些在遠(yuǎn)程分支上的修改检盼,并將其合并到你本地的開發(fā)分支,然后你的提交歷史就會(huì)變成這樣:
接下來(lái)吨枉,這個(gè)人又決定把合并操作回滾,改用變基县恕;繼而又用 git push --force
命令覆蓋了服務(wù)器上的提交歷史东羹。 之后你從服務(wù)器抓取更新剂桥,會(huì)發(fā)現(xiàn)多出來(lái)一些新的提交忠烛。
結(jié)果就是你們兩人的處境都十分尷尬权逗。 如果你執(zhí)行 git pull
命令美尸,你將合并來(lái)自兩條提交歷史的內(nèi)容,生成一個(gè)新的合并提交斟薇,最終倉(cāng)庫(kù)會(huì)如圖所示:
此時(shí)如果你執(zhí)行 git log
命令,你會(huì)發(fā)現(xiàn)有兩個(gè)提交的作者堪滨、日期胯陋、日志居然是一樣的,這會(huì)令人感到混亂袱箱。 此外遏乔,如果你將這一堆又推送到服務(wù)器上,你實(shí)際上是將那些已經(jīng)被變基拋棄的提交又找了回來(lái)发笔,這會(huì)令人感到更加混亂盟萨。 很明顯對(duì)方并不想在提交歷史中看到 C4
和 C6
,因?yàn)橹熬褪撬堰@兩個(gè)提交通過變基丟棄的了讨。
變基解決變基
如果真的遇到了類似的問題捻激,那咋辦啊前计?
實(shí)際上胞谭,Git 除了對(duì)整個(gè)提交計(jì)算 SHA-1 校驗(yàn)和以外,也對(duì)本次提交所引入的修改計(jì)算了校驗(yàn)和——即 “patch-id”男杈。
如果團(tuán)隊(duì)中的某人強(qiáng)制推送并覆蓋了一些你所基于的提交丈屹,你需要做的就是檢查你做了哪些修改,以及他們覆蓋了哪些修改势就。
如果你拉取被覆蓋過的更新并將你手頭的工作基于此進(jìn)行變基的話泉瞻,一般情況下 Git 都能成功分辨出哪些是你的修改脉漏,并把它們應(yīng)用到新分支上。
舉個(gè)例子袖牙,如果遇到前面提到的有人推送了經(jīng)過變基的提交侧巨,并丟棄了你的本地開發(fā)所基于的一些提交那種情境(也就是上面的情境):
如果我們不是執(zhí)行合并(也就是沒有C8的提交),而是執(zhí)行 git rebase teamone/master
, Git 將會(huì):
- 檢查哪些提交是我們的分支上獨(dú)有的(C2鞭达,C3司忱,C4,C6畴蹭,C7)
- 檢查其中哪些提交不是合并操作的結(jié)果(C2坦仍,C3,C4)
- 檢查哪些提交在對(duì)方覆蓋更新時(shí)并沒有被納入目標(biāo)分支(只有 C2 和 C3叨襟,因?yàn)?C4 其實(shí)就是 C4')
- 把查到的這些提交應(yīng)用在
teamone/master
上面
從而我們將得到與你將相同的內(nèi)容又合并了一次繁扎,生成了一個(gè)新的提交 中不同的結(jié)果,如圖 在一個(gè)被變基然后強(qiáng)制推送的分支上再次執(zhí)行變基 所示:
要想上述方案有效糊闽,還需要對(duì)方在變基時(shí)確保 C4'
和 C4
是幾乎一樣的梳玫。 否則變基操作將無(wú)法識(shí)別网严,并新建另一個(gè)類似 C4
的補(bǔ)逗蛏(而這個(gè)補(bǔ)丁很可能無(wú)法整潔的整合入歷史,因?yàn)檠a(bǔ)丁中的修改已經(jīng)存在于某個(gè)地方了)侮邀。
在本例中另一種簡(jiǎn)單的方法是使用 git pull --rebase
命令而不是直接 git pull
念链。 又或者你可以自己手動(dòng)完成這個(gè)過程盼忌,先 git fetch
,再 git rebase teamone/master
掂墓。
如果你習(xí)慣使用 git pull
谦纱,同時(shí)又希望默認(rèn)使用選項(xiàng) --rebase
,你可以執(zhí)行這條語(yǔ)句 git config --global pull.rebase true
來(lái)更改 pull.rebase
的默認(rèn)配置梆暮。
如果你只對(duì)不會(huì)離開你電腦的提交執(zhí)行變基服协,那就不會(huì)有事。 如果你對(duì)已經(jīng)推送過的提交執(zhí)行變基啦粹,但別人沒有基于它的提交偿荷,那么也不會(huì)有事。 如果你對(duì)已經(jīng)推送至共用倉(cāng)庫(kù)的提交上執(zhí)行變基命令唠椭,并因此丟失了一些別人的開發(fā)所基于的提交跳纳, 那你就有大麻煩了。
如果你或你的同事在某些情形下決意要這么做贪嫂,請(qǐng)一定要通知每個(gè)人執(zhí)行 git pull --rebase
命令寺庄,這樣盡管不能避免傷痛,但能有所緩解。
變基 VS 合并
了解了變基和合并斗塘,肯定會(huì)有疑惑赢织,都能達(dá)到一樣的目的,那哪種方式更好呢馍盟?
有人認(rèn)為于置,倉(cāng)庫(kù)的提交歷史即是記錄實(shí)際發(fā)生過什么,它是針對(duì)歷史的文檔贞岭,本身就有價(jià)值八毯,不能亂改。即使由合并產(chǎn)生的提交歷史是一團(tuán)糟瞄桨,這些痕跡也應(yīng)該被保留下來(lái)话速,讓后人能夠查閱。
還有人認(rèn)為芯侥,提交歷史是項(xiàng)目過程中發(fā)生的事泊交,不必每件事都記錄的那么清楚,可以允許丟失一些無(wú)關(guān)緊要的細(xì)節(jié)筹麸,保留主要的脈絡(luò)就行了活合。
其實(shí)這個(gè)問題沒有標(biāo)準(zhǔn)答案雏婶, Git 是一個(gè)非常強(qiáng)大的工具物赶,它允許你對(duì)提交歷史做許多事情,但每個(gè)團(tuán)隊(duì)留晚、每個(gè)項(xiàng)目對(duì)此的需求并不相同酵紫。 既然你已經(jīng)分別學(xué)習(xí)了兩者的用法,相信你能夠根據(jù)實(shí)際情況作出明智的選擇错维。
總的原則就是:只對(duì)尚未推送或分享給別人的本地修改執(zhí)行變基操作清理提交歷史奖地,不對(duì)已推送至別處的提交執(zhí)行變基操作。
Git全文總結(jié)
終于要結(jié)束了赋焕,但是git還沒有真正的學(xué)完参歹,看完這些,只是了解了一些git比較基礎(chǔ)的知識(shí)隆判,跟多高級(jí)的操作犬庇,還需要繼續(xù)學(xué)習(xí),但是也勉強(qiáng)夠用了侨嘀,后續(xù)如果遇到一些坑臭挽,我會(huì)重新寫一些文章來(lái)進(jìn)行記錄。