Git幕後的「故事」

由於作操做系統實驗的緣由,因此通讀了一遍《Understanding git conceptually》,以爲確實不錯,因而就簡單地記錄一下。有的地方理解的還不是很深,可能不夠準確,等抽時間好好讀一下《Pro Git》。git

做者開篇說到:僅僅記住在何時用什麼命令是不夠的,出問題只是遲早的事。只有理解了Git的工做原理,纔算真正學會Git。遺憾的是大部分網上的教程都只是教你在什麼時候使用哪一個命令,而後讓你去模仿。說得這麼好,那就看看做者這篇教程是否把Git的工做原理講清楚了。注意:如下1.2.1到1.2.3都是本地化操做,不要看到倉庫、分支、合併等詞語就覺得必定是遠程操做了,1.2.4纔會講到多人協同開發時的遠程操做。markdown


1.Commit Object和Head

使用Git的目的固然就是管理項目文件的版本變化,在Git中保存全部管理信息的數據結構叫作倉庫(Repository)。能夠在一個文件夾中經過命令git init建立倉庫。倉庫保存在項目文件目錄下的一個叫作.git的文件夾中,它由兩部分組成:數據結構

  • 提交對象(Commit object):反映項目狀態的一組文件、對父提交對象的引用、當前提交對象的SHA-1簽名構成了提交對象。父提交對象的引用使得倉庫的提交對象造成了一個有向無環圖,咱們能夠一直遍歷到第一次提交。每當咱們要查詢或操做倉庫,都應該以如何操做commit object圖的角度去思考
  • 對提交對象的引用(Head):每一個Head都有個名字,默認每一個倉庫都有個叫作master的Head。此外,指向當前活躍Head的引用叫作HEAD(二級引用)。

例如,下面就是提交三次後的對象圖:分佈式

---->  time  ----->

(A) <-- (B) <-- (C)
                 ^
                 |
               master
                 ^
                 |
                HEAD

咱們平常的工做流程通常以下,下面就從圖的角度說明一下Git在背後到底作了什麼:fetch

  1. 修改一些代碼
  2. git log查看歷史記錄:顯示從HEAD到初始提交的全部提交日誌。用log命令顯示出每一個提交對象的SHA-1簽名,咱們就能控制HEAD的移動。
  3. git status查看修改文件列表:顯示當前項目狀態與HEAD發生變化的文件列表。
  4. git diff比較修改內容:顯示當前項目狀態與HEAD發生變化的文件內容。
  5. git commit -am "message"提交修改:建立提交對象,將HEAD做爲父提交對象。提交完成後,HEAD將指向剛建立的新提交對象。-a參數至關於git add,自動將修改文件添加到提交列表裏。

2.Branching

下面說說Git中的分支(Branch)。在Git中,Branch與Head幾乎是等同的。每一個Branch都由一個Head來表示。因此,咱們用Branch指分支的Head以及它的全部父對象構成的整個歷史,用Head指單獨一個提交對象,通常是分支中最近的提交。Git分支的最佳實踐是:用分支實現新特性,保證master(主幹)始終處於可發佈的狀態。Git用戶常常會說:」commits are cheap」,當每一個開發者都在本身的分支上提交時是不用擔憂任何東西的,由於你不會影響到其餘人!spa

繼續上面提交三次的那個例子,咱們首先用git branch [new-head-name] [reference-to]命令新建一個Head。其中,HEAD^表示HEAD的父級,若是未提供提交對象的話,默認爲HEAD。若是隻是執行git branch則會列出全部Head:操作系統

git branch fix-headers HEAD^

(A) -- (B) ------- (C)
        |           |
   fix-headers    master
                    |
                   HEAD

要開始在新分支上工做,就要將新建立的Head設置爲HEAD,能夠經過git checkout [head-name]完成。注意:checkout不僅會修改HEAD的指向,它還會重寫文件夾中的全部文件來匹配新HEAD指向提交對象所表示的項目狀態。因此,checkout以前最好提交全部修改。切換完成後,再提交後對象圖就變成了下面的樣子:日誌

+-------------- (D)
        /                 |
(A) -- (B) -- (C)         |
               | |
             master  fix-headers
                          |
                         HEAD

如今繼續看經常使用命令,腦海裏必定從圖的角度思考:code

  • git checkout [branch_name]切換分支:切換HEAD指向的位置
  • git checkout -t [branch_name]新建分支

3.Merging

當你在分支上完成開發時就須要將改動Merge回master,命令就是git merge [head]git pull . [head]。假設當前Head叫作HEAD,分支Head叫作fix-headers,則Git的代碼合併過程以下:對象

  1. 找到HEAD和fix-headers的共同祖先,假設叫ancestor,先看兩種簡單狀況:
    1.1 若是ancestor是fix-headers,則什麼都不作
    1.2 若是ancestor是HEAD,則執行fast-forward-merge
  2. 不然,比較出fix-headers在ancestor後的改動,將這些改動合併到HEAD
    2.1 若是沒有衝突,則建立一個新的提交對象,以HEAD和fix-headers爲父級,並將HEAD指向這個新對象,並更新項目文件
    2.2 若是有衝突,則不建立提交對象,插入衝突的標記,通知用戶處理
fast-forward-merge的例子:
                +-- (D) -- (E)
               /            |
(A) -- (B) -- (C)           |
               |            |
            current     fix-headers
               |
              HEAD

執行`git merge fix-headers`合併以後的樣子:
                +-- (D) -- (E)
               /            |
(A) -- (B) -- (C)           |
                            |
                    fix-headers, current
                                 |
                                HEAD
複雜狀況下的合併例子:
         +---------- (D)
        /             |
(A) -- (B) -- (C) -------------- (E)
                      |           |
                 fix-headers    master
                                  |
                                 HEAD

執行`git merge fix-headers`合併以後的樣子:
         +---------- (D) ---------------+
        /             |                  \
(A) -- (B) -- (C) -------------- (E) -- (F)
                      |                  |
                 fix-headers           master
                                         |
                                        HEAD

4.協同開發

前面講到過:Git的重要特色就是Repository與項目文件是保存在一塊兒的,因此Git能夠在無需連網的狀態下正常工做。可是這也意味着不一樣的開發者在默認狀況下是不共享Repository的。爲了實現共享,Git使用分佈式模型(Distribution Model)版本管理,既能夠無中心化也能夠有中心。

首先要訪問你朋友的遠程倉庫就須要一個位置,叫作remote-specification,Git能夠經過SSH、HTTP等協議對外提供訪問。而後就能夠經過git clone [remote-specification]下載遠程倉庫到本地了。除了簡單的拷貝,Git會給遠程Repository建立一個reference叫作origin,同時還會給每一個Head新建一個Head,名字以」origin/」開頭來區分

遠程倉庫的樣子:
                +---------------(E)
               /                 |
(A) -- (B) -- (C) -- (D)         |
                      |          |
                    master    feature
                      |
                     HEAD

你clone後的本地倉庫是這個樣子:
                +-------------- (E)
               /                 |
(A) -- (B) -- (C) -- (D)         |
                      |          |
     origin/master, master    origin/feature
                      |
                     HEAD

當遠程倉庫變化時,咱們能夠經過git fetch [remote-repository-reference]命令將改動抓取到本地,生成對應的提交對象,再用前面講過的git pull [remote-repository-reference] [remote-head-name]進行合併。其實,pull命令也會自動進行fetch,因此平時咱們直接使用pull就能夠了。來看一個例子,假設你朋友在跟你一塊兒開發,他本地的Repository變成了這個樣子:

+-------- (E) -- (F) -- (G)
               /                         |
(A) -- (B) -- (C) -- (D) -- (H)          |
                             |           |
                           master     feature
                             |
                            HEAD

在你執行fetch以後你的倉庫是這個樣子:
                +------------ (E) ------------ (F) ---- (G)
               /               |                         |
(A) -- (B) -- (C) -- (D) --------------- (H)             |
                      |        |          |              |
                    master  feature origin/master  origin/feature
                      |
                     HEAD

注意:你的Head並未受任何影響,變化的只是帶有"origin/"的Head。
如今就用pull命令合併,更新你的master和feature,完成後你的倉庫就成了這個樣子:
                +-------- (E) ------------- (F) ----- (G)
               /           |                           |
(A) -- (B) -- (C) -- (D) ------------ (H)              |
                           |           |               |
                        feature  origin/master,  origin/feature
                                     master
                                       |
                                      HEAD

與之相反,將本地修改發送到遠程倉庫使用git push [remote-repository-reference] [remote-head-name],遠程倉庫會負責提交對象的建立以及Head的合併移動等工做。要注意的是:向遠程倉庫push時,要求必須是fast-forward合併。

整理一下這部分的經常使用命令:

  • git pull [remote-repository-reference] [remote-head-name]下載分支代碼
相關文章
相關標籤/搜索