03_ Git分支

Git分支

分支簡(jiǎn)介

提交對(duì)象(commit object)

Git在進(jìn)行提交操作的時(shí)候蒙秒,會(huì)保存一個(gè)提交對(duì)象抠刺,該提交對(duì)象包含的信息有:

  1. 指向暫存內(nèi)容==快照==的指針
    • 這里簡(jiǎn)單說(shuō)一下==快照==這一概念(本人也不太懂)
    • 快照是數(shù)據(jù)存儲(chǔ)的某一時(shí)刻的狀態(tài)記錄篮愉;備份則是數(shù)據(jù)存儲(chǔ)的某一個(gè)時(shí)刻的副本
    • ==快照==僅僅記錄邏輯地址和物理地址的對(duì)應(yīng)關(guān)系
    • ==備份==就是將物理數(shù)據(jù)做一次復(fù)制
    • 我們只要知道快照速度快很多耻矮,通常情況下占用的空間比備份少很多就行了初厚。如果要研究清楚就得使用搜索引擎之類的了
  2. 作者的姓名和電子郵箱
  3. 提交時(shí)的提交說(shuō)明
  4. 指向它的父對(duì)象的指針贸毕。當(dāng)然首次提交的提交對(duì)象沒(méi)有父對(duì)象郑叠,其余的提交對(duì)象都有至少一個(gè)父對(duì)象

Git是如何保存數(shù)據(jù)的

  1. 為了形象地說(shuō)明Git是如何保存數(shù)據(jù)的,我們先假設(shè)我們現(xiàn)在有一個(gè)工作目錄明棍,里面包含三個(gè)將要被暫存和提交的文件乡革;

  2. 如果我們這時(shí)候執(zhí)行暫存操作,那么暫存操作就會(huì)為每一個(gè)文件計(jì)算校驗(yàn)和,然后把當(dāng)前版本的文件快照保存到Git倉(cāng)庫(kù)中(Git使用blob對(duì)象來(lái)保存它們)沸版,最后將校驗(yàn)和放入暫存區(qū)域中等待提交嘁傀。如果我們對(duì)上面的三個(gè)文件都進(jìn)行了暫存操作,此時(shí)Git倉(cāng)庫(kù)中就新增了三個(gè)blob對(duì)象视粮;

  3. 隨后如果我們進(jìn)行提交操作细办,這是Git會(huì)先計(jì)算每一個(gè)子目錄的校驗(yàn)和(此時(shí)只有一個(gè)子目錄就是根目錄),其后將這些校驗(yàn)和以==樹對(duì)象==的形式保存在Git中蕾殴。然后再創(chuàng)建一個(gè)==提交對(duì)象==笑撞,提交對(duì)象包含上述的信息,此外該對(duì)象還保存著一個(gè)指向剛才那個(gè)樹對(duì)象的指針钓觉。如此一來(lái)茴肥,Git就可以在需要的時(shí)候重現(xiàn)此次保存的快照

  4. 最終可用下圖表示初次提交后各個(gè)對(duì)象之間的關(guān)系

    image-20200527110126266
  1. 稍作修改之后再次提交,那么這次的提交對(duì)象就會(huì)包含一個(gè)指向上一次的提交對(duì)象(父對(duì)象)的指針,如下圖所示

    image-20200527110152320

分支的實(shí)質(zhì)

Git的分支實(shí)質(zhì)上就是指向提交對(duì)象的可變指針荡灾。Git的默認(rèn)分支是==master==,在多次提交之后瓤狐,我們其實(shí)已經(jīng)擁有了一個(gè)指向最后一個(gè)提交對(duì)象的==master==分支。

  • Git的==master==分支和其他的分支實(shí)質(zhì)上沒(méi)有任何區(qū)別批幌,它也沒(méi)有任何特殊的地方础锐。之所以用的多,是因?yàn)?code>git init會(huì)默認(rèn)創(chuàng)建它荧缘,然后人們懶得改皆警。

分支的創(chuàng)建

  1. 本文的圖片大部分來(lái)自Git官方的中文教程中的圖片

  2. 在簡(jiǎn)單了解了分支的概念之后,我們就應(yīng)該很想知道分支是怎么創(chuàng)建的胜宇。

  3. 實(shí)際上這很簡(jiǎn)單耀怜,我們只需要使用git branch <branchName>命令就可以創(chuàng)建一個(gè)分支了。

  4. 執(zhí)行了上面的命令之后桐愉,

  5. Git實(shí)際上就新建了一個(gè)可以移動(dòng)的指向提交對(duì)象的指針财破。例如

    $ git branch testing
    

    這個(gè)命令就新建了一個(gè)testing的指針指向當(dāng)前的提交對(duì)象(也就是最新的提交對(duì)象)。如下圖所示

    image-20200527111955045
  6. 那么Git是如何知道當(dāng)前在哪一個(gè)分支上呢从诲?實(shí)際上在Git中還有一個(gè)名為HEAD的特殊指針左痢,該指針指向當(dāng)前所在的本地分支,如下圖所示

    <img src="https://cdn.jsdelivr.net/gh/Square-John/Image/img/image-20200527112259747.png" alt="image-20200527112259747" style="zoom:50%;" />

  7. 使用git branch <branchName>命令只是新建了一個(gè)分支系洛,但是并不會(huì)自動(dòng)切換到新建的分支中去俊性。也就是HEAD指針不會(huì)自動(dòng)指向該新建的指針。

  8. 我們可以使用git log --decorate命令查看當(dāng)前的各個(gè)分支所指向的提交對(duì)象描扯。

切換分支

  1. 上面我們已經(jīng)學(xué)習(xí)了如何新建一個(gè)分支定页,那么我們應(yīng)該怎么樣切換到這些已存在的分支上呢?

  2. 我們需要使用git checkout <branchName>來(lái)切換到分支==testing==上绽诚。例如

    $ git checkout testing
    
    • 命令執(zhí)行之后它就會(huì)提示我們現(xiàn)在已經(jīng)切換到==testing==上了

    • 根據(jù)上面的講述典徊,我們應(yīng)該知道了分支的切換杭煎,實(shí)際上就是HEAD指針改變了它所指向的對(duì)象。這里在執(zhí)行該指令之后卒落,HEAD就指向了testing羡铲。如下圖所示

      <img src="https://cdn.jsdelivr.net/gh/Square-John/Image/img/image-20200527143912380.png" alt="image-20200527143912380" style="zoom:50%;" />

  1. 然后我們?cè)?code>testing分支上做一些修改然后再提交

    <img src="https://cdn.jsdelivr.net/gh/Square-John/Image/img/image-20200527144947687.png" alt="image-20200527144037668" style="zoom:50%;" />

    • 如圖所示,testing分支向前移動(dòng)了儡毕,但是master分支卻沒(méi)有也切,依然停留在原地。
  2. 然后我們切換回到master分支

    $ git checkout master
    
    • 此時(shí)腰湾,HEAD就會(huì)指向master雷恃。如下圖所示

      <img src="https://cdn.jsdelivr.net/gh/Square-John/Image/img/image-20200527144037668.png" alt="image-20200527144947687" style="zoom:50%;" />

    • 這一條命令做了兩件事:

      • 使HEAD指向master
      • 將工作目錄中的文件內(nèi)容恢復(fù)為master分支所指向的快照的內(nèi)容
    • 也就是說(shuō),你現(xiàn)在切換回來(lái)的話檐盟,你就相當(dāng)于回退到了一個(gè)較老的版本褂萧,它會(huì)忽略掉testing分支所做的修改

  3. 從上面的描述來(lái)看押桃,分支的切換葵萎,是會(huì)改變我們工作目錄中的內(nèi)容的。如果Git不能干凈利落地改變工作目錄中的內(nèi)容唱凯,也就是在切換過(guò)程中如果可能使文件內(nèi)容混淆錯(cuò)亂羡忘,這時(shí)候Git將會(huì)禁止分支的切換。換句話說(shuō)磕昼,Git中進(jìn)行分支的切換時(shí)可以保證數(shù)據(jù)的準(zhǔn)確性的卷雕,否則Git會(huì)阻止那種會(huì)導(dǎo)致工作區(qū)數(shù)據(jù)內(nèi)容錯(cuò)亂的切換。

  4. 隨后票从,我們又在master分支上進(jìn)行了修改和提交漫雕,這時(shí)候,項(xiàng)目的提交歷史就會(huì)產(chǎn)生分叉峰鄙,如下圖所示

    <img src="https://cdn.jsdelivr.net/gh/Square-John/Image/img/image-20200527150018922.png" alt="image-20200527150018922" style="zoom:67%;" />

  5. 我們可以不斷地在分支之間進(jìn)行切換和工作浸间,這些分支之間的提交互不干擾,因此在它們上進(jìn)行的工作也互不干擾吟榴。在實(shí)際成熟之后魁蒜,我們還可以把它們合并起來(lái)。

  6. 使用git log --oneline --decorate --graph --all命令可以查看項(xiàng)目的整個(gè)提交歷史吩翻,項(xiàng)目的分支分叉情況兜看,以及各個(gè)分支的指向。

  7. Git分支實(shí)質(zhì)上是僅包含其所指對(duì)象的檢驗(yàn)和(包含40個(gè)字符的SHA-1值)的文件狭瞎,因此它的創(chuàng)建和銷毀都異常高效细移。創(chuàng)建一個(gè)新分支就相當(dāng)于在文件中寫入41個(gè)字節(jié)。

  8. 在創(chuàng)建分支的同時(shí)切換到創(chuàng)建的分支的命令如下

    git checkout -b <newBranchName>
    

分支的新建和合并

  1. 首先我們?cè)?code>master分支上已經(jīng)進(jìn)行了一些工作熊锭,產(chǎn)生了一些提交弧轧,如下圖

    <img src="https://cdn.jsdelivr.net/gh/Square-John/Image/img/image-20200527151729990.png" alt="image-20200527151729990" style="zoom:50%;" />

  2. 然后可能想開發(fā)一個(gè)新功能缔刹,我們新建一個(gè)分支iss53來(lái)進(jìn)行這個(gè)開發(fā)工作

    $ git checkout -b iss53
    
    • 結(jié)果如圖

      <img src="https://cdn.jsdelivr.net/gh/Square-John/Image/img/image-20200527151910011.png" alt="image-20200527151910011" style="zoom:50%;" />

  3. 然后我們?cè)?code>iss53分支上進(jìn)行了一些工作并進(jìn)行了一次提交,就變成如下這樣

    <img src="https://cdn.jsdelivr.net/gh/Square-John/Image/img/image-20200527152025027.png" alt="image-20200527152025027" style="zoom:50%;" />

  4. 這時(shí)候我們發(fā)現(xiàn)master上存在bug劣针,那么這個(gè)時(shí)候校镐,分支的優(yōu)勢(shì)就體現(xiàn)出來(lái)了,因?yàn)槲覀內(nèi)绻胄迯?fù)bug捺典,只需要將分支切換回master就可以將工作區(qū)中的內(nèi)容恢復(fù)到當(dāng)時(shí)的版本了鸟廓,而不是需要將iss53分支上的修改撤銷。

    $ git checkout master
    
    • 我們?cè)谇袚Q分支的時(shí)候務(wù)必確保當(dāng)前所在的分支上的所有工作都已經(jīng)進(jìn)行了提交襟己,否則會(huì)導(dǎo)致分支的切換失敗
  5. 然后我們創(chuàng)建一個(gè)新分支hotfix用于修復(fù)bug

    $ git branch hotfix
    $ git checkout hotfix
    
    • 隨后在其上進(jìn)行了一些工作引谜,進(jìn)行了一次提交,就變成下面這樣

      <img src="https://cdn.jsdelivr.net/gh/Square-John/Image/img/image-20200527154229252.png" alt="image-20200527152853839" style="zoom:50%;" />

分支的快進(jìn)合并

  1. 然后經(jīng)過(guò)測(cè)試擎浴,我們發(fā)現(xiàn)员咽,C4的修改把bug修復(fù)了,然后我們就可以將hotfix分支合并回到master

    • 首先我們要將分支切換到我們想要并入的分支贮预,本例中就是master分支

      $ git checkout master
      
    • 然后執(zhí)行合并命令git merge <branchName>贝室,此處的==branchName==是要合并進(jìn)來(lái)的分支,本例中是hotfix分支

      $ git merge hotfix
      
    • 執(zhí)行命令之后的輸出信息如下

      $ git checkout master
      $ git merge hotfix
      Updating f42c576..3a0874c
      Fast-forward
      index.html | 2 ++
      1 file changed, 2 insertions(+)
      
  2. 在上面的合并輸出的時(shí)候仿吞,我們應(yīng)該有注意到這樣的一個(gè)詞語(yǔ)==Fast-forward==滑频。這個(gè)詞語(yǔ)的含義是

    • 當(dāng)我們?cè)噲D合并兩個(gè)分支的時(shí)候,如果順著一個(gè)分支走下去能夠到達(dá)另外一個(gè)分支唤冈,那么在合并這兩個(gè)分支的時(shí)候峡迷,只會(huì)將落后的指針向前推進(jìn)到另外一個(gè)指針的地方

    • 因?yàn)檫@樣的合并沒(méi)有需要解決的分歧,所以叫做==快進(jìn)==

    • ==快進(jìn)==合并之后的結(jié)果如圖所示

      <img src="https://cdn.jsdelivr.net/gh/Square-John/Image/img/image-20200527154708589.png" alt="image-20200527154229252" style="zoom:50%;" />

  3. 在完成了合并之后你虹,我們就可以刪除其中的某一個(gè)分支绘搞,例如本例中,要把hotfix分支刪除傅物,那么我們就可以通過(guò)git branch -d <branchName>命令進(jìn)行刪除操作夯辖。本例為

    $ git branch -d hotfix
    
    • 此時(shí),分支的情況就變成了下圖這樣

      <img src="https://cdn.jsdelivr.net/gh/Square-John/Image/img/image-20200527154848656.png" alt="image-20200527154708589" style="zoom:80%;" />

  4. 然后我們又切換回到iss53分支上繼續(xù)進(jìn)行開發(fā)挟伙,如下圖所示

    <img src="https://cdn.jsdelivr.net/gh/Square-John/Image/img/image-20200527152853839.png" alt="image-20200527154848656" style="zoom:67%;" />

  5. 這里需要注意的是楼雹,我們?cè)?code>hotfix分支上所做的修改并沒(méi)有包含在iss53分支上。如果我們需要其中的修改數(shù)據(jù)尖阔,我們可以使用git merge master命令將master分支合并到iss53分支上贮缅。另外我們也可以等iss53分支的開發(fā)任務(wù)完成之后再把它合并回到master分支上。

分支的分叉合并

  1. 假設(shè)此時(shí)我們已經(jīng)完成了開發(fā)介却,打算將iss53分支合并到master分支上谴供,這時(shí)候和前面的合并操作沒(méi)有什么分別

    • 切換到master分支

      $ git checkout master
      
    • iss53分支合并進(jìn)來(lái)

      $ git merge iss53
      
    • 然后可以將多余的分支iss53刪除

      $ git branch -d iss53
      
  2. 但是這次合并的輸出信息和和上次合并hotfix分支的情況有點(diǎn)不一樣,如下所示

    $ git checkout master
    Switched to branch 'master'
    $ git merge iss53
    Merge made by the 'recursive' strategy.
    index.html | 1 +
    1 file changed, 1 insertion(+)
    
    • 此時(shí)的合并的信息是==Merge made by the ‘recursive’ strategy==齿坷,其中==recursive==的意思是遞歸桂肌、循環(huán)的意思
    • 這是因?yàn)楸敬魏喜⒌膬蓚€(gè)分支沒(méi)有誰(shuí)是誰(shuí)的祖先数焊,兩個(gè)分支是分叉形成的
  3. 為了對(duì)這樣的兩個(gè)分支進(jìn)行合并,Git要做一些額外的工作:

    • 出現(xiàn)這樣的情況的時(shí)候崎场,Git會(huì)使用兩個(gè)分支的末端所指的快照(==C4==和==C5==)以及兩個(gè)分支的共同祖先(==C2==)來(lái)做一個(gè)==三方合并==佩耳。如下圖所示

      <img src="https://cdn.jsdelivr.net/gh/Square-John/Image/img/image-20200527173114246.png" alt="image-20200527173114246" style="zoom:80%;" />

    • 和之前的快進(jìn)合并不同的是,Git將這次的三方合并的結(jié)果做了一次新的快照谭跨,并自動(dòng)創(chuàng)建一個(gè)新的提交對(duì)象指向這個(gè)新的快照干厚。這個(gè)提交被稱作==一個(gè)合并提交==,其特別之處在于螃宙,這樣的提交對(duì)象有著不止一個(gè)父提交蛮瞄,如下圖所示

      image-20200527173204349
    • 從圖中可以看到C6有著兩個(gè)父提交,分別是C4C5

遇到?jīng)_突時(shí)的分支合并

  1. 有時(shí)候我們的合并不會(huì)如此順利谆扎。例如如果我們?cè)趦蓚€(gè)分支中都對(duì)同一個(gè)文件的相同部分(比如同一行或者是相同的幾行)分別進(jìn)行了修改挂捅,這時(shí)候Git就無(wú)法干凈地合并它們。

  2. 對(duì)于這樣的情況堂湖,Git會(huì)將他們合并起來(lái)但是不會(huì)自動(dòng)生成一個(gè)新的提交闲先。在合并完之后,Git會(huì)暫停下來(lái)苗缩,等待我們自己去解決這些沖突然后再自行提交饵蒂。

  3. 在發(fā)生合并沖突之后声诸,我們可以使用git status查看是那些文件產(chǎn)生了沖突而處于未完成合并的狀態(tài)酱讶。例如

    $ git status
    On branch master
    You have unmerged paths.
    (fix conflicts and run "git commit")
    Unmerged paths:
    (use "git add <file>..." to mark resolution)
    both modified: index.html
    no changes added to commit (use "git add" and/or "git commit -a")
    

    上面?zhèn)€就列出了未完成合并的文件名以及原因

    • ==Unmerged paths:==列表中的就是因?yàn)闆_突而未完成合并的文件列表
    • ==both modified==是文件沖突的原因,兩個(gè)分支都對(duì)同一個(gè)文件進(jìn)行了修改
  4. Git會(huì)將兩個(gè)分支的同一個(gè)文件的沖突部分都合并到一個(gè)文件中彼乌,這個(gè)文件就是未完成合并的文件泻肯,沖突的內(nèi)容使用特殊的標(biāo)記標(biāo)出,并標(biāo)明哪一部分是哪一個(gè)分支的慰照。

    • 我們可以分別打開這些未完成合并的文件灶挟,看到?jīng)_突的信息。例如
    <<<<<<< HEAD:index.html
    <div id="footer">contact : email.support@github.com</div>
    =======
    <div id="footer">
    please contact us at support@github.com
    </div>
    >>>>>>> iss53:index.html
    
    • 沖突信息分析
      • ==<<<<<<< HEAD:index.html==這一行表示HEAD所指向的分支的文件版本(在本例中也就是master分支中的文件版本)中的沖突內(nèi)容
      • ==>>>>>>> iss53:index.html==同理毒租。表示iss53分支中的文件版本的沖突部分的內(nèi)容
      • ======是分隔符稚铣,上半部分是HEAD所指向的分支的文件中的沖突內(nèi)容,下半部分是iss53分支的文件中的沖突內(nèi)容
  5. 以上具有沖突信息的文件需要我們手動(dòng)修改合并墅垮。比如說(shuō)對(duì)于重復(fù)的內(nèi)容保留其中的一個(gè)惕医,或者是對(duì)于不同的內(nèi)容進(jìn)行修正合并。修改完成之后要把上面標(biāo)有著特殊含義的行都刪除算色,就是上面沖突分析指出那幾行抬伺。

  6. 修改完成之后,需要使用git add命令將這些文件都進(jìn)行暫存灾梦,git add之后峡钓,Git就會(huì)將它們標(biāo)記為沖突已解決的狀態(tài)

  7. 隨后還可以使用git status工具來(lái)確認(rèn)是否所有沖突都已經(jīng)解決妓笙。如果是,并且自己對(duì)于修改的結(jié)果都已經(jīng)滿意能岩,就可以使用git commit命令來(lái)完成合并提交寞宫。

  8. 只有使用git commit命令完成了合并提交,本次合并操作才算徹底完成了拉鹃。

分支管理

本小節(jié)主要是學(xué)幾個(gè)常用的分支管理的工具

git branch命令不只是可以創(chuàng)建和刪除分支淆九。如果不加任何參數(shù)執(zhí)行它,就會(huì)列出當(dāng)前所有分支的列表毛俏,這跟git tag命令類似炭庙。

  • git branch命令列出當(dāng)前所有分支。例如

    $ git branch
    iss53
    * master
    testing
    
    • 其中帶==*==號(hào)的分支就是當(dāng)前所在的分支或者說(shuō)是HEAD所指向的分支煌寇。
  • git branch -v可以看到每一個(gè)分支的最后一次提交的信息焕蹄。例如

    $ git branch -v
    iss53 93b412c fix javascript issue
    * master 7a98805 Merge branch 'iss53'
    testing 782fd34 add scott to the author list in the readmes
    
    • 我們可以看到每一個(gè)分支最后一次提交時(shí)的部分校驗(yàn)和和提交說(shuō)明
  • git branch后增加--merged--no-merged選項(xiàng)可以過(guò)濾出當(dāng)前所有的分支中已經(jīng)合并到當(dāng)前分支或者是沒(méi)有合并到當(dāng)前分支的分支列表

    • --merged選項(xiàng)通常用來(lái)查看哪些分支是已經(jīng)合并到當(dāng)前分支的裁替。然后可以將這些多余的分支進(jìn)行刪除操作

    • --no-merged選項(xiàng)可以選出那些還沒(méi)有合并到當(dāng)前分支的分支糖声。如果我們要對(duì)這些分支進(jìn)行刪除操作,如果使用的是gti branch -d <branchName>會(huì)刪除失敗冯丙,因?yàn)樗鼈儼诉€沒(méi)有合并的工作银锻。如果非要?jiǎng)h除永品,可以增加-D選項(xiàng)來(lái)進(jìn)行強(qiáng)制刪除

    • 實(shí)際上這兩個(gè)選項(xiàng)還可以在后面指定分支名,那么這時(shí)候過(guò)濾的就是已經(jīng)合并到指定分支或者是還沒(méi)有合并到指定分支的分支列表了击纬。例如

      $ git checkout testing
      $ git branch --no-merged master
      topicA
      featureB
      

      就是說(shuō)列出當(dāng)前還沒(méi)有合并到master分支的分支列表鼎姐,然后==topicA==和==featureB==就是還沒(méi)有合并到master分支的分支

    • 也就是說(shuō),如果其后有分支名更振,就過(guò)濾的是和指定分支是否合并的分支列表炕桨;如果沒(méi)有,就默認(rèn)這個(gè)指定的分支是當(dāng)前所在的分支肯腕。

分支開發(fā)工作流

這部分估計(jì)也就本人看得懂献宫,大家看不懂的就湊個(gè)熱鬧好了,語(yǔ)言表達(dá)能力太貧乏实撒,沒(méi)文化真就只能一句 ‘wo曹’走天下了

長(zhǎng)期分支

長(zhǎng)期分支的工作方式大概可以描述為:用一個(gè)分支保留最穩(wěn)定的代碼姊途,然后使用另外的分支將工作向前推進(jìn),工作進(jìn)行到一定的階段知态,代碼變得穩(wěn)定之后就并入穩(wěn)定的分支捷兰,然后再轉(zhuǎn)換到其他分支上繼續(xù)推進(jìn)工作,如此反復(fù)肴甸。這就相當(dāng)于用一個(gè)分支用作版本發(fā)布寂殉,不在其上面工作,使用另外的分支推進(jìn)工作原在。它的方式相當(dāng)于下圖

image-20200527211334747
  • 這類似于流水線工作

    image-20200527211431973
  • 這就相當(dāng)于多個(gè)分支在一線上跑友扰,前面的是測(cè)試版彤叉,后面的是穩(wěn)定版,前面的測(cè)試穩(wěn)定了就將穩(wěn)定版分支指針移到向前移動(dòng)到當(dāng)前位置村怪,然后發(fā)布新的穩(wěn)定版秽浇,然后測(cè)試版又向前推進(jìn)。

主題分支

主題分支是一種短期分支甚负。主題分支就是按照主題進(jìn)行分支柬焕,每一個(gè)分支的開發(fā)都有某一個(gè)單一目標(biāo)和主題,當(dāng)該分支達(dá)到了其目標(biāo)功能或者是實(shí)現(xiàn)了它的主題梭域,就將其合并回到主干分支斑举。

  • 就比如說(shuō),我想開發(fā)某一個(gè)功能病涨,那我就創(chuàng)建一個(gè)分支來(lái)開發(fā)這個(gè)功能富玷,這個(gè)分支的創(chuàng)建就是用來(lái)在其上完成該功能的開發(fā)。我一旦想開發(fā)一個(gè)新功能既穆,我就創(chuàng)建一個(gè)新分支赎懦,然后再這個(gè)分支上的工作就圍繞這個(gè)主題(功能)展開,而一旦完成了這個(gè)功能幻工,就把這個(gè)分株合并回到主干分支励两。

長(zhǎng)期分支和主題分支的區(qū)別

  1. 長(zhǎng)期分支的意思大概就是說(shuō),一個(gè)項(xiàng)目長(zhǎng)期保持著多個(gè)分支囊颅,然后這些不同的分支指向不同穩(wěn)定性的階段性工作成果当悔,這些分支沒(méi)有明顯的主題,實(shí)際上迁酸,所有的功能都是在一條線上開發(fā)的先鱼,一個(gè)分支開發(fā)多個(gè)功能甚至是所有功能。分支的主要目的就是奸鬓,用一些分支來(lái)開發(fā)新功能,開展新工作掸读,而另外一些分支純粹是為了保存穩(wěn)定的版本內(nèi)容串远,用作向公眾發(fā)布。
  2. 主題分支的一個(gè)顯著的特點(diǎn)就是儿惫,按照主題來(lái)創(chuàng)建分支澡罚,每一個(gè)分支有著明確的主題和任務(wù),任務(wù)完成后就合并到主干分支肾请。沒(méi)有新功能或者新主題的時(shí)候可能項(xiàng)目只有一個(gè)分支留搔,只有加入新主題,才創(chuàng)建新分支铛铁,分支的任務(wù)完成之后就將其與主干分支合并然后將其刪除隔显。

遠(yuǎn)程分支

  1. 我們上面的所有操作却妨,所創(chuàng)建的全部分支都是存儲(chǔ)在本地的,我們都沒(méi)有和服務(wù)器發(fā)生交互

  2. 遠(yuǎn)程引用是對(duì)遠(yuǎn)程倉(cāng)庫(kù)的引用(指針)括眠,包括分支彪标、標(biāo)簽等。

    • 我們可以通過(guò)git ls-remote <remoteRepoditory>來(lái)顯示獲得遠(yuǎn)程引用的完整列表掷豺。例如

      $ git ls-remote con
      2fb972523cbca9fbf6cd3730b34fb38af552533d        HEAD
      2fb972523cbca9fbf6cd3730b34fb38af552533d        refs/heads/master
      
- 或者是`git remote show <remoteRepository>`來(lái)獲取遠(yuǎn)程分支的更多信息捞烟。例如

    ```bash 
    $ git remote show con
    * remote con
      Fetch URL: https://github.com/srevinsaju/conozco.git
      Push  URL: https://github.com/srevinsaju/conozco.git
      HEAD branch: master
      Remote branch:
        master new (next fetch will store in remotes/con)
      Local branch configured for 'git pull':
        master merges with remote master
      Local ref configured for 'git push':
        master pushes to master (up to date)
    ```
  1. 遠(yuǎn)程跟蹤分支是遠(yuǎn)程分支狀態(tài)的引用,它們是我們無(wú)法移動(dòng)的本地指針当船。一旦我們進(jìn)行網(wǎng)絡(luò)通信题画,Git就會(huì)自動(dòng)移動(dòng)這樣的指針,以精確反應(yīng)遠(yuǎn)程倉(cāng)庫(kù)的狀態(tài)德频。這樣的分支記錄了我們最后一次連接到遠(yuǎn)程服務(wù)器上的遠(yuǎn)程倉(cāng)庫(kù)時(shí)婴程,遠(yuǎn)程庫(kù)上的該分支所處的位置。

    • 遠(yuǎn)程跟蹤分支抱婉,顧名思義就是用來(lái)跟蹤遠(yuǎn)程倉(cāng)庫(kù)上的分支位置档叔,保存在本地上的指針。這個(gè)指針的特別之處就在于它不能認(rèn)為移動(dòng)蒸绩,是在和相應(yīng)的遠(yuǎn)程庫(kù)連接后Git進(jìn)行自動(dòng)移動(dòng)的衙四,即將該指針在本地移動(dòng)到和遠(yuǎn)程庫(kù)相應(yīng)的分支指針相同的位置。==相當(dāng)于自動(dòng)同步==

    • 這樣的分支以<remoteRepository>/<branchName>的形式命名患亿。如果我們向查看我們最后一次連接該遠(yuǎn)程庫(kù)時(shí)該分支所在的位置传蹈,我們就可以以<remoteRepository>/<branchName>的形式查看該分支的信息。

    • 我們?cè)趯?duì)一個(gè)倉(cāng)庫(kù)進(jìn)行克隆的時(shí)候步藕,Git的clone命令會(huì)自動(dòng)為我們創(chuàng)建一個(gè)表示該遠(yuǎn)程庫(kù)的簡(jiǎn)寫名origin惦界,然后拉取它的所有數(shù)據(jù),然后自動(dòng)創(chuàng)建一個(gè)指向遠(yuǎn)程庫(kù)master分支的指針origin/master咙冗,這就是一個(gè)遠(yuǎn)程跟蹤分支沾歪。同時(shí),Git還會(huì)為我們克隆的本地庫(kù)創(chuàng)建一個(gè)本地分支master雾消,它和origin/master指針指向相同的地方灾搏。master分支由我們自己控制,就是一個(gè)普通的本地分支立润,origin/master由Git自動(dòng)控制狂窑,只有連接到相應(yīng)的遠(yuǎn)程庫(kù)并且遠(yuǎn)程庫(kù)上的master分支移動(dòng)了,該指針才會(huì)移動(dòng)桑腮,并且會(huì)自動(dòng)移動(dòng)到和遠(yuǎn)程庫(kù)的master分支當(dāng)前所在位置一致的地方泉哈。

      <img src="https://cdn.jsdelivr.net/gh/Square-John/Image/img/image-20200527211431973.png" alt="image-20200528091250777" style="zoom:80%;" />

      • origin這一個(gè)名稱和master一樣,沒(méi)有什么特殊的含義,只是因?yàn)?code>clone命令會(huì)默認(rèn)生成這樣的一個(gè)名稱丛晦。
    • <remoteRepository>/<branchName>這個(gè)指針是保存在本地的奕纫,但是使用Git自動(dòng)控制的,只有當(dāng)本地和==remoteRepository==連接后采呐,該指針的位置才會(huì)更新若锁,它指向的位置總是最后一次和遠(yuǎn)程服務(wù)器連接之后相應(yīng)的分支的最新位置。只要不和遠(yuǎn)程服務(wù)器連接通信斧吐,他就不會(huì)變

    • 小結(jié) 上面所說(shuō)的內(nèi)容涉及了三個(gè)分支

      • 遠(yuǎn)程服務(wù)器上的對(duì)應(yīng)遠(yuǎn)程庫(kù)上的master分支又固,這一分支是位于遠(yuǎn)程服務(wù)器上的
      • 本地倉(cāng)庫(kù)中的masteroringin/master分支,這兩個(gè)都是在本機(jī)上的
      • 位于本機(jī)上的兩個(gè)分支煤率,實(shí)際上都是指向本地倉(cāng)庫(kù)的提交歷史仰冠。master分支是由我們自己掌控的用來(lái)進(jìn)行工作的,而origin/master分支是指向本地歷史的蝶糯,但是是Git自動(dòng)控制的洋只,它用來(lái)追蹤遠(yuǎn)程服務(wù)器上的master的狀態(tài),因此只有和遠(yuǎn)程服務(wù)器進(jìn)行通信的時(shí)候它才能獲得遠(yuǎn)程服務(wù)器上的master分支的最新位置昼捍,也才能進(jìn)行更新识虚。
      • 因此,遠(yuǎn)程服務(wù)器上的master分支的推進(jìn)不會(huì)影響本地的master分支的工作妒茬,本地master分支的推進(jìn)也不會(huì)改變origin/master分支的位置担锤。一旦連接到遠(yuǎn)程服務(wù)器,origin/master就會(huì)和遠(yuǎn)程服務(wù)器上的master分支同步乍钻,斷開連接之后肛循,origin/master分支就會(huì)停在這樣的位置不動(dòng),直到下一次聯(lián)機(jī)同步银择。
  2. 這里補(bǔ)充一下多糠,上面所說(shuō)的==遠(yuǎn)程跟蹤分支==的自動(dòng)更新并非真的不需要任何操作Git就會(huì)幫我們做好。實(shí)際上我們要將origin/master這樣的遠(yuǎn)程跟蹤分支進(jìn)行更新浩考,必須要手動(dòng)執(zhí)行git fetch <remoteRepository>命令夹孔,這時(shí)候Git才會(huì)幫我們自動(dòng)抓取遠(yuǎn)程服務(wù)器上的master更新的內(nèi)容到本地,然后自動(dòng)移動(dòng)origin/master指針與之同步怀挠。也就是說(shuō)析蝴,如果我們不手動(dòng)拉取遠(yuǎn)程庫(kù)上的信息,Git是不會(huì)幫我們自動(dòng)拉取這些更新的內(nèi)容下來(lái)的绿淋。這也很好理解,因?yàn)槲覀冞B接到遠(yuǎn)程服務(wù)器不總是想要更新我們本地的數(shù)據(jù)的

推送

  1. 當(dāng)我們想要公開分享一個(gè)分支的時(shí)候尝盼,我們需要將其推送到有寫入權(quán)限的遠(yuǎn)程倉(cāng)庫(kù)上吞滞。本地的分支并不會(huì)自動(dòng)地和遠(yuǎn)程庫(kù)進(jìn)行同步,我們必須顯示推送我們想要分享的分支

  2. 這樣的好處就是我們可以自行決定我們想要分享和不想分享的分支裁赠。我們可以只把我們想要分享的分支推送到遠(yuǎn)程庫(kù)上殿漠。

  3. git push <remoteRepository> <branchName>命令就是用來(lái)推送分支到遠(yuǎn)程庫(kù)上的

    • 在我們執(zhí)行這樣的一條命令之后,Git實(shí)際上會(huì)自動(dòng)幫我們把==branchName==展開成refs/heads/<branchName>:refs/heads/<<branchName>

    • 實(shí)際上我們也可以使用這樣的一個(gè)命令來(lái)推送我們的分支

      $ git push <remoteRepository> <localBranchName>:<remoteBranchName>
      

      這樣就可以把一個(gè)本地分支推送到遠(yuǎn)程庫(kù)并可以給它起一個(gè)和本地分支名不同的名字

  4. 在我們把分支推送到遠(yuǎn)程服務(wù)器上之后佩捞,別人就可以從服務(wù)器上抓取這一分支的內(nèi)容到本地

    • 我們已經(jīng)知道绞幌,git fetch <remoteRepository>命令只會(huì)把遠(yuǎn)程庫(kù)的數(shù)據(jù)下載到本地,但是不會(huì)將這些數(shù)據(jù)自動(dòng)合并到本地倉(cāng)庫(kù)

    • 所以一忱,如果我們通過(guò)該命令抓取到了一個(gè)新的分支莲蜘,它也不會(huì)自動(dòng)合并到我們當(dāng)前所在的分支,而是在本地多了一個(gè)<remoteRepository>/<branchName>指針帘营,也就是遠(yuǎn)程跟蹤分支

    • 我們可以將這一分支和某一個(gè)分支進(jìn)行合并票渠,這和上面講述的本地庫(kù)的分支的合并沒(méi)有什么區(qū)別

      • 指定分支名,就把遠(yuǎn)程跟蹤分支合并到指定的分支

        $ git merge <localBranch> <remoteRepository>/<branchName>
        
      • 不指定分支名芬迄,就將遠(yuǎn)程跟蹤分支和當(dāng)前分支進(jìn)行合并

        $ git merge <remoteRepository>/<branchName>
        
    • 如果想要在一個(gè)自己本地的分支上工作问顷,但是這些工作建立在遠(yuǎn)程跟蹤分支之上,那么我們可以使用下面的命令

      $ git checkout -b <localBranch> <remoteRepository>/<branch>
      
      • 這一命令就會(huì)給我們一個(gè)用于工作的本地分支<localBranch>禀梳,分支的起點(diǎn)位于<remoteRepository>/<branch>
      • 這時(shí)候?qū)嶋H上我們就建立了一個(gè)==跟蹤分支==<localBranch>

跟蹤分支

  1. 從一個(gè)遠(yuǎn)程跟蹤分支上檢出(也就是執(zhí)行的git chechout命令)一個(gè)本地分支會(huì)自動(dòng)創(chuàng)建所謂的==跟蹤分支==(它跟蹤的分支叫做==上游分支==)杜窄。

  2. 跟蹤分支是一個(gè)與遠(yuǎn)程分支有直接關(guān)系的本地分支。如果在一個(gè)跟蹤分支上輸入git pull算途,Git能自動(dòng)識(shí)別要到哪一個(gè)服務(wù)器上去抓取數(shù)據(jù)塞耕,然后合并到哪一個(gè)分支

  3. 在我們克隆一個(gè)倉(cāng)庫(kù)的時(shí)候,Git會(huì)自動(dòng)創(chuàng)建一個(gè)跟蹤origin/master分支的master分支

  4. 我們也可以自行設(shè)置跟蹤分支或者不跟蹤哪一個(gè)分支郊艘,我們還可以設(shè)置一個(gè)在其他遠(yuǎn)程倉(cāng)庫(kù)上的跟蹤分支

  5. 設(shè)置跟蹤分支的方法

    • git checkout -b <branchA> <remoteRep>/<branchB>
    • git checkout --track <remoteRep>/<branch>這個(gè)命令直接==創(chuàng)建==一個(gè)和遠(yuǎn)程分支同名的跟蹤分支
    • 如果我們?cè)跈z出一個(gè)分支的時(shí)候荷科,該分支名不存在,而又剛好這時(shí)候在本地記錄的所有遠(yuǎn)程庫(kù)中只有一個(gè)遠(yuǎn)程庫(kù)有一個(gè)分支叫這個(gè)名字纱注,那么這個(gè)時(shí)候Git就會(huì)為我們自動(dòng)==創(chuàng)建==一個(gè)叫這個(gè)名字的跟蹤分支畏浆,跟蹤的是那個(gè)唯一具有同名的遠(yuǎn)程分支。即git checkout <branch>
  6. 上面設(shè)置跟蹤分支的方法都是新建分支來(lái)進(jìn)行跟蹤的狞贱。如果想要使用一個(gè)已經(jīng)存在的分支來(lái)跟蹤剛剛抓取下來(lái)的遠(yuǎn)程分支刻获,或者說(shuō)是想要修正正在跟蹤的上游分支,可以使用-u或者是--set-upstream-to選項(xiàng)運(yùn)行git branch來(lái)顯式設(shè)置瞎嬉。例如

    $ git branch -u origin/master
    

    命令就將當(dāng)前分支設(shè)置為origin/master的跟蹤分支

  7. 上游快捷方式 設(shè)置好跟蹤分支之后蝎毡,我們可以使用@{upstream} or @{u}來(lái)引用它的上游分支。比如說(shuō)我們正處在master分支上氧枣,并且該分支正在跟蹤origin/master分支我們可以使用

    $ git merge @{u}
    

    來(lái)取代

    $4 git merge origin/master
    
  8. 如果想查看設(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==的意思是領(lǐng)先,==2==代表領(lǐng)先的版本數(shù)预吆。這意味著本地還有兩個(gè)提交沒(méi)有推送到遠(yuǎn)程服務(wù)器上

    • 也能看到master分支正在跟蹤origin/master分支并且二者是同步的

    • 接下來(lái)還看到serverfix分支正在跟蹤origin/server-fix-good分支龙填,并且領(lǐng)先3落后1,==behind==就是落后的意思拐叉,其后的==1==就是落后的版本數(shù)岩遗。這意味著本地有3次提交沒(méi)有推送到遠(yuǎn)程服務(wù)器上,遠(yuǎn)程服務(wù)器上有一次提交沒(méi)有被并入本地分支

    • 最后看到testing沒(méi)有跟蹤任何分支

    • ==注意== 這里的領(lǐng)先和落后的次數(shù)都是拿本地的跟蹤分支和其跟蹤的遠(yuǎn)程跟蹤分支進(jìn)行比較的巷嚣,也就是比較的是本地跟蹤分支和最后一次連接遠(yuǎn)程庫(kù)時(shí)更新的遠(yuǎn)程跟蹤分支的數(shù)據(jù)

    • 想要統(tǒng)計(jì)最新的領(lǐng)先和落后次數(shù)喘先,必須在查看之前先對(duì)所有的遠(yuǎn)程分支進(jìn)行抓取⊥⒘#可以使用下面的命令

      $ git fetch --all
      

拉取

  1. 使用git fetch命令會(huì)把服務(wù)器上有而本地沒(méi)有的數(shù)據(jù)抓取到本地窘拯,但是這些數(shù)據(jù)不會(huì)自動(dòng)合并到本地的跟蹤分支上,而是保存在遠(yuǎn)程倉(cāng)庫(kù)的本地引用中坝茎,我們需要使用git merge進(jìn)行手動(dòng)合并
  2. 如果我們已經(jīng)設(shè)置好了相應(yīng)的跟蹤分支涤姊,那么此時(shí)就可以使用git pull自動(dòng)抓取服務(wù)器上的數(shù)據(jù),然后Git會(huì)自動(dòng)將這些抓取到的內(nèi)容找到相對(duì)應(yīng)的本地的跟蹤分支來(lái)進(jìn)行合并嗤放。
  3. 通常建議使用git fetchgit merge而不是直接使用git pull

刪除遠(yuǎn)程分支

  1. 如果我們已經(jīng)通過(guò)遠(yuǎn)程分支完成了所有的工作思喊,并且將遠(yuǎn)程分支的內(nèi)容和服務(wù)器上的master分支(或者是其他的分支)進(jìn)行了合并。也就是說(shuō)在服務(wù)器上的某一個(gè)分支的工作已經(jīng)完成并且已經(jīng)和服務(wù)器上的master分支(或者其他分支)進(jìn)行了合并次酌,那么該分支相當(dāng)于多余的可刪除的分支酗钞。那么我們就可以將它刪除了娇澎。

  2. 刪除一個(gè)遠(yuǎn)程分支的命令是

    $ git push <remoteRep> --delete <RemoteBranch>
    
    • 該命令會(huì)將==RemoteBranch==從遠(yuǎn)程服務(wù)器上刪除
  3. 實(shí)際上上面的刪除命令只是將服務(wù)器上的這個(gè)代表某一個(gè)分支的指針刪除了,而分支上的數(shù)據(jù)Git通常會(huì)將其保留在服務(wù)器上一段時(shí)間,直到垃圾回收運(yùn)行骇扇。所以如果一不小心刪除了一個(gè)遠(yuǎn)程分支档押,通常是很容易就可以恢復(fù)的窖维。

變基

在Git中整合來(lái)自不同分支的修改主要有兩種方法:mergerebase啦吧。本節(jié)主要就是==rebase(變基)==

變基的基本操作

  1. 回顧前面的分支的合并的方式,對(duì)于如下的兩個(gè)分支

    <img src="https://cdn.jsdelivr.net/gh/Square-John/Image/img/image-20200528203202339.png" alt="image-20200528203202339" style="zoom:50%;" />

  2. 使用merge命令將兩個(gè)分支合并璃搜。合并的過(guò)程可以描述為:Git會(huì)把兩個(gè)分支的最新快照(==C4==和==C3==)以及二者的最近的共同祖先(==C2==)進(jìn)行三方合并拖吼,并且為合并的結(jié)果形成一個(gè)新的提交,如下圖

    <img src="https://cdn.jsdelivr.net/gh/Square-John/Image/img/image-20200528205758154.png" alt="image-20200528203540423" style="zoom:80%;" />

  3. 這里介紹一種新的合并方法——變基:我們可以提取在==C4==中引入的修改和補(bǔ)丁这吻,然后在==C3==的基礎(chǔ)上再應(yīng)用一次==C4==的修改和補(bǔ)丁吊档。這樣的操作再Git中就被成為變基

  4. 我們可以使用rebase命令將提交到==某一個(gè)分支==上的所有修改都轉(zhuǎn)移到另外一個(gè)分支上唾糯,就好像在==另外一個(gè)分支==上面重新播放一遍這個(gè)==某一個(gè)分支==上面的所有修改一樣籍铁。

  5. 對(duì)于上面的例子涡上,我們可以checkout==experiment==分支趾断,然后把它變基到==master==分支上拒名。這只需要執(zhí)行如下的命令

    $ git checkout experiment
    $ git rebase master
    First, rewinding head to replay your work on top of it...
    Applying: added staged command
    
    • 其原理是首先找到這兩個(gè)分支(當(dāng)前分支==experiment==、變基操作的目標(biāo)基底分支==master==)的最近的共同祖先C2

    • 然后對(duì)比當(dāng)前分支==experiment==相對(duì)于該祖先的歷次提交芋酌,然后提取相應(yīng)的修改并保存為臨時(shí)的文件增显。也就是說(shuō),找到最近的共同祖先之后脐帝,使用一個(gè)文件保存下來(lái)當(dāng)前分支==experiment==的歷次提交的修改內(nèi)容同云。

    • 然后將當(dāng)前分支==experiment==指向目標(biāo)基底分支的最新一次的提交C3,最后將上面臨時(shí)保存的文件中的歷次修改依次應(yīng)用堵腹。這就相當(dāng)于在==master==分支上在其最新的提交之后依次重新提交一遍==experiment==上自與==master==最近共同祖先之后的歷次修改炸站。過(guò)程如下圖所示

      image-20200528205758154
    • 然后,我們還需要回到==master==分支上執(zhí)行一次快進(jìn)合并

      $ git checkout master
      $ git merge experiment
      
    • 這就有點(diǎn)類似于==長(zhǎng)期分支==的流水形式疚顷。也就是將另外一個(gè)分支所做的工作搬到想要并入的分支的最前面旱易,然后再將==master==指針向前移動(dòng)。如下圖所示

      image-20200528210210293
  6. 執(zhí)行變基之后腿堤,Figure 38C4'指向的快照實(shí)際上和Figure 36中的C5指向的快照是一模一樣的阀坏。這兩種合并的方法的最終合并結(jié)果并沒(méi)有什么差別。但是變基使得提交歷史變得更加整潔笆檀。

  7. 當(dāng)我們查看經(jīng)過(guò)一個(gè)變基之后的分支的提交歷史時(shí)我們會(huì)發(fā)現(xiàn)忌堂,盡管實(shí)際的開發(fā)工作是并行的,但它們看上去就好像是串行的一樣酗洒,提交的歷史是一條直線而沒(méi)有分叉士修。也就是說(shuō),經(jīng)過(guò)變基之后的分支的提交歷史不再是實(shí)際的像Figure 36中的樣子樱衷,而是像Figure 38中那樣棋嘲,把另外一個(gè)分支的提交歷史接在了==并入到==的分支的提交歷史的前面。

  8. 我們使用變基的目的一般是為了確保在向遠(yuǎn)程分支推送時(shí)能保持提交歷史的整潔箫老。例如我們要為遠(yuǎn)程服務(wù)器中的某一個(gè)項(xiàng)目貢獻(xiàn)代碼封字,我們現(xiàn)在我們的本地分支上進(jìn)行修改,然后再把這些修改變基到遠(yuǎn)程的某一個(gè)分支上耍鬓,這樣阔籽,遠(yuǎn)程倉(cāng)庫(kù)的代碼維護(hù)者就不用進(jìn)行整合工作,只需要快進(jìn)合并即可牲蜀。

  9. 注意 無(wú)論是三方合并還是變基笆制,合并的最終結(jié)果所指向的快照是一樣的,只不過(guò)是提交歷史不同而已涣达。變基是把一個(gè)分支的提交歷史依次應(yīng)用到另一個(gè)分支在辆,而三方合并是將兩個(gè)分支的最終結(jié)果合在一起证薇。變基的提交歷史是一條直線,三方合并則會(huì)有分叉匆篓。

更有趣的變基例子

這個(gè)例子實(shí)際上可不是為了有趣而已浑度。

  1. 對(duì)兩個(gè)分支進(jìn)行變基時(shí),所生成的重放并不一定要在目標(biāo)分支上應(yīng)用鸦概,也可以應(yīng)用到另外一條分支上箩张。

  2. 例如下面的例子,一個(gè)項(xiàng)目的各個(gè)分支的提交歷史如下

    image-20200528213448004
  3. 假設(shè)此時(shí)我們想要將client中的修改合并到master中并發(fā)布窗市,但是暫時(shí)并不想合并server中的修改先慷,可能這個(gè)分支的工作還沒(méi)有完成。

  4. 這時(shí)候我們就可以使用git rebase命令的--onto選項(xiàng)咨察,選擇那些在client分支中但是不在server分支中的修改(也就是C8C9)论熙,將它們?cè)?code>master上重放,即

    $ git rebase --onto master server client
    
    • 我們可以看到該命令有三個(gè)參數(shù)

    • master為進(jìn)行進(jìn)行重放的分支(==第一個(gè)參數(shù)==)

    • server相當(dāng)于兩個(gè)分支在進(jìn)行變基時(shí)的基底分支(==第二個(gè)參數(shù)==)

    • client分支就相當(dāng)于進(jìn)行變基的兩個(gè)分支中的當(dāng)前分支摄狱,是用來(lái)提取修改和補(bǔ)丁的(==第三個(gè)參數(shù)==)

    • 這一個(gè)命令的意思就是:取出在client上的脓诡,和server分叉之后的補(bǔ)丁,然后將這些補(bǔ)丁在master上重放一遍二蓝,讓client看起來(lái)像是直接基于master進(jìn)行了修改一樣

    • 其結(jié)果如下圖

      image-20200528214749314
  5. 然后可以快進(jìn)合并master分支了

    $ git checkout master
    $ git merge client
    
    image-20200528214909946
  6. 這上面的例子就說(shuō)明了在開頭說(shuō)的“對(duì)兩個(gè)分支進(jìn)行變基時(shí)誉券,所生成的==重放==并不一定要在目標(biāo)分支上應(yīng)用,也可以應(yīng)用到另外一條分支上”這句話的含義刊愚。上面的例子實(shí)際上是server分支和client分支進(jìn)行變基踊跟,但是卻將變基的重放應(yīng)用在了master分支上。

  7. 接下來(lái)我們決定把server分支也合并到master分支上鸥诽,這個(gè)時(shí)候的變基實(shí)際上就和普通的變基沒(méi)什么區(qū)別了商玫。這里介紹一個(gè)更加便捷的命令,如下所示

    $ git rebase <baseBranch> <topicBranch>
    

    對(duì)于本例牡借,就是

    $ git rebase master server
    

    該命令就省去了要先切換到server分支拳昌,然后提取其補(bǔ)丁變基到master分支上,這個(gè)命令執(zhí)行之后钠龙,結(jié)果如下圖所示

    <img src="https://cdn.jsdelivr.net/gh/Square-John/Image/img/image-20200529082914058.png" alt="image-20200529082200467" style="zoom:150%;" />

  8. 接下來(lái)我們就可以進(jìn)行快進(jìn)合并了

    $ git checkout master
    $ git merge server
    
  9. 至此clientserver分支都已經(jīng)合并到了master分支里面炬藤。接下來(lái)我們就可以刪除這兩個(gè)分支了,最終的提交歷史就會(huì)變成下圖所示的樣子

    <img src="https://cdn.jsdelivr.net/gh/Square-John/Image/img/image-20200529082200467.png" alt="image-20200529082914058" style="zoom:150%;" />

變基的風(fēng)險(xiǎn)

  1. 變基操作的實(shí)質(zhì)是丟棄一些現(xiàn)有的提交碴里,然后相應(yīng)地新建一些內(nèi)容一樣但是實(shí)際上不同 的提交

  2. 如果我們將倉(cāng)庫(kù)上的內(nèi)容推送到了遠(yuǎn)程服務(wù)器沈矿,而其他人又從這個(gè)遠(yuǎn)程庫(kù)中拉取了這些提交并進(jìn)行后續(xù)工作,但是之后你使用rebase命令重新進(jìn)行了整理再次提交推送咬腋,那么這時(shí)候羹膳,其他人就不得不重新拉取你的推送和他們正在進(jìn)行的工作進(jìn)行整合。如果接下來(lái)我們又拉取并整合了他們所做的提交根竿,這時(shí)候陵像,事情就會(huì)變得一團(tuán)糟就珠。

  3. 我們用一個(gè)例子來(lái)說(shuō)明這件事

    • 假如我們從一個(gè)服務(wù)器上克隆了一個(gè)倉(cāng)庫(kù)進(jìn)行開發(fā)。提交歷史如下圖

      image-20200529083839219
    • 隨后有某個(gè)人向服務(wù)器推送了自己的修改醒颖,這其中包括一次合并

      image-20200529084029763
    • 然后你把這些提交的內(nèi)容拉取下來(lái)并和本地倉(cāng)庫(kù)進(jìn)行了合并妻怎,我們本地庫(kù)的提交歷史就會(huì)變成下面這樣

      image-20200529084150656
    • 接下來(lái)這個(gè)人決定把剛才的合并撤銷,采用變基的方式重新合并图贸,然后使用git push --force命令重新推送到服務(wù)器上覆蓋了上一次合并的提交歷史蹂季,這時(shí)候服務(wù)器上的歷史就變成了這樣

      image-20200529084418401
    • 然后我們又從服務(wù)器拉取更新,這時(shí)候我們發(fā)現(xiàn)多出來(lái)了一些新的提交

      image-20200529084612131
    • 這就使得我們雙方的處境都變得很尬尷疏日。如果我們執(zhí)行git pull命令,結(jié)果就像下圖這樣

      image-20200529085020207
    • 這個(gè)時(shí)候就相當(dāng)于我們又把相同的內(nèi)容進(jìn)行了依次合并撒汉,生成了一個(gè)新的提交沟优。此時(shí)如果我們執(zhí)行git log會(huì)發(fā)現(xiàn),這兩個(gè)提交的的作者睬辐,日期和日志都是一樣的挠阁,這就會(huì)令人感到混亂

    • 如果我們也推送我們的內(nèi)容到服務(wù)器,那么那些由于變基而被拋棄的提交又變回來(lái)了溯饵,這更加使人感到混亂侵俗。

用變基解決變基

用魔法打敗魔法?太強(qiáng)了吧丰刊。老爹說(shuō)的果然沒(méi)有錯(cuò)

  1. 實(shí)際上隘谣,Git除了對(duì)整個(gè)提交計(jì)算SHA-1校驗(yàn)和之外,也會(huì)對(duì)本次提交所引入的修改計(jì)算校驗(yàn)和啄巧,即==patch-id==

  2. 如果我們抓取被覆蓋過(guò)的更新并將我們手頭的工作基于此進(jìn)行變基的話寻歧,Git一般都可以分辨出哪些是我們的修改,并把它們應(yīng)用到新的分支上秩仆。

  3. 例如码泛,對(duì)于上面的例子,如果我們?cè)谧ト×诵碌耐扑蛿?shù)據(jù)后不是使用三方合并澄耍,而是執(zhí)行git rebase teamone/master的話噪珊,Git將會(huì):

    • 步驟1:檢查哪些提交是我們的分支上獨(dú)有的(C2, C4, C3, C6, C7
    • 步驟2:檢查上一個(gè)步驟篩選出的提交結(jié)果中不是合并操作結(jié)果的提交(C2, C4, C3
    • 步驟3:再檢查上一步篩選出的提交結(jié)果中那些在對(duì)方進(jìn)行覆蓋更新時(shí)沒(méi)有被納入目標(biāo)分支的提交(只有C2C3,因?yàn)?code>C4實(shí)際上就是C4’
    • 步驟4:把步驟3篩選出的最終提交結(jié)果依次應(yīng)用到teamone/master
  4. 最終就得到了==在一個(gè)被變基然后強(qiáng)制推送的分支上再次執(zhí)行變基之后的結(jié)果==如下圖所示

    image-20200529094029084
  5. 要想上述的方案有效齐莲,前提是對(duì)方在進(jìn)行變基時(shí)C4C4’幾乎是一樣的痢站。否則變基操作就無(wú)法識(shí)別它們是相同的。然后就會(huì)創(chuàng)建一個(gè)新的類似C4的補(bǔ)丁铅搓,而這個(gè)補(bǔ)丁的內(nèi)容又和C4’發(fā)生沖突瑟押,從而無(wú)法整潔地整合入歷史

  6. 對(duì)于本例的另外一個(gè)簡(jiǎn)單的方法是我們?cè)诶「聲r(shí),使用git pull --rebase而非直接git pull星掰。又或者是先git fetch然后再git rebase teamone/master

  7. 如果我們習(xí)慣使用git pull多望,但是有希望默認(rèn)使用--rebase選項(xiàng)嫩舟。我們可以執(zhí)行下面這條語(yǔ)句來(lái)更改pull.rebase的默認(rèn)配置

    $ git config --global pull.rebase true
    
  1. 總之,我們要記住這樣的一條原則:如果我們的提交會(huì)存在于我們的本地倉(cāng)庫(kù)之外怀偷,同時(shí)別人又可能會(huì)基于這些提交做開發(fā)家厌,那么我們這個(gè)時(shí)候就不應(yīng)該使用變基

  2. 實(shí)際上就是說(shuō)椎工,不應(yīng)該出現(xiàn)上面例子中的那種情況饭于,先推送了提交,之后又回滾维蒙,變基之后又重新強(qiáng)制推送掰吕。我們可以在我們的本地庫(kù)中在做完工作之后先執(zhí)行完變基,然后再推送到遠(yuǎn)程服務(wù)器上颅痊,這樣就不會(huì)給別人造成混亂

變基 vs. 合并

是使用變基還是使用合并殖熟,這似乎是一個(gè)哲學(xué)問(wèn)題

  1. 支持使用合并而不是變基的人認(rèn)為:倉(cāng)庫(kù)的提交歷史就應(yīng)該如是記錄開發(fā)的每一步,它就像歷史一樣斑响,可以為以后的開發(fā)提供參考菱属,而篡改歷史是一種褻瀆。所以即使是我們的提交歷史是一團(tuán)糟舰罚,也應(yīng)該如實(shí)記錄下來(lái)纽门,以供后人查閱。
  2. 而支持使用變基而不是合并的人則認(rèn)為:倉(cāng)庫(kù)的提交歷史應(yīng)該像一本故事書营罢,它可以清晰地告訴人們這個(gè)項(xiàng)目的開發(fā)故事赏陵。因此提交的歷史應(yīng)該是邏輯清晰的,就像是故事書一樣愤钾,人們通常才不關(guān)注我們寫這本書的時(shí)候的草稿瘟滨,而我們也不應(yīng)該將一篇草稿發(fā)布給別人看,而是應(yīng)該僅僅向公眾發(fā)布我們的修訂好的版本能颁。
  3. 所以杂瘸,是使用合并還是使用變基,沒(méi)有一個(gè)強(qiáng)制性的規(guī)定伙菊,我們應(yīng)該結(jié)合實(shí)際情況進(jìn)行明智地選擇败玉。我們只要記住使用變基時(shí)候應(yīng)該遵循的原則就不會(huì)錯(cuò)誤地使用變基,在那種既可以使用變基也可以使用合并的場(chǎng)合镜硕,選用哪種方式运翼,這就取決于個(gè)人了,取決于你的處世態(tài)度了兴枯。

本章小結(jié)

分支的創(chuàng)建

  1. 創(chuàng)建一個(gè)新分支

    $ git branch <branchName>
    
  1. 查看各個(gè)分支指向的提交對(duì)象

    $ git log --decorate
    

切換分支

  1. 切換到一個(gè)分支

    $ git checkout <branchName>
    
  1. 查看項(xiàng)目的整個(gè)提交歷史血淌,項(xiàng)目的分支分叉情況,以及各個(gè)分支的指向

    $ git log --oneline --decorate --graph --all
    
  1. 新建分支的同時(shí)切換到該分支

    $ git checkout -b <branchName>
    

分支的新建與合并

  1. 分支的合并

    $ git merge [<A>] <B>
    

    把==B==并入==A==,==A==可以省略悠夯,省略就默認(rèn)并入當(dāng)前分支

  1. 分支的刪除

    $ git branch -d <branchName>
    
  2. 遇到?jīng)_突時(shí)候的合并

    • 合并的時(shí)候遇到?jīng)_突

      $ git merge [<A>] <B>
      
- 查看所有沖突文件

    ```bash
    $ git status
    ```

    

- 修改沖突文件并加入暫存區(qū)標(biāo)記為已解決沖突狀態(tài)

    ```bash
    $ git add <file>
    ```

    

- 提交修改后的沖突文件完成合并

    ```bash
    $ git commit
    ```

分支管理

  1. 列出當(dāng)前所有分支

    $ git branch
    
  1. 查看當(dāng)前所有分支的最后一次提交的信息

    $ git branch -v
    
  1. 分支的過(guò)濾

    • 過(guò)濾出那些已經(jīng)和指定分支合并過(guò)的分支

      $ git branch --merged [<branchName>]
      
      • 如果省略后面的分支名癌淮,就是過(guò)濾出已經(jīng)和當(dāng)前分支合并過(guò)的分支
      • 否則就是過(guò)濾出已經(jīng)和==branchName==分支合并過(guò)的分支
      • 這些分支可以使用git branch -d <branchName>命令進(jìn)行刪除
    • 過(guò)濾那些還沒(méi)有和指定分支合并過(guò)的分支

      $ git branch --no-merged [<branchName]
      
      • --merged類似,過(guò)濾的是還沒(méi)有和指定分支合并的分支
      • 這些過(guò)濾出來(lái)的分支要使用-D選項(xiàng)才能進(jìn)行強(qiáng)制的刪除

遠(yuǎn)程分支

  1. 獲取遠(yuǎn)程引用的完整列表

    $ git ls-remote <remoteRep>
    
  1. 獲取遠(yuǎn)程分支的更多信息

    $ git remote show <remoteRep>
    
  1. 更新遠(yuǎn)程跟蹤分支

    $ git fetch <remoteRep>
    
  1. 推送一個(gè)分支到遠(yuǎn)程倉(cāng)庫(kù)上

    $ git push <remoteRep> <localBranch>[:<remoteBranch>]
    
    • 后面的第二個(gè)參數(shù)為遠(yuǎn)程分支名沦补,省略的話就和本地分支同名
  2. 抓取遠(yuǎn)程庫(kù)的內(nèi)容到本地

    $ git fetch <remoteRep>
    
  1. 將遠(yuǎn)程跟蹤分支的內(nèi)容和本地分支合并

    $ git merge [<localBranch>] <remoteRep>/<remoteBranch>
    
    • 如果不指定本地分支名乳蓄,就默認(rèn)為當(dāng)前分支
  2. 創(chuàng)建一個(gè)本地分支,并且其起點(diǎn)在和一個(gè)遠(yuǎn)程跟蹤分支相同

    $ git checkout -b <newBranch> <remoteRep>/<branch>
    
  1. 設(shè)置一個(gè)跟蹤分支

    • 創(chuàng)建一個(gè)分支跟蹤遠(yuǎn)程分支

      $ git checkout -b <brancha> <remoteRep>/<branchb>
      
- 創(chuàng)建一個(gè)和遠(yuǎn)程分支同名的跟蹤分支

    ```bash
    $ git checkout --track <remoteRep>/<branch>
    ```

    

- 方式3

    ```bash
    $ git checkout <branch>
    ```

    - 如果==branch==不存在夕膀,但是遠(yuǎn)程引用中有唯一一個(gè)叫做==branch==的分支虚倒,那么該命令就會(huì)創(chuàng)建一個(gè)跟蹤分支==branch==且它的上游分支就是那個(gè)唯一同名的遠(yuǎn)程分支
  1. 為一個(gè)已經(jīng)存在的分支設(shè)置上游分支使其成為一個(gè)跟蹤分支

    $ git branch -u/--set-upstream-to <remoteRep>/<branch>
    
  1. 訪問(wèn)跟蹤分支的上游分支的快捷方式

    • 設(shè)置好跟蹤分支后,可以使用@{u}或者是@{upstream}來(lái)引用上游分支产舞。例如

      $ git merge @{u}
      
- 就相當(dāng)于

    ```bash
    $ git merge <remote>/<branch>
    ```

    其中`<remote>/<branch>`就是當(dāng)前分支的上游分支
  1. 查看設(shè)置的所有跟蹤分支的信息

    $ git branch -vv
    
  1. 抓取所有的分支數(shù)據(jù)

    $ git fetch --all
    
  1. 拉取

    $ git pull <remoteRep>/<branch>
    
  1. 刪除一個(gè)遠(yuǎn)程分支

    $ git push <remoteRep> --delete <remoteBranch>
    

變基

  1. 將一個(gè)分支變基到指定分支

    $ git rebase <branchA> [<branchB>]
    
    • 省略==branchB==就默認(rèn)將當(dāng)前分支變基到==branchA==分支上
    • 否則就是將==branchB==分支變基到==branchA==上
  2. 變基之后進(jìn)行快進(jìn)合并

    $ git merge <baseBranch>
    
  1. 將兩個(gè)分支的變基的重放應(yīng)用到另外一個(gè)分支上

    $ git rebase --onto <branchA> <branchB> <branchC>
    
    • 將==branchB==和==branchC==兩個(gè)分支的重放應(yīng)用到==branchA==上
    • 具體是將==branchB==和==branchC==分叉后屬于==branchC==而不屬于==branchB==的提交歷史在==branchA==上依次重放
  2. 用變基解決變基

    $ git rebase <remoteRep>/<branch>
    
    • 這個(gè)命令最終會(huì)將在本地庫(kù)獨(dú)有的那些提交歷史應(yīng)用到<remoteRep>/<branch>

END

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末魂奥,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子庞瘸,更是在濱河造成了極大的恐慌捧弃,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,968評(píng)論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件擦囊,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡嘴办,警方通過(guò)查閱死者的電腦和手機(jī)瞬场,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)涧郊,“玉大人贯被,你說(shuō)我怎么就攤上這事∽彼遥” “怎么了彤灶?”我有些...
    開封第一講書人閱讀 153,220評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)批旺。 經(jīng)常有香客問(wèn)我幌陕,道長(zhǎng),這世上最難降的妖魔是什么汽煮? 我笑而不...
    開封第一講書人閱讀 55,416評(píng)論 1 279
  • 正文 為了忘掉前任搏熄,我火速辦了婚禮,結(jié)果婚禮上暇赤,老公的妹妹穿的比我還像新娘心例。我一直安慰自己,他們只是感情好鞋囊,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,425評(píng)論 5 374
  • 文/花漫 我一把揭開白布止后。 她就那樣靜靜地躺著,像睡著了一般溜腐。 火紅的嫁衣襯著肌膚如雪译株。 梳的紋絲不亂的頭發(fā)上瓜喇,一...
    開封第一講書人閱讀 49,144評(píng)論 1 285
  • 那天,我揣著相機(jī)與錄音古戴,去河邊找鬼欠橘。 笑死,一個(gè)胖子當(dāng)著我的面吹牛现恼,可吹牛的內(nèi)容都是我干的肃续。 我是一名探鬼主播,決...
    沈念sama閱讀 38,432評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼叉袍,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼始锚!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起喳逛,我...
    開封第一講書人閱讀 37,088評(píng)論 0 261
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤瞧捌,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后润文,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體姐呐,經(jīng)...
    沈念sama閱讀 43,586評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,028評(píng)論 2 325
  • 正文 我和宋清朗相戀三年典蝌,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了曙砂。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,137評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡骏掀,死狀恐怖鸠澈,靈堂內(nèi)的尸體忽然破棺而出语盈,到底是詐尸還是另有隱情简烘,我是刑警寧澤,帶...
    沈念sama閱讀 33,783評(píng)論 4 324
  • 正文 年R本政府宣布贡羔,位于F島的核電站葵袭,受9級(jí)特大地震影響涵妥,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜眶熬,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,343評(píng)論 3 307
  • 文/蒙蒙 一妹笆、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧娜氏,春花似錦拳缠、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,333評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春哲鸳,著一層夾襖步出監(jiān)牢的瞬間臣疑,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,559評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工徙菠, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留讯沈,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,595評(píng)論 2 355
  • 正文 我出身青樓婿奔,卻偏偏與公主長(zhǎng)得像缺狠,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子萍摊,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,901評(píng)論 2 345