近期公司由svn轉向了git,遷移過后發現周邊同事各種不適應,對於分支理解不到位,胡亂操作出現各種問題。所以我覺得有必要跟大家說一些有關git的知識。這篇文章不涉及基礎操作,也不涉及git命令,只涉及一些對git操作的理解,希望大家學完后在運行git命令的時候,知道自己大概做了些什么。以下都是出自本人的理解,如有偏差,還請指正。
我們以本地git庫為例,由於概念是相同的,所以理解本地git庫后遠程庫也能自然理解。
點、線、樹
點-從提交說起
提交大家都很清楚,就是把自己的改動,提交到版本庫生成一個保存點,在任何時候我們都能回到這個點來,就跟單機游戲存檔一樣。git會根據你的提交內容,生成一個hash值,並保證每個提交的標識(hash值)都是獨一無二的。那么只要你能提供這個標識,就肯定能找到你的這個提交。我們先不要去想各保存點間的關系如何,就記住一個概念:提交就是生成一個獨一無二的保存點。如下圖的三個提交。
線-建立父子關系
那么我們在一個版本(也就是一個保存點)的基礎上,提交了另一個版本。這樣我們就存在兩個保存點了,而我們知道這兩個點其實是有關系的,git的做法是每個保存點都會記錄他是基於哪個保存點之上的,我們可以稱之為父保存點。這樣就相當於將兩個點用線連接了起來,多次基於最新版本建立新的保存點,這樣就行成了多個點連結而成的長線。而基於這條長線,我們就能輕易找到各個點之間的關系。
如圖可知。A1是基於A0修改而來,A5是基於A0修改5次后而來。
樹-怎么理解分支
大家都知道建立一個git庫之后,庫里會自帶一個master分支,而我們經常也會說,開發的時候拉一個分支出來,那分支到底是一個什么東西呢?
其實很簡單,分支就是一個指針!(只是它記錄的不是內存地址,而是我們之前說到的保存點獨一無二的hash標識)
在上面我們知道了多個版本是怎么連結起來的,假設我們上圖就是在master分支上干的,應該怎樣表示呢?直接一個指針指向就完事了。
git有個優點就是建立分支非常之快,那為什么快呢?因為就只要加一個指針就完事了。如下圖,我們新建一個develop分支,其實就是加了一個指針指向我們創建分支的版本,這里我們就是基於A5版本創建的develop分支。
分支怎么來的弄清楚了,那么git怎么知道我們現在在哪個分支上呢?這里就要引入一個非常重要的指針了,HEAD指針。HEAD會指在你當前所在的分支上。假設我們當前在develop分支上,如下圖。(有一點需要說明的是HEAD指針不一定是指向分支的噢,那就是我們有時候會遇到的游離指針了,后面會提到的。)
假設我們在develop上又提交了多次,會怎么樣呢?如圖。
這時我們切到master上再提交多次呢?如圖。
那么如果兩個分支合並呢?誰向誰合不重要,過程都是一樣的。假設我們這時要把develop的分支內容合到master分支上去,那么我們要在master分支上執行合並命令,git會在合並內容后(假設沒有沖突)在master上建立一個合並提交點,並將master指針指向最新提交。如下圖。
刪除分支就不畫圖了,刪除相應的指針即可。不過master分支是不可刪除的。
一個倉庫經過長時間多次迭代之后,我們的git歷史提交就會形成一個我們常見的提交樹了。
以上就是git的日常使用中需要理解的一些操作了。
git子模塊
開發過程我們經常會遇到一種情況,有一個模塊(庫)為多個項目引用。那么在進行版本管理的時候就會出現一些麻煩,是將公共模塊單獨存放,還是拷貝多份每個項目放一份?這兩種方式都有其利弊,git子模塊在這種情況下就派上用場了。
子模塊就是將要用的公共代碼放到一個或幾個單獨的git庫里,然后需要使用它的父模塊建立一個類似Linux下的軟鏈接指向這個庫就好啦。
那么如何理解子模塊這個功能?我把它看成一個庫中庫,也就是說一個git庫中包含着另一個git庫。那么順其自然的,想操作子模塊的話,就進入子模塊中按日常操作即可。當然git也提供了一些命令,可以直接在父模塊中操作子模塊。
其中需要特別提出的一點是關於父子模塊是怎樣關聯起來的。簡單來說就是父模塊中存儲了與之相關聯子模塊的版本號。因為父模塊中只是記錄了子模塊的版本號,所以當我們用git clone --recurse-submodules將父模塊倉庫拉下來后,切到子模塊所在的目錄中,你會發現此時的HEAD是指向特定的版本號,而不指向任何一個分支,這就是游離分支了。
因為這個游離分支的存在,導致了下面這種坑爹情況的存在:
你在修改父模塊的時候,順帶修改了子模塊,提交時又沒注意子模塊游離分支的告警,強行提交。這樣的情況下,子模塊由於HEAD不指向任何一個分支會導致推送遠端失敗。父模塊推送成功后會與一個不存在的版本號相關聯。導致當另一個人去拉取此倉庫時就會出錯。所以一定要牢記,除非你清楚自己在做什么,否則對於任何操作都要在分支上進行。
另外,修改后推送遠端時千萬要記得將父子模塊都推送。
如果覺得太復雜,那么就記住以下的總結吧:
別人更新了子模塊,需要拉取
- 進入子模塊,進行拉取操作。
- 進入父模塊,提交子模塊關聯版本更新,推送遠端。
我要更新子模塊
- 進入子模塊,確認自己在分支上后,提交修改,推送遠端。
- 進入父模塊,提交子模塊關聯版本更新, 推送遠端。
別人更新子模塊后,我也要更新子模塊
- 進入子模塊,確認自己在分支上后,提交修改。
- 拉取別人的更新,合並后提交,推送遠端。
- 進入父模塊,提交子模塊關聯版本更新, 推送遠端。