git筆記_03_Git分支

Git分支

1 分支簡介

1.1 提交對象(commit object)

Git在進行提交操做的時候,會保存一個提交對象,該提交對象包含的信息有:javascript

  1. 指向暫存內容快照的指針
    • 這裏簡單說一下快照這一律念(本人也不太懂)
    • 快照是數據存儲的某一時刻的狀態記錄;備份則是數據存儲的某一個時刻的副本
    • 快照僅僅記錄邏輯地址和物理地址的對應關係
    • 備份就是將物理數據作一次複製
    • 咱們只要知道快照速度快不少,一般狀況下佔用的空間比備份少不少就好了。若是要研究清楚就得使用搜索引擎之類的了
  2. 做者的姓名和電子郵箱
  3. 提交時的提交說明
  4. 指向它的父對象的指針。固然首次提交的提交對象沒有父對象,其他的提交對象都有至少一個父對象

1.2 Git是如何保存數據的

  1. 爲了形象地說明Git是如何保存數據的,咱們先假設咱們如今有一個工做目錄,裏面包含三個將要被暫存和提交的文件;html

  2. 若是咱們這時候執行暫存操做,那麼暫存操做就會爲每個文件計算校驗和,而後把當前版本的文件快照保存到Git倉庫中(Git使用blob對象來保存它們),最後將校驗和放入暫存區域中等待提交。若是咱們對上面的三個文件都進行了暫存操做,此時Git倉庫中就新增了三個blob對象;java

  3. 隨後若是咱們進行提交操做,這是Git會先計算每個子目錄的校驗和(此時只有一個子目錄就是根目錄),其後將這些校驗和以樹對象的形式保存在Git中。而後再建立一個提交對象,提交對象包含上述的信息,此外該對象還保存着一個指向剛纔那個樹對象的指針。如此一來,Git就能夠在須要的時候重現這次保存的快照git

  4. 最終可用下圖表示初次提交後各個對象之間的關係github

  5. 稍做修改以後再次提交,那麼此次的提交對象就會包含一個指向上一次的提交對象(父對象)的指針,以下圖所示bash

1.3 分支的實質

Git的分支實質上就是指向提交對象的可變指針。Git的默認分支是master,在屢次提交以後,咱們其實已經擁有了一個指向最後一個提交對象的master分支。服務器

  • Git的master分支和其餘的分支實質上沒有任何區別,它也沒有任何特殊的地方。之因此用的多,是由於git init會默認建立它,而後人們懶得改。

2 分支的建立

  1. 本文的圖片大部分來自Git官方的中文教程中的圖片網絡

  2. 在簡單瞭解了分支的概念以後,咱們就應該很想知道分支是怎麼建立的。併發

  3. 實際上這很簡單,咱們只須要使用git branch <branchName>命令就能夠建立一個分支了。工具

  4. 執行了上面的命令以後,

  5. Git實際上就新建了一個能夠移動的指向提交對象的指針。例如

    $ git branch testing

    這個命令就新建了一個testing的指針指向當前的提交對象(也就是最新的提交對象)。以下圖所示

  6. 那麼Git是如何知道當前在哪個分支上呢?實際上在Git中還有一個名爲HEAD的特殊指針,該指針指向當前所在的本地分支,以下圖所示

  7. 使用git branch <branchName>命令只是新建了一個分支,可是並不會自動切換到新建的分支中去。也就是HEAD指針不會自動指向該新建的指針。

  8. 咱們可使用git log --decorate命令查看當前的各個分支所指向的提交對象

3 切換分支

  1. 上面咱們已經學習瞭如何新建一個分支,咱們應該怎麼樣切換到這些已存在的分支上呢?

  2. 咱們須要使用git checkout <branchName>來切換到分支testing上。例如

    $ git checkout testing
    • 命令執行以後它就會提示咱們如今已經切換到testing上了

    • 根據上面的講述,咱們應該知道了分支的切換,實際上就是HEAD指針改變了它所指向的對象。這裏在執行該指令以後,HEAD就指向了testing。以下圖所示

  3. 而後咱們在testing分支上作一些修改而後再提交

    • 如圖所示,testing分支向前移動了,可是master分支卻沒有,依然停留在原地。
  4. 而後咱們切換回到master分支

    $ git checkout master
    • 此時,HEAD就會指向master。以下圖所示

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

      • 使HEAD指向master
      • 將工做目錄中的文件內容恢復爲master分支所指向的快照的內容
    • 也就是說,你如今切換回來的話,你就至關於回退到了一個較老的版本,它會忽略掉testing分支所作的修改

  5. 從上面的描述來看,分支的切換,是會改變咱們工做目錄中的內容的。若是Git不能幹淨利落地改變工做目錄中的內容,也就是在切換過程當中若是可能使文件內容混淆錯亂,這時候Git將會禁止分支的切換。換句話說,Git中進行分支的切換時能夠保證數據的準確性的,不然Git會阻止這種會致使工做區數據內容錯亂的切換。

  6. 隨後,咱們又在master分支上進行了修改和提交,這時候,項目的提交歷史就會產生分叉,以下圖所示

  7. 咱們能夠不斷地在分支之間進行切換和工做,這些分支之間的提交互不干擾,所以在它們上進行的工做也互不干擾。在實際成熟以後,咱們還能夠把它們合併起來。

  8. 使用git log --oneline --decorate --graph --all命令能夠查看項目的整個提交歷史,項目的分支分叉狀況,以及各個分支的指向。

  9. Git分支實質上是僅包含其所指對象的檢驗和(包含40個字符的SHA-1值)的文件,所以它的建立和銷燬都異常高效。建立一個新分支就至關於在文件中寫入41個字節。

  10. 在建立分支的同時切換到建立的分支的命令以下

    git checkout -b <newBranchName>

4 分支的新建和合並

  1. 首先咱們在master分支上已經進行了一些工做,產生了一些提交,以下圖

  2. 而後可能想開發一個新功能,咱們新建一個分支iss53來進行這個開發工做

    $ git checkout -b iss53
    • 結果如圖

  3. 而後咱們在iss53分支上進行了一些工做並進行了一次提交,就變成以下這樣

  4. 這時候咱們發現master上存在bug,那麼這個時候,分支的優點就體現出來了,由於咱們若是想修復bug,只須要將分支切換回master就能夠將工做區中的內容恢復到當時的版本了,而不是須要將iss53分支上的修改撤銷。

    $ git checkout master
    • 咱們在切換分支的時候務必確保當前所在的分支上的全部工做都已經進行了提交,不然會致使分支的切換失敗
  5. 而後咱們建立一個新分支hotfix用於修復bug

    $ git branch hotfix
    	$ git checkout hotfix
    • 隨後在其上進行了一些工做,進行了一次提交,就變成下面這樣

4.1 分支的快進合併

  1. 而後通過測試,咱們發現,C4的修改把bug修復了,而後咱們就能夠將hotfix分支合併回到master

    • 首先咱們要將分支切換到咱們想要併入的分支,本例中就是master分支

      $ git checkout master
    • 而後執行合併命令git merge <branchName>,此處的branchName是要合併進來的分支,本例中是hotfix分支

      $ git merge hotfix
    • 執行命令以後的輸出信息以下

      $ git checkout master
      	$ git merge hotfix
      	Updating f42c576..3a0874c
      	Fast-forward
      	index.html | 2 ++
      	1 file changed, 2 insertions(+)
  2. 在上面的合併輸出的時候,咱們應該有注意到這樣的一個詞語Fast-forward。這個詞語打含義是

    • 當咱們試圖合併兩個分支的時候,若是順着一個分支走下去可以到達另一個分支,則在合併這兩個分支的時候,只會將落後的指針向前推動到另一個指針的地方

    • 由於這樣的合併無須要解決的分歧,因此叫作快進

    • 快進合併以後的結果如圖所示

  3. 在完成了合併以後,咱們就能夠刪除其中的某一個分支,例如本例中,要把hotfix分支刪除,則咱們就能夠經過git branch -d <branchName>命令進行刪除操做。本例爲

    $ git branch -d hotfix
    • 此時,分支的狀況就變成了下圖這樣

  4. 而後咱們又切換回到iss53分支上繼續進行開發,以下圖所示

  5. 這裏須要注意的是,咱們在hotfix分支上所作的修改並無包含在iss53分支上。若是咱們須要其中的修改數據,咱們可使用git merge master命令將master分支合併到iss53分支上。另外咱們也能夠等iss53分支的開發任務完成以後再把它合併回到master分支上。

4.2 分支的分叉合併

  1. 假設此時咱們已經完成了開發,打算將iss53分支合併到master分支上,這時候和前面的合併操做沒有什麼分別

    • 切換到master分支

      $ git checkout master
    • iss53分支合併進來

      $ git merge iss53
    • 而後能夠將多餘的分支iss53刪除

      $ git branch -d iss53
  2. 可是此次合併的輸出信息和和上次合併hotfix分支的狀況有點不同,以下所示

    $ git checkout master
    	Switched to branch 'master'
    	$ git merge iss53
    	Merge made by the 'recursive' strategy.
    	index.html | 1 +
    	1 file changed, 1 insertion(+)
    • 此時的合併的信息是Merge made by the ‘recursive’ strategy,其中recursive的意思是遞歸、循環的意思
    • 這是由於本次合併的兩個分支沒有誰是誰的祖先,兩個分支是分叉造成的
  3. 爲了對這樣的兩個分支進行合併,Git要作一些額外的工做:

    • 出現這樣的狀況的時候,Git會使用兩個分支的末端所指的快照(C4C5)以及兩個分支的共同祖先(C2)來作一個三方合併。以下圖所示

    • 和以前的快進合併不一樣的是,Git將此次的三方合併的結果作了一次新的快照,並自動建立一個新的提交對象指向這個新的快照。這個提交被稱做一個合併提交,其特別之處在於,這樣的提交對象有着不止一個父提交,以下圖所示

    • 從圖中能夠看到C6有着兩個父提交,分別是C4C5

4.3 遇到衝突時的分支合併

  1. 有時候咱們的合併不會如此順利。例如若是咱們在兩個分支中都對同一個文件的相同部分(好比同一行或者是相同的幾行)分別進行了修改,這時候Git就沒法乾淨地合併它們。

  2. 對於這樣的狀況,Git會將他們合併起來可是不會自動生成一個新的提交。在合併完以後,Git會暫停下來,等待咱們本身去解決這些衝忽然後再自行提交。

  3. 在發生合併衝突以後,咱們可使用git status查看是那些文件產生了衝突而處於未完成合並的狀態。例如

    $ 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")

    上面個就列出了未完成合並的文件名以及緣由

    • Unmerged paths:列表中的就是由於衝突而未完成合並的文件列表
    • both modified是文件衝突的緣由,兩個分支都對同一個文件進行了修改
  4. Git會將兩個分支的同一個文件的衝突部分都合併到一個文件中,這個文件就是未完成合並的文件,衝突的內容使用特殊的標記標出,並標明哪一部分是哪個分支的。

    • 咱們能夠分別打開這些未完成合並的文件,看到衝突的信息。例如
    <<<<<<< 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分支中的文件版本)中的衝突內容
      • >>>>>>> iss53:index.html同理。表示iss53分支中的文件版本的衝突部分的內容
      • `````是分隔符,上半部分是HEAD所指向的分支的文件中的衝突內容,下半部分是iss53分支的文件中的衝突內容
  5. 以上具備衝突信息的文件須要咱們手動修改合併。好比說對於重複的內容保留其中的一個,或者是對於不一樣的內容進行修正合併。修改完成以後要把上面標有着特殊含義的行都刪除,就是上面衝突分析指出那幾行。

  6. 修改完成以後,須要使用git add命令將這些文件都進行暫存,git add以後,Git就會將它們標記爲衝突已解決的狀態

  7. 隨後還可使用git status工具來確認是否全部衝突都已經解決。若是是,而且本身對於修改的結果都已經滿意,就可使用git commit命令來完成合並提交。

  8. 只有使用git commit 命令完成了合併提交,本次合併操做纔算完全完成了

5 分支管理

本小節主要是學幾個經常使用的分支管理的工具

git branch命令不僅是能夠建立和刪除分支。若是不加任何參數執行它,就會列出當前全部分支的列表,這跟git tag命令相似。

  • git branch命令列出當前全部分支。例如

    $ git branch
    	iss53
    	* master
    	testing
    • 其中帶*號的分支就是當前所在的分支或者說是HEAD所指向的分支。
  • git branch -v能夠看到每個分支的最後一次提交的信息。例如

    $ git branch -v
    	iss53 93b412c fix javascript issue
    	* master 7a98805 Merge branch 'iss53'
    	testing 782fd34 add scott to the author list in the readmes
    • 咱們能夠看到每個分支最後一次提交時的部分校驗和和提交說明
  • git branch後增長--merged--no-merged選項能夠過濾出當前全部的分支中已經合併到當前分支或者是沒有合併到當前分支的分支列表

    • --merged選項一般用來查看哪些分支是已經合併到當前分支的。而後能夠將這些多餘的分支進行刪除操做

    • --no-merged選項能夠選出那些尚未合併到當前分支的分支。若是咱們要對這些分支進行刪除操做,若是使用的是gti branch -d <branchName>會刪除失敗,由於它們包含了尚未合併的工做。若是非要刪除,能夠增長-D選項來進行強制刪除

    • 實際上這兩個選項還能夠在後面指定分支名,這時候過濾的就是已經合併到指定分支或者是尚未合併到指定分支的分支列表了。例如

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

      就是說列出當前尚未合併到master分支的分支列表,而後topicAfeatureB就是尚未合併到master分支的分支

    • 也就是說,若是其後有分支名,就過濾的是和指定分支是否合併的分支列表;若是沒有,就默認這個指定的分支是當前所在的分支。

6 分支開發工做流

這部分估計也就本人看得懂,你們看不懂的就湊個熱鬧好了,語言表達能力太貧乏,沒文化真就只能一句 ‘wo曹’走天下了

6.1 長期分支

長期分支的工做方式大概能夠描述爲:用一個分支保留最穩定的代碼,而後使用另外的分支將工做向前推動,工做進行到必定的階段,代碼變得穩定以後就併入穩定的分支,而後再轉換到其餘分支上繼續推動工做,如此反覆。這就至關於用一個分支用做版本發佈,不在其上面工做,使用另外的分支推動工做。它的方式至關於下圖

  • 這相似於流水線工做

  • 這就至關於多個分支在一線上跑,前面的是測試版,後面的是穩定版,前面的測試穩定了就將穩定版分支指針移到向前移動到當前位置,而後發佈新的穩定版,而後測試版又向前推動。

6.2 主題分支

主題分支是一種短時間分支。主題分支就是按照主題進行分支,每個分支的開發都有某一個單一目標和主題,當該分支達到了其目標功能或者是實現了它的主題,就將其合併回到主幹分支。

  • 就好比說,我想開發某一個功能,那我就建立一個分支來開發這個功能,這個分支的建立就是用來在其上完成該功能的開發。我一旦想開發一個新功能,我就建立一個新分支,而後再這個分支上的工做就圍繞這個主題(功能)展開,而一旦完成了這個功能,就把這個分株合併回到主幹分支。

6.3 長期分支和主題分支的區別

  1. 長期分支的意思大概就是說,一個項目長期保持着多個分支,而後這些不一樣的分支指向不一樣穩定性的階段性工做成果,這些分支沒有明顯的主題,實際上,全部的功能都是在一條線上開發的,一個分支開發多個功能甚至是全部功能。分支的主要目的就是,用一些分支來開發新功能,開展新工做,而另一些分支純粹是爲了保存穩定的版本內容,用做向公衆發佈。
  2. 主題分支的一個顯著的特色就是,按照主題來建立分支,每個分支有着明確的主題和任務,任務完成後就合併到主幹分支。沒有新功能或者新主題的時候可能項目只有一個分支,只有加入新主題,才建立新分支,分支的任務完成以後就將其與主幹分支合併而後將其刪除。

7 遠程分支

  1. 咱們上面的全部操做,所建立的所有分支都是存儲在本地的,咱們都沒有和服務器發生交互

  2. 遠程引用是對遠程倉庫的引用(指針),包括分支、標籤等。

    • 咱們能夠經過git ls-remote <remoteRepoditory>來顯示得到遠程引用的完整列表。例如

      $ git ls-remote con
      	2fb972523cbca9fbf6cd3730b34fb38af552533d        HEAD
      	2fb972523cbca9fbf6cd3730b34fb38af552533d        refs/heads/master
    • 或者是git remote show <remoteRepository>來獲取遠程分支的更多信息。例如

      $ 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)
  3. 遠程跟蹤分支是遠程分支狀態的引用,它們是咱們沒法移動的本地指針。一旦咱們進行網絡通訊,Git就會自動移動這樣的指針,以精確反應遠程倉庫的狀態。這樣的分支記錄了咱們最後一次鏈接到遠程服務器上的遠程倉庫時,遠程庫上的該分支所處的位置。

    • 遠程跟蹤分支,顧名思義就是用來跟蹤遠程倉庫上的分支位置,保存在本地上的指針。這個指針的特別之處就在於它不能認爲移動,是在和相應的遠程庫鏈接後Git進行自動移動的,即將該指針在本地移動到和遠程庫相應的分支指針相同的位置。至關於自動同步

    • 這樣的分支以<remoteRepository>/<branchName>的形式命名。若是咱們向查看咱們最後一次鏈接該遠程庫時該分支所在的位置,咱們就能夠以<remoteRepository>/<branchName>的形式查看該分支的信息。

    • 咱們在對一個倉庫進行克隆的時候,Git的clone命令會自動爲咱們建立一個表示該遠程庫的簡寫名origin,而後拉取它的全部數據,而後自動建立一個指向遠程庫master分支的指針origin/master,這就是一個遠程跟蹤分支。同時,Git還會爲咱們克隆的本地庫建立一個本地分支master,它和origin/master指針指向相同的地方。master分支由咱們本身控制,就是一個普通的本地分支,origin/master由Git自動控制,只有鏈接到相應的遠程庫而且遠程庫上的master分支移動了,該指針纔會移動,而且會自動移動到和遠程庫的master分支當前所在位置一致的地方。

      • origin這一個名稱和master同樣,沒有什麼特殊的含義,只是由於clone命令會默認生成這樣的一個名稱。
    • <remoteRepository>/<branchName>這個指針是保存在本地的,可是使用Git自動控制的,只有當本地和remoteRepository鏈接後,該指針的位置纔會更新,它指向的位置老是最後一次和遠程服務器鏈接以後相應的分支的最新位置。只要不和遠程服務器鏈接通訊,他就不會變

    • 小結 上面所說的內容涉及了三個分支

      • 遠程服務器上的對應遠程庫上的master分支,這一分支是位於遠程服務器上的
      • 本地倉庫中的masteroringin/master分支,這兩個都是在本機上的
      • 位於本機上的兩個分支,實際上都是指向本地倉庫的提交歷史。master分支是由咱們本身掌控的用來進行工做的,而origin/master分支是指向本地歷史的,可是是Git自動控制的,它用來追蹤遠程服務器上的master的狀態,所以只有和遠程服務器進行通訊的時候它才能得到遠程服務器上的master分支的最新位置,也才能進行更新。
      • 所以,遠程服務器上的master分支的推動不會影響本地的master分支的工做,本地master分支的推動也不會改變origin/master分支的位置。一旦鏈接到遠程服務器,origin/master就會和遠程服務器上的master分支同步,斷開鏈接以後,origin/master分支就會停在這樣的位置不動,直到下一次聯機同步。
  4. 這裏補充一下,上面所說的遠程跟蹤分支的自動更新並不是真的不須要任何操做Git就會幫咱們作好。實際上咱們要將origin/master這樣的遠程跟蹤分支進行更新,必需要手動執行git fetch <remoteRepository>命令,這時候Git纔會幫咱們自動抓取遠程服務器上的master更新的內容到本地,而後自動移動origin/master指針與之同步。`也就是說,若是咱們不手動拉取遠程庫上的信息,Git是不會幫咱們自動拉取這些更新的內容下來的。這也很好理解,由於咱們鏈接到遠程服務器不老是想要更新咱們本地的數據的

7.1 推送

  1. 當咱們想要公開分享一個分支的時候,咱們須要將其推送到有寫入權限的遠程倉庫上。本地的分支並不會自動地和遠程庫進行同步,**咱們必須顯示推送咱們想要分享的分支。

  2. 這樣的好處就是咱們能夠自行決定咱們想要分享和不想分享的分支。咱們能夠只把咱們想要分享的分支推送到遠程庫上。

  3. git push <remoteRepository> <branchName>命令就是用來推送分支到遠程庫上的

    • 在咱們執行這樣的一條命令以後,Git實際上會自動幫咱們把branchName展開成refs/heads/<branchName>:refs/heads/<<branchName>

    • 實際上咱們也可使用這樣的一個命令來推送咱們的分支

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

      這樣就能夠把一個本地分支推送到遠程庫並能夠給它起一個和本地分支名不一樣的名字

  4. 在咱們把分支推送到遠程服務器上以後,別人就能夠從服務器上抓取這一分支的內容到本地

    • 咱們已經直到,git fetch <remoteRepository>命令只會把遠程庫的數據下載到本地,可是不會將這些數據自動合併到本地倉庫

    • 因此,若是咱們經過該命令抓取到了一個新的分支,它也不會自動合併到咱們當前所在的分支,而是在本地多了一個<remoteRepository>/<branchName>指針,也就是遠程跟蹤分支

    • 咱們能夠將這一分支和某一個分支進行合併,這和上面講述的本地庫的分支的合併無什麼區別

      • 指定分支名,就把遠程跟蹤分支合併到指定的分支

        $ git merge <localBranch> <remoteRepository>/<branchName>
      • 不指定分支名,就將遠程跟蹤分支和當前分支進行合併

        $ git merge <remoteRepository>/<branchName>
    • 若是想要在一個本身本地的分支上工做,可是這些工做創建在遠程跟蹤分支之上,則咱們可使用下面的命令

      $ git checkout -b <localBranch> <remoteRepository>/<branch>
      • 這一命令就會給咱們一個用於工做的本地分支<localBranch>,分支的起點位於<remoteRepository>/<branch>
      • 這時候實際上咱們就創建了一個跟蹤分支``<localBranch>

7.2 跟蹤分支

  1. 從一個遠程跟蹤分支上檢出(也就是執行的git chechout命令)一個本地分支會自動建立所謂的**跟蹤分支**(它跟蹤的分支叫作**上游分支**)。

  2. 跟蹤分支是一個與遠程分支有直接關係的本地分支。若是在一個跟蹤分支上輸入git pull,Git能自動識別要到哪個服務器上去抓取數據,而後合併到哪個分支

  3. 在咱們克隆一個倉庫的時候,Git會自動建立一個跟蹤origin/master分支的master分支

  4. 咱們也能夠自行設置跟蹤分支或者不跟蹤哪個分支,咱們還能夠設置一個在其餘遠程倉庫上的跟蹤分支

  5. 設置跟蹤分支的方法

    • git checkout -b <branchA> <remoteRep>/<branchB>
    • git checkout --track <remoteRep>/<branch>這個命令直接建立一個和遠程分支同名的跟蹤分支
    • 若是咱們在檢出一個分支的時候,該分支名不存在,而又恰好這時候在本地記錄的全部遠程庫中只有一個遠程庫有一個分支叫這個名字,這個時候Git就會爲咱們自動建立一個叫這個名字的跟蹤分支,跟蹤的是上文所說的惟一具備同名的遠程分支。即git checkout <branch>
  6. 上面設置跟蹤分支的方法都是新建分支來進行跟蹤的。若是想要使用一個已經存在的分支來跟蹤剛剛抓取下來的遠程分支,或者說是想要修正正在跟蹤的上游分支,可使用-u或者是--set-upstream-to選項運行git branch來顯式設置。例如

    $ git branch -u origin/master

    命令就將當前分支設置爲origin/master的跟蹤分支

  7. 上游快捷方式 設置好跟蹤分支以後,咱們可使用@{upstream} or @{u}來引用它的上游分支。好比說咱們正處在master分支上,而且該分支正在跟蹤origin/master分支咱們可使用

    $ git merge @{u}

    來取代

    $4 git merge origin/master
  8. 若是想查看設置的全部跟蹤分支,可使用git branch-vv選項。這會將全部的本地分支列出來而且包含更多的信息,好比每個分支正在跟蹤哪個遠程分支,以及本地分支是領先、落後,仍是都有。例如

    $ 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表明領先的版本數。這意味着本地還有兩個提交沒有推送到遠程服務器上

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

    • 接下來還看到serverfix分支正在跟蹤origin/server-fix-good分支,而且領先3落後1,behind就是落後的意思,其後的1就是落後的版本數。這意味着本地有3次提交沒有推送到遠程服務器上,遠程服務器上有一次提交沒有被併入本地分支

    • 最後看到testing沒有跟蹤任何分支

    • **注意** 這裏的領先和落後的次數都是拿本地的跟蹤分支和其跟蹤的遠程跟蹤分支進行比較的,也就是比較的是本地跟蹤分支和最後一次鏈接遠程庫時更新的遠程跟蹤分支的數據

    • 想要統計最新的領先和落後次數,必須在查看以前先對全部的遠程分支進行抓取。可使用下面的命令

      $ git fetch --all

7.3 拉取

  1. 使用git fetch命令會把服務器上有而本地沒有的數據抓取到本地,可是這些數據不會自動合併到本地的跟蹤分支上,而是保存在遠程倉庫的本地引用中,咱們須要使用git merge進行手動合併
  2. 若是咱們已經設置好了相應的跟蹤分支,此時就可使用git pull自動抓取服務器上的數據,而後Git會自動將這些抓取到的內容找到相對應的本地的跟蹤分支來進行合併。
  3. 一般建議使用git fetchgit merge而不是直接使用git pull

7.4 刪除遠程分支

  1. 若是咱們已經經過哦遠程分支完成了全部的工做,而且將遠程分支的內容和服務器上的master分支(或者是其餘的分支)進行了合併。**也就是說在服務器上的某一個分支的工做已經完成而且已經和服務器上的master分支(或者其餘分支)進行了合併,則該分支至關於多餘的可刪除的分支。**而後咱們就能夠將它刪除了。

  2. 刪除一個遠程分支的命令是

    $ git push <remoteRep> --delete <RemoteBranch>
    • 該命令會將RemoteBranch從遠程服務器上刪除
  3. 實際上上面的刪除命令只是將服務器上的這個表明某一個分支的指針刪除了,而分支上的數據Git一般會將其保留在服務器上一段時間,直到垃圾回收運行。因此若是一不當心刪除了一個遠程分支,一般是很容易就能夠恢復的。

8 變基

在Git中整合來自不一樣分支的修改主要有兩種方法:mergerebase。本節主要就是``rebase(變基)

8.1 變基的基本操做

  1. 回顧前面的分支的合併的方式,對於以下的兩個分支

  2. 使用merge命令將兩個分支合併。合併的過程能夠描述爲:Git會把兩個分支的最新快照(C4C3)以及兩者的最近的共同祖先(C2)進行三方合併,而且爲合併的結果造成一個新的提交,以下圖

  3. 這裏介紹一種新的合併方法——變基:咱們能夠提取在C4中引入的修改和補丁,而後在C3的基礎上再應用一次C4的修改和補丁。這樣的操做再Git中就被成爲變基

  4. 咱們可使用rebase命令將提交到某一個分支上的全部修改都轉移到另一個分支上,就好像在另一個分支上面從新播放一遍這個某一個分支上面的全部修改同樣。

  5. 對於上面的例子,咱們能夠checkout``experiment分支,而後把它變基到master分支上。這隻須要執行以下的命令

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

    • 而後對比當前分支experiment相對於該祖先的歷次提交,而後提取相應的修改並保存爲臨時的文件。也就是說,找到最近的共同祖先以後,使用一個文件保存下來當前分支experiment的歷次提交的修改內容。

    • 而後將當前分支experiment指向目標基底分支的最新一次的提交C3,最後將上面臨時保存的文件中的歷次修改依次應用。這就至關於在master分支上在其最新的提交以後依次從新提交一遍experiment上自與master最近共同祖先以後的歷次修改。過程以下圖所示

    • 而後,咱們還須要回到master分支上執行一次快進合併

      $ git checkout master
      	$ git merge experiment
    • 這就有點相似於長期分支的流水形式。也就是將另一個分支所作的工做搬到想要併入的分支的最前面,而後再將master指針向前移動。以下圖所示

  6. 執行變基以後,Figure 38C4'指向的快照實際上和Figure 36中的C5指向的快照是如出一轍的。這兩種合併的方法的最終合併結果並無什麼差異。可是變基使得提交歷史變得更加整潔。

  7. 當咱們查看通過一個變基以後的分支的提交歷史時咱們會發現,儘管實際的開發工做是並行的,但它們看上去就好像是串行的同樣,提交的歷史是一條直線而沒有分叉。也就是說,通過變基以後的分支的提交歷史再也不是實際的像Figure 36中的樣子,而是像Figure 38中同樣,把另一個分支的提交歷史接在了併入到的分支的提交歷史的前面。

  8. 咱們使用變基的目的通常是爲了確保在向遠程分支推送時能保持提交歷史的整潔。例如咱們要爲遠程服務器中的某一個項目貢獻代碼,咱們如今咱們的本地分支上進行修改,而後再把這些修改變基到遠程的某一個分支上,這樣,遠程倉庫的代碼維護者就不用進行整合工做,只須要快進合併便可。

  9. 注意 不管是三方合併仍是變基,合併的最終結果所指向的快照是同樣的,只不過是提交歷史不一樣而已。變基是把一個分支的提交歷史依次應用到另外一個分支,而三方合併是將兩個分支的最終結果合在一塊兒。變基的提交歷史是一條直線,三方合併則會有分叉。

8.2 更有趣的變基例子

這個例子實際上可不是爲了有趣而已。

  1. 對兩個分支進行變基時,所生成的重放並不必定要在目標分支上應用,也能夠應用到另一條分支上。

  2. 例以下面的例子,一個項目的各個分支的提交歷史以下

  3. 假設此時咱們想要將client中的修改合併到master中併發布,可是暫時並不想合併server中的修改,可能這個分支的工做尚未完成。

  4. 這時候咱們就可使用git rebase命令的--onto選項,選擇那些在client分支中可是不在server分支中的修改(也就是C8C9),將它們在master上重放,即

    $ git rebase --onto master server client
    • 咱們能夠看到該命令有三個參數

    • master爲進行進行重放的分支(第一個參數

    • server至關於兩個分支在進行變基時的基底分支(第二個參數

    • client分支就至關於進行變基的兩個分支中的當前分支,是用來提取修改和補丁的(第三個參數

    • 這一個命令的意思就是:取出在client上的,和server分叉以後的補丁,而後將這些補丁在master上重放一遍,讓client看起來像是直接基於master進行了修改同樣

    • 其結果以下圖

  5. 而後能夠快進合併master分支了

    $ git checkout master
    	$ git merge client

  6. 這上面的例子就說明了在開頭說的**「對兩個分支進行變基時,所生成的****重放****並不必定要在目標分支上應用,也能夠應用到另一條分支上」**這句話的含義。上面的例子其實是server分支和client分支進行變基,可是卻將變基的重放應用在了master分支上。

  7. 接下來咱們決定把server分支也合併到master分支上,這個時候的變基實際上就和普通的變基沒什麼區別了。這裏介紹一個更加便捷的命令,以下所示

    $ git rebase <baseBranch> <topicBranch>

    對於本例,就是

    $ git rebase master server

    該命令就省去了要先切換到server分支,而後提取其補丁變基到master分支上,這個命令執行以後,結果以下圖所示

  8. 接下來咱們就能夠進行快進合併了

    $ git checkout master
    	$ git merge server
  9. 至此clientserver分支都已經合併到了master分支裏面。接下來咱們就能夠刪除這兩個分支了,最終的提交歷史就會變成下圖所示的樣子

8.3 變基的風險

  1. 變基操做的實質是丟棄一些現有的提交,而後相應地新建一些內容同樣可是實際上不一樣 的提交

  2. 若是咱們將倉庫上的內容推送到了遠程服務器,而其餘人又從這個遠程庫中拉取了這些提交併進行後續工做,可是以後你使用rebase命令從新進行了整理再次提交推送,那麼這時候,其餘人就不得不從新拉取你的推送和他們正在進行的工做進行整合。若是接下來咱們又拉取並整合了他們所作的提交,這時候,事情就會變得一團糟。

  3. 咱們用一個例子來講明這件事

    • 假如咱們從一個服務器上克隆了一個倉庫進行開發。提交歷史以下圖

    • 隨後有某我的向服務器推送了本身的修改,這其中包括一次合併

    • 而後你把這些提交的內容拉取下來並和本地倉庫進行了合併,咱們本地庫的提交歷史就會變成下面這樣

    • 接下來這我的決定把剛纔的合併撤銷,採用變基的方式從新合併,而後使用git push --force命令從新推送到服務器上覆蓋了上一次合併的提交歷史,這時候服務器上的歷史就變成了這樣

    • 而後咱們又從服務器拉取更新,這時候咱們發現多出來了一些新的提交

    • 這就使得咱們雙方的處境都變得很尬尷。若是咱們執行git pull命令,結果就像下圖這樣

    • 這個時候就至關於咱們又把相同的內容進行了依次合併,生成了一個新的提交。此時若是咱們執行git log會發現,這兩個提交的的做者,日期和日誌都是同樣的,這就會使人感到混亂

    • 若是咱們也推送咱們的內容到服務器,那麼那些因爲變基而被拋棄的提交又變回來了,這更加令人感到混亂。

8.4 用變基解決變基

用魔法戰勝魔法?太強了吧。老爹說的果真沒有錯

  1. 實際上,Git除了對整個提交計算SHA-1校驗和以外,也會對本次提交所引入的修改計算校驗和,即patch-id

  2. 若是咱們抓取被覆蓋過的更新並將咱們手頭的工做基於此進行變基的話,Git通常均可以分辨出哪些是咱們的修改,並把它們應用到新的分支上。

  3. 例如,對於上面的例子,若是咱們在抓取了新的推送數據後不是使用三方合併,而是執行git rebase teamone/master的話,Git將會:

    • 步驟1:檢查哪些提交是咱們的分支上獨有的(C2, C4, C3, C6, C7
    • 步驟2:檢查上一個步驟篩選出的提交結果中不是合併操做結果的提交(C2, C4, C3
    • 步驟3:再檢查上一步篩選出的提交結果中那些在對方進行覆蓋更新時沒有被歸入目標分支的提交(只有C2C3,由於C4實際上就是C4’
    • 步驟4:把步驟3篩選出的最終提交結果依次應用到teamone/master
  4. 最終就獲得了`在一個被變基而後強制推送的分支上再次執行變基以後的結果以下圖所示

  5. 要想上述的方案有效,前提是對方在進行變基時C4C4’幾乎是同樣的。不然變基操做就沒法識別它們是相同的。而後就會建立一個新的相似C4的補丁,而這個補丁的內容又和C4’發生衝突,從而沒法整潔地整合入歷史

  6. 對於本例的另一個簡單的方法是咱們在拉取更新時,使用git pull --rebase而非直接git pull。又或者是先git fetch而後再git rebase teamone/master

  7. 若是咱們習慣使用git pull,可是有但願默認使用--rebase選項。咱們能夠執行下面這條語句來更改pull.rebase的默認配置

    $ git config --global pull.rebase true
  8. 總之,咱們要記住這樣的一條原則:若是咱們的提交會存在於咱們的本地倉庫以外,同時別人又可能會基於這些提交作開發,那麼咱們這個時候就不該該使用變基

  9. 實際上就是說,不該該出現上面例子中的那種狀況,先推送了提交,以後又回滾,變基以後又從新強制推送。咱們能夠在咱們的本地庫中在作完工做以後先執行完變基,而後再推送到遠程服務器上,這樣就不會給別人形成混亂

8.5 變基 vs. 合併

是使用變基仍是使用合併,這彷佛是一個哲學問題

  1. 支持使用合併而不是變基的人認爲:倉庫的提交歷史就應該如是記錄開發的每一步,它就像歷史同樣,能夠爲之後的開發提供參考,而篡改歷史是一種褻瀆。因此即便是咱們的提交歷史是一團糟,也應該如實記錄下來,以供後人查閱。
  2. 而支持使用變基而不是合併的人則認爲:倉庫的提交歷史應該像一本故事書,它能夠清晰地告訴人們這個項目的開發故事。所以提交的歷史應該是邏輯清晰的,就像是故事書同樣,人們一般纔不關注咱們寫這本書的時候的草稿,而咱們也不該該將一篇草稿發佈給別人看,而是應該僅僅向公衆發佈咱們的修訂好的版本。
  3. 因此,是使用合併仍是使用變基,沒有一個強制性的規定,咱們應該結合實際狀況進行明智地選擇。咱們只要記住使用變基時候應該遵循的原則就不會錯誤地使用變基,在那種既可使用變基也可使用合併的場合,選用哪一種方式,這就取決於我的了,取決於你的處世態度了。

9本章小結

9.1 分支的建立

  1. 建立一個新分支

    $ git branch <branchName>
  2. 查看各個分支指向的提交對象

    $ git log --decorate

9.2 切換分支

  1. 切換到一個分支

    $ git checkout <branchName>
  2. 查看項目的整個提交歷史,項目的分支分叉狀況,以及各個分支的指向

    $ git log --oneline --decorate --graph --all
  3. 新建分支的同時切換到該分支

    $ git checkout -b <branchName>

9.3 分支的新建與合併

  1. 分支的合併

    $ git merge [<A>] <B>

    B併入AA能夠省略,省略就默認併入當前分支

  2. 分支的刪除

    $ git branch -d <branchName>
  3. 遇到衝突時候的合併

    • 合併的時候遇到衝突

      $ git merge [<A>] <B>
    • 查看全部衝突文件

      $ git status
    • 修改衝突文件並加入暫存區標記爲已解決衝突狀態

      $ git add <file>
    • 提交修改後的衝突文件完成合並

      $ git commit

9.4 分支管理

  1. 列出當前全部分支

    $ git branch
  2. 查看當前全部分支的最後一次提交的信息

    $ git branch -v
  3. 分支的過濾

    • 過濾出那些已經和指定分支合併過的分支

      $ git branch --merged [<branchName>]
      • 若是省略後面的分支名,就是過濾出已經和當前分支合併過的分支
      • 不然就是過濾出已經和branchName分支合併過的分支
      • 這些分支可使用git branch -d <branchName>命令進行刪除
    • 過濾那些尚未和指定分支合併過的分支

      $ git branch --no-merged [<branchName]
      • --merged相似,過濾的是尚未和指定分支合併的分支
      • 這些過濾出來的分支要使用-D選項才能進行強制的刪除

9,5 遠程分支

  1. 獲取遠程引用的完整列表

    $ git ls-remote <remoteRep>
  2. 獲取遠程分支的更多信息

    $ git remote show <remoteRep>
  3. 更新遠程跟蹤分支

    $ git fetch <remoteRep>
  4. 推送一個分支到遠程倉庫上

    $ git push <remoteRep> <localBranch>[:<remoteBranch>]
    • 後面的第二個參數爲遠程分支名,省略的話就和本地分支同名
  5. 抓取遠程庫的內容到本地

    $ git fetch <remoteRep>
  6. 將遠程跟蹤分支的內容和本地分支合併

    $ git merge [<localBranch>] <remoteRep>/<remoteBranch>
    • 若是不指定本地分支名,就默認爲當前分支
  7. 建立一個本地分支,而且其起點在和一個遠程跟蹤分支相同

    $ git checkout -b <newBranch> <remoteRep>/<branch>
  8. 設置一個跟蹤分支

    • 建立一個分支跟蹤遠程分支

      $ git checkout -b <brancha> <remoteRep>/<branchb>
    • 建立一個和遠程分支同名的跟蹤分支

      $ git checkout --track <remoteRep>/<branch>
    • 方式3

      $ git checkout <branch>
      • 若是branch不存在,可是遠程引用中有惟一一個叫作branch的分支,該命令就會建立一個跟蹤分支branch且它的上游分支就是上面說的惟一同名的遠程分支
  9. 爲一個已經存在的分支設置上游分支使其成爲一個跟蹤分支

    $ git branch -u/--set-upstream-to <remoteRep>/<branch>
  10. 訪問跟蹤分支的上游分支的快捷方式

    • 設置好跟蹤分支後,可使用@{u}或者是@{upstream}來引用上游分支。例如

      $ git merge @{u}
    • 就至關於

      $ git merge <remote>/<branch>

      其中<remote>/<branch>就是當前分支的上游分支

  11. 查看設置的全部跟蹤分支的信息

    $ git branch -vv
  12. 抓取全部的分支數據

    $ git fetch --all
  13. 拉取

    $ git pull <remoteRep>/<branch>
  14. 刪除一個遠程分支

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

9.6 變基

  1. 將一個分支變基到指定分支

    $ git rebase <branchA> [<branchB>]
    • 省略branchB就默認將當前分支變基到branchA分支上
    • 不然就是將branchB分支變基到branchA
  2. 變基以後進行快進合併

    $ git merge <baseBranch>
  3. 將兩個分支的變基的重放應用到另一個分支上

    $ git rebase --onto <branchA> <branchB> <branchC>
    • branchBbranchC兩個分支的重放應用到branchA
    • 具體是將branchBbranchC分叉後屬於branchC而不屬於branchB的提交歷史在branchA上依次重放
  4. 用變基解決變基

    $ git rebase <remoteRep>/<branch>
    • 這個命令最終會將在本地庫獨有的那些提交歷史應用到<remoteRep>/<branch>

END

相關文章
相關標籤/搜索