九澳淑、一篇弄懂Git分支

什么是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)大之處。

image-20200923094752490.png

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)此次保存的快照。

image-20200918042109655.png

別方勃蜘,現(xiàn)在我們來(lái)看看首次提交對(duì)象及其樹結(jié)構(gòu):

image-20200920221304154.png

現(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ì)象)的指針莹汤。

image-20200920223441356.png

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

image-20200920232041377.png

注: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è)指向相同提交歷史的分支:

image-20200920233111213.png

那么堤舒,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)切換到新分支中去环础。

image-20200921001503092.png

你可以簡(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)前 mastertesting 分支均指向校驗(yàn)和以 f30ab 開頭的提交對(duì)象喳整。

分支切換

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

$ git checkout testing

這樣 HEAD 就指向 testing 分支了框都。

image-20200921003131064.png

這樣的實(shí)現(xiàn)方式有什么好處搬素?當(dāng)再次進(jìn)行提交時(shí):

image-20200921003330375.png

testing分支(HEAD所在分支)隨著提交操作自動(dòng)向前移動(dòng),但是master分支卻沒有移動(dòng)魏保,他仍然指向運(yùn)行git checkout時(shí)所指的對(duì)象熬尺。再切回master分支看看:

$ git checkout master
image-20200921003745986.png

可以看到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

image-20200921010301499.png

你可以簡(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)歷:

  1. 開發(fā)某個(gè)網(wǎng)站墩虹。
  2. 為實(shí)現(xiàn)某個(gè)新的用戶需求嘱巾,創(chuàng)建一個(gè)分支憨琳。
  3. 在這個(gè)分支上開展工作。

這時(shí)浓冒,出現(xiàn)一個(gè)很嚴(yán)重的bug需要緊急修補(bǔ)栽渴。那么這時(shí)候的流程應(yīng)該是這樣的:

  1. 切換到你的線上分支(production branch)。
  2. 為這個(gè)緊急任務(wù)新建一個(gè)分支稳懒,并在其中修復(fù)它闲擦。
  3. 在測(cè)試通過之后,切換回線上分支场梆,然后合并這個(gè)修補(bǔ)分支墅冷,最后將改動(dòng)推送到線上分支。
  4. 切換回你最初工作的分支上或油,繼續(xù)工作寞忿。

假設(shè)在master分支上已經(jīng)有了一些提交:

image-20200921093629463.png

這個(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

image-20200921094054060.png

你繼續(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):

image-20200921094614566.png

這時(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(-)
image-20200921100559184.png

可以在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

image-20200921101641681.png

合并完成之后顺少,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(-)
image-20200921113511692.png

假設(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(-)
image-20200927155746985.png

這和之前合并 hotfix 分支的時(shí)候看起來(lái)有一點(diǎn)不一樣癞松。 在這種情況下爽撒,你的開發(fā)歷史從一個(gè)更早的地方開始分叉開來(lái)(diverged)。 因?yàn)椋?code>master 分支所在提交并不是 iss53 分支所在提交的直接祖先响蓉,git 不得不做一些額外的工作硕勿。 出現(xiàn)這種情況的時(shí)候,git 會(huì)使用兩個(gè)分支的末端所指的快照(C4C5)以及這兩個(gè)分支的公共祖先(C2)枫甲,做一個(gè)簡(jiǎn)單的三方合并源武。

image-20200921153724325.png

和之前將分支指針向前推進(jìn)所不同的是,git 將此次三方合并的結(jié)果做了一個(gè)新的快照并且自動(dòng)創(chuàng)建一個(gè)新的提交指向它想幻。 這個(gè)被稱作一次合并提交粱栖,它的特別之處在于他有不止一個(gè)父提交。

image-20200921154157859.png

既然你的修改已經(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ǔ)路捧。

image-20200922104438392.png

如果你在本地的 master 分支做了一些工作抓韩,在同一段時(shí)間內(nèi)有其他人推送提交到 git.ourcompany.com 并且更新了它的 master 分支,這就是說你們的提交歷史已走向不同的方向鬓长。 即便這樣谒拴,只要你保持不與 origin 服務(wù)器連接(并拉取數(shù)據(jù)),你的 origin/master 指針就不會(huì)移動(dòng)涉波。

image-20200922105108905.png

如果要與給定的遠(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 指針到更新之后的位置相恃。

image-20200922105220881.png

為了演示有多個(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 的縮寫。

image-20200922105617419.png

現(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 指向 teamonemaster 分支。

image-20200922105852517.png

推送

在推送本地分支到遠(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/mastermaster 分支宠蚂。 然而式撼,如果你愿意的話可以設(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è)新的分支:

image-20200927181822767.png
$ 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è)置還是通過 clonecheckout 命令為你創(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ù):

  1. 查看reflog策州,找到最后一次commitid

    $ git reflog --date=iso
    

    reflogreference 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
    
  2. 檢出分支

    $ git checkout -b recovery_branch_name commitid
    

    檢出分支病蛉,本地就有分支了炫加。

  3. push到遠(yuǎn)程倉(cāng)庫(kù)

    $ git push origin recovery_branch_name
    

    至此,恢復(fù)完成铺然。

變基

image-20200928165826202.png
image-20200923165435138.png

在 Git 中整合來(lái)自不同分支的修改主要有兩種方法:merge 以及 rebase俗孝。rebase有人把它翻譯成“變基”。

變基的基本操作

之前的分支合并中有一個(gè)例子魄健,你會(huì)看到開發(fā)任務(wù)分叉到兩個(gè)不同的分支赋铝,又各自提交了更新:

image-20200928170407752.png

之前介紹過,整合分支最容易的方法是 merge 命令沽瘦。 它會(huì)把兩個(gè)分支的最新快照(C3C4)以及二者最近的共同祖先(C2)進(jìn)行三方合并革骨,合并的結(jié)果是生成一個(gè)新的快照(并提交):

image-20200928170709177.png

另一種方法:提取在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)用蔫浆。

image-20200928171433525.png

現(xiàn)在回到 master 分支,進(jìn)行一次快進(jìn)合并姐叁。

$ git checkout master
$ git merge experiment
image-20200928171547867.png

這兩種整合方法的最終結(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ù)端添加了一些功能布疙,提交了 C3C4蚊惯。 然后從 C3 上創(chuàng)建了分支 client愿卸,為客戶端添加了一些功能,提交了 C8C9截型。 最后趴荸,你回到 server 分支,又提交了 C10宦焦。

image-20200928172718863.png

你希望將 client 中的修改合并到主分支并發(fā)布发钝,但暫時(shí)并不想合并 server 中的修改, 因?yàn)樗鼈冞€需要經(jīng)過更全面的測(cè)試波闹。這時(shí)酝豪,你就可以使用 git rebase 命令的 --onto 選項(xiàng), 選中在 client 分支里但不在 server 分支里的修改(即 C8C9)精堕,將它們?cè)?master 分支上整合:

$ git rebase --onto master server client

以上命令的意思是:“取出 client 分支孵淘,找出它從 server 分支分歧之后的補(bǔ)丁, 然后把這些補(bǔ)丁在 master 分支上整合歹篓,讓 client 看起來(lái)像直接基于 master 修改一樣”瘫证。

image-20200928173415142.png

現(xiàn)在可以快進(jìn)合并 master 分支了。(如圖快進(jìn)合并 master 分支庄撮,使之包含來(lái)自 client 分支的修改):

$ git checkout master
$ git merge client
image-20200928173618435.png

接下來(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 后面巡扇。

image-20200928173943542.png

然后就可以快進(jìn)合并主分支 master 了:

$ git checkout master
$ git merge server
# 合并后 client和server分支都不需要了扭仁,可以刪除
$ git branch -d client
$ git branch -d server
image-20200928174117268.png

變基的風(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ā)。 你的提交歷史如圖所示:

克隆一個(gè)倉(cāng)庫(kù)缸沃,然后在它的基礎(chǔ)上進(jìn)行了一些開發(fā)

然后恰起,某人又向中央服務(wù)器提交了一些修改,其中還包括一次合并趾牧。 你抓取了這些在遠(yuǎn)程分支上的修改检盼,并將其合并到你本地的開發(fā)分支,然后你的提交歷史就會(huì)變成這樣:

抓取別人的提交翘单,合并到自己的開發(fā)分支

接下來(lái)吨枉,這個(gè)人又決定把合并操作回滾,改用變基县恕;繼而又用 git push --force 命令覆蓋了服務(wù)器上的提交歷史东羹。 之后你從服務(wù)器抓取更新剂桥,會(huì)發(fā)現(xiàn)多出來(lái)一些新的提交忠烛。

有人推送了經(jīng)過變基的提交,并丟棄了你的本地開發(fā)所基于的一些提交

結(jié)果就是你們兩人的處境都十分尷尬权逗。 如果你執(zhí)行 git pull 命令美尸,你將合并來(lái)自兩條提交歷史的內(nèi)容,生成一個(gè)新的合并提交斟薇,最終倉(cāng)庫(kù)會(huì)如圖所示:

你將相同的內(nèi)容又合并了一次师坎,生成了一個(gè)新的提交

此時(shí)如果你執(zhí)行 git log 命令,你會(huì)發(fā)現(xiàn)有兩個(gè)提交的作者堪滨、日期胯陋、日志居然是一樣的,這會(huì)令人感到混亂袱箱。 此外遏乔,如果你將這一堆又推送到服務(wù)器上,你實(shí)際上是將那些已經(jīng)被變基拋棄的提交又找了回來(lái)发笔,這會(huì)令人感到更加混亂盟萨。 很明顯對(duì)方并不想在提交歷史中看到 C4C6,因?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ā)所基于的一些提交那種情境(也就是上面的情境):

image-20200928175512283.png

如果我們不是執(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í)行變基 所示:

image-20200928181907257.png

要想上述方案有效糊闽,還需要對(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)行記錄。

image-20201001133250303.png
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末咬腕,一起剝皮案震驚了整個(gè)濱河市欢峰,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖纽帖,帶你破解...
    沈念sama閱讀 222,252評(píng)論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件宠漩,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡懊直,警方通過查閱死者的電腦和手機(jī)哄孤,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,886評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)吹截,“玉大人瘦陈,你說我怎么就攤上這事〔ǘ恚” “怎么了晨逝?”我有些...
    開封第一講書人閱讀 168,814評(píng)論 0 361
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)懦铺。 經(jīng)常有香客問我捉貌,道長(zhǎng),這世上最難降的妖魔是什么冬念? 我笑而不...
    開封第一講書人閱讀 59,869評(píng)論 1 299
  • 正文 為了忘掉前任趁窃,我火速辦了婚禮,結(jié)果婚禮上急前,老公的妹妹穿的比我還像新娘醒陆。我一直安慰自己,他們只是感情好裆针,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,888評(píng)論 6 398
  • 文/花漫 我一把揭開白布刨摩。 她就那樣靜靜地躺著,像睡著了一般世吨。 火紅的嫁衣襯著肌膚如雪澡刹。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,475評(píng)論 1 312
  • 那天耘婚,我揣著相機(jī)與錄音罢浇,去河邊找鬼。 笑死沐祷,一個(gè)胖子當(dāng)著我的面吹牛嚷闭,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播戈轿,決...
    沈念sama閱讀 41,010評(píng)論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼凌受,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了思杯?” 一聲冷哼從身側(cè)響起胜蛉,我...
    開封第一講書人閱讀 39,924評(píng)論 0 277
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤挠进,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后誊册,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體领突,經(jīng)...
    沈念sama閱讀 46,469評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,552評(píng)論 3 342
  • 正文 我和宋清朗相戀三年案怯,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了君旦。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,680評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡嘲碱,死狀恐怖金砍,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情麦锯,我是刑警寧澤恕稠,帶...
    沈念sama閱讀 36,362評(píng)論 5 351
  • 正文 年R本政府宣布,位于F島的核電站扶欣,受9級(jí)特大地震影響鹅巍,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜料祠,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,037評(píng)論 3 335
  • 文/蒙蒙 一骆捧、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧髓绽,春花似錦敛苇、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,519評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至塘匣,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間巷帝,已是汗流浹背忌卤。 一陣腳步聲響...
    開封第一講書人閱讀 33,621評(píng)論 1 274
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留楞泼,地道東北人驰徊。 一個(gè)月前我還...
    沈念sama閱讀 49,099評(píng)論 3 378
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像堕阔,于是被迫代替她去往敵國(guó)和親棍厂。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,691評(píng)論 2 361