Git幕后的“故事”
來自: http://blog.csdn.net/dc_726/article/details/50639745
因為做操作系統實驗的原因,所以通讀了一遍 《Understanding git conceptually》 ,覺得確實不錯,于是就簡單地記錄一下。有的地方理解的還不是很深,可能不夠準確,等抽時間好好讀一下《Pro Git》。
作者開篇說到:僅僅記住在什么時候用什么命令是不夠的,出問題只是早晚的事。只有理解了Git的工作原理,才算真正學會Git。遺憾的是大部分網上的教程都只是教你在何時使用哪個命令,然后讓你去模仿。說得這么好,那就看看作者這篇教程是否把Git的工作原理講清楚了。注意: 以下1.2.1到1.2.3都是本地化操作 ,不要看到倉庫、分支、合并等詞語就以為一定是遠程操作了,1.2.4才會講到多人協同開發時的遠程操作。
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在背后到底做了什么:
- 修改一些代碼
- git log 查看歷史記錄:顯示從HEAD到初始提交的所有提交日志。用log命令顯示出每個提交對象的SHA-1簽名,我們就能控制HEAD的移動。
- git status 查看修改文件列表:顯示當前項目狀態與HEAD發生變化的文件列表。
- git diff 比較修改內容:顯示當前項目狀態與HEAD發生變化的文件內容。
- 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”,當每個開發者都在自己的分支上提交時是不用擔心任何東西的,因為你不會影響到其他人!
繼續上面提交三次的那個例子,我們首先用 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
現在繼續看常用命令,腦海里一定從圖的角度思考:
- 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的代碼合并過程如下:
- 找到HEAD和fix-headers的共同祖先,假設叫ancestor,先看兩種簡單情況:
1.1 如果ancestor是fix-headers,則什么都不做
1.2 如果ancestor是HEAD,則執行fast-forward-merge - 否則,比較出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] 下載分支代碼