Git版本管理原理
目錄
?
Git工作區(qū)域
工作區(qū)間的關(guān)系
Git簡介
版本管理
本地執(zhí)行
文件狀態(tài)
git內(nèi)部原理
Git對象
塊
目錄樹
提交
引用
tag
HEAD
分支
.git 結(jié)構(gòu)說明
版本演變
分支
創(chuàng)建分支
切換分支
遠(yuǎn)程分支
跟蹤分支
合并(merge)
快進(jìn)合并(fast-forward)
合并沖突
變基(rebase)
變基沖突
交互式變基(interactive)
變基 vs. 合并
應(yīng)用提交(cherry-pick)
儲藏(stash)
參考
Git工作區(qū)域
1、工作目錄(Working Directory)
存放項目代碼的地方
2、暫存區(qū)(Stage/Index)
用于臨時存放改動,事實上它只是一個文件,保存即將提交到文件列表信息
3、資源庫(Repository或Git Directory)
倉庫區(qū)(或本地倉庫),就是安全存放數(shù)據(jù)的位置,這里面有提交的所有版本的數(shù)據(jù)。其中HEAD指向最新放入倉庫的版本
4、遠(yuǎn)程的git倉庫(Remote Directory)
遠(yuǎn)程倉庫,托管代碼的服務(wù)器。
?
工作區(qū)間的關(guān)系
Git簡介
版本管理
Git與其他版本管理工具最重要的不同點是:其他工具記錄下文件的變化情況(delta-based),而git是基于快照(snapshots),每次變更都會是存儲文件的快照。
delta-based方式:
快照流方式:
只有被修改過的文件才會在對應(yīng)版本產(chǎn)生出新的快照。所以每次提交所產(chǎn)生的快照不是整個文件系統(tǒng),而是被修改過的那部分文件。(上圖虛線圈出的文件表示沒有生成新的快照)
本地執(zhí)行
Git在每個用戶機(jī)器上都有一份完整的版本庫,所以在集中式版本控制系統(tǒng)通常所做的提交代碼、比較代碼、分支合并等工作在本地就可以完成,而且速度極快。但是不同開發(fā)者之間的代碼協(xié)作,還是需要通過網(wǎng)絡(luò)連接遠(yuǎn)程倉庫進(jìn)行交換的。
文件狀態(tài)
Git管理的文件存在三個狀態(tài):
- 已修改(modified):已修改表示修改了文件,但還沒保存到數(shù)據(jù)庫中
- 已暫存(staged):? 已暫存表示對一個已修改文件的當(dāng)前版本做了標(biāo)記,使之包含在下次提交的快照中
- 已提交(committed):已提交表示數(shù)據(jù)已經(jīng)安全的保存在本地數(shù)據(jù)庫中
git內(nèi)部原理
在git版本庫中,git維護(hù)兩個主要數(shù)據(jù)結(jié)構(gòu):對象庫(object store),索引(index)。
Git對象
對象庫是git版本庫實現(xiàn)的心臟,包含四種類型:
- 塊(blob,binary lare object)
- 目錄樹(tree)
- 提交(commit)
為了有效的利用磁盤空間和網(wǎng)絡(luò)帶寬名,git把對象壓縮并存儲在打包文件(pack file)里,這些文件也在對象庫里。
塊
文件的每一個版本表示為一個塊。一個blob被視為一個存儲任意數(shù)據(jù),且內(nèi)部結(jié)構(gòu)被程序忽略的變量或文件的黑盒。一個blob保存一個文件的數(shù)據(jù),但不包含任何關(guān)于這個文件的元數(shù)據(jù)(Metadata,描述數(shù)據(jù)的數(shù)據(jù))。
目錄樹
一個目錄樹對象代表一層目錄信息。它記錄blob標(biāo)識符、路徑名和在一個目錄里所有文件的元數(shù)據(jù)。它也可以遞歸引用其他目錄樹或子樹對象,從而建立一個包含文件和子目錄的完整層次結(jié)構(gòu)。
提交
一個提交對象保存版本庫中每一次變化的元數(shù)據(jù),每一個提交對象指向一個目錄樹對象,這個樹對象在一張完整的快照中捕獲提交時版本庫的狀態(tài)。
從提交流角度來看,提交對象會包含一個指向上次提交對象(父對象)的指針。
引用
引用指的是對提交記錄的引用,提交記錄用哈希值(SHA-1)唯一標(biāo)識,每個引用用一個文件表示,文件中保存其引用的提交記錄的哈希值,文件名為引用的名稱。本地分支名稱、遠(yuǎn)程跟蹤分支名稱和標(biāo)簽名都是引用。
heads目錄下有個名為master的文件,內(nèi)容為:1defc8eb6a0fb360438c9672cb31bbc2e46d621e
tag
Git 使用兩種主要類型的標(biāo)簽:輕量標(biāo)簽(lightweight)與附注標(biāo)簽(annotated)
一個輕量標(biāo)簽很像一個不會改變的分支 - 它只是一個特定提交的引用。
附注標(biāo)簽是存儲在 Git 數(shù)據(jù)庫中的一個完整對象。 它們是可以被校驗的;其中包含打標(biāo)簽者的名字、電子郵件地址、日期時間;還有一個標(biāo)簽信息;并且可以使用 GNU Privacy Guard (GPG)簽名與驗證
- 對應(yīng).git/refs/tags/目錄中的文件
- 不可變, 除非刪除后重新創(chuàng)建, 否則總是指向特定的提交記錄
- 每個git倉庫可以有多個tag
HEAD
- 對應(yīng).git/HEAD 。里面存儲的內(nèi)容:? ref: refs/heads/master
- 可變
- 通常指向某個本地分支,即引用的引用
- 也可以直接指向某個提交記錄,稱為HEAD detached, 即分離頭指針狀態(tài)
- 也可以指向tag,git將這種情況也處理成HEAD detached
- 也可以指向遠(yuǎn)端分支, git將這種情況也處理成HEAD detached
- 每個git倉庫只有一個HEAD
- 表示當(dāng)前工作區(qū)檢出的文件(或者說文件在修改之前)是屬于哪個提交記錄的
- git checkout 指令,就是在改變HEAD的指向
- git checkout 本地分支名
- git checkout 提交記錄哈希值, detached
- git checkout 遠(yuǎn)端分支名, detached
- git checkout tag名, detached
分支
- 可變, 在不同的時刻可以指向不同的提交記錄
- 本地分支
- 對應(yīng).git/refs/heads/<branchname>
- 每個本地倉庫有多個本地分支
- 遠(yuǎn)程分支
- 對應(yīng).git/refs/remotes/<遠(yuǎn)端倉庫名>/<branchname>
- 每個本地倉庫可以對應(yīng)多個遠(yuǎn)端倉庫, 同時每個遠(yuǎn)端倉庫可以有多個遠(yuǎn)端分支
.git 結(jié)構(gòu)說明
- HEAD 指示目前被檢出的分支
- index 保存暫存區(qū)信息
- config* 包含項目特有的配置選項
- description 僅供gitweb程序使用,用戶一般不需要關(guān)注。
- hooks 包含客戶端和服務(wù)端的鉤子
- info 包含全局排除(global excude)文件,存放那些不希望被記錄在.gitignore中的忽略模式
- objects 存儲所有數(shù)據(jù)內(nèi)容
- refs 存儲指向數(shù)據(jù)(分支)的提交對象的指針
版本演變
分支
Git僅有一個提交樹。而Git 的分支其實本質(zhì)上僅僅是指向提交對象的可變指針。 Git 的默認(rèn)分支名字是?master。 在多次提交操作之后,你其實已經(jīng)有一個指向最后那個提交對象的?master?分支。 它會在每次的提交操作中自動向前移動。
創(chuàng)建分支
?創(chuàng)建分支只是創(chuàng)建了一個可以移動的新的指針。HEAD指向當(dāng)前分支。
切換分支
切換分支就是將HEAD指針指向另一個本地分支。
從一次提交創(chuàng)建不同分支,之后又分別在這些分支上做出了新的提交。這時項目將會產(chǎn)生分叉提交歷史,多個提交將指向同一個父提交。這些分叉如果想要最終合并回原來的分支,就要通過合并或變基操作來解決。
遠(yuǎn)程分支
遠(yuǎn)程引用是對遠(yuǎn)程倉庫的引用(指針),包括分支、標(biāo)簽等等。
遠(yuǎn)程跟蹤分支是遠(yuǎn)程分支狀態(tài)的引用。 它們是你不能移動的本地引用,當(dāng)你做任何網(wǎng)絡(luò)通信操作時,它們會自動移動。 遠(yuǎn)程跟蹤分支像是你上次連接到遠(yuǎn)程倉庫時,那些分支所處狀態(tài)的書簽。
它們以 (remote)/(branch) 形式命名。 例如,如果你想要看你最后一次與遠(yuǎn)程倉庫 origin 通信時 master
分支的狀態(tài),你可以查看 origin/master 分支。
跟蹤分支
從一個遠(yuǎn)程跟蹤分支檢出(check out)一個本地分支會自動創(chuàng)建一個叫做 “跟蹤分支”(有時候也叫做 “上游分支”)。 跟蹤分支是與遠(yuǎn)程分支有直接關(guān)系的本地分支。 如果在一個跟蹤分支上輸入?git pull或git push,Git 能自動地識別去哪個服務(wù)器上抓取、合并到哪個分支。
當(dāng)克隆一個倉庫時,它通常會自動地創(chuàng)建一個跟蹤?origin/master?的?master?分支。 然而,如果你愿意的話可以設(shè)置其他的跟蹤分支 - 其他遠(yuǎn)程倉庫上的跟蹤分支,或者不跟蹤?master?分支。
跟蹤分支信息一般保存在local級別的配置文件當(dāng)中
branch.master.remote=origin
branch.master.merge=refs/heads/master
branch.dev_5.2.remote=origin
branch.dev_5.2.merge=refs/heads/dev_5.2
合并(merge)
我們想將出現(xiàn)分叉提交的分支整合在一起時,可以使用合并(merge)操作來完成。
Git 會使用兩個分支的末端所指的快照以及這兩個分支的工作祖先,做一個簡單的三方合并。
和之前將分支指針向前推進(jìn)所不同的是,Git 將此次三方合并的結(jié)果做了一個新的快照并且自動創(chuàng)建一個新的提交指向它。 這個被稱作一次合并提交,它的特別之處在于他有不止一個父提交。
Git 會自行決定選取哪一個提交作為最優(yōu)的共同祖先,并以此作為合并的基礎(chǔ)。可以將合并理解為,從分叉提交中提取全部快照整合在一起,最后做一次新的提交。并且新提交擁有多個父提交。
快進(jìn)合并(fast-forward)
當(dāng)試圖合并兩個分支時,如果順著一個分支走下去能夠到達(dá)另一個分支,那么 Git 在合并兩者的時候,只會簡單的將指針向前推進(jìn)(指針右移),因為這種情況下的合并操作沒有需要解決的分歧——這就叫做 “快進(jìn)(fast-forward)”。
master只要向前推進(jìn)就可以完成與iss53的合并,所以會使用快進(jìn)合并。
合并沖突
有時候合并操作不會如此順利。 如果在兩個不同的分支中,對同一個文件的同一個部分進(jìn)行了不同的修改,Git 就沒法干凈的合并它們。在合并它們的時候就會產(chǎn)生合并沖突。
此時 Git 做了合并,但是沒有自動地創(chuàng)建一個新的合并提交。 Git 會暫停下來,等待你去解決合并產(chǎn)生的沖突。等你手動解決之后,Git 會詢問剛才的合并是否成功。 如果你回答是,Git 會暫存那些文件以表明沖突已解決。
如果你對結(jié)果感到滿意,并且確定之前有沖突的的文件都已經(jīng)暫存了,這時你可以輸入?git commit?來完成合并提交。 從而產(chǎn)生一次新的合并提交。
與無沖突合并的區(qū)別主要有:
- 1、需要手動解決沖突并標(biāo)記已解決。
- 2、需要自己提交新的合并提交。
變基(rebase)
在 Git 中整合來自不同分支的修改主要有兩種方法:merge?以及?rebase。
你可以提取在一個分支中引入的補(bǔ)丁和修改,然后在另一個分支的基礎(chǔ)上應(yīng)用一次。 在 Git 中,這種操作就叫做?變基。 你可以使用變基將提交到某一分支上的所有修改都移至另一分支上,就好像“重新播放”一樣。
它的原理是首先找到這兩個分支(即當(dāng)前分支、變基操作的目標(biāo)基底分支)的最近共同祖先,然后對比當(dāng)前分支相對于該祖先的歷次提交,提取相應(yīng)的修改并存為臨時文件,然后將當(dāng)前分支指向目標(biāo)基底, 最后以此將之前另存為臨時文件的修改依序應(yīng)用。
變基也并非完美無缺,要用它得遵守一條準(zhǔn)則:
不要對在你的倉庫外有副本的分支執(zhí)行變基。
如果你遵循這條金科玉律,就不會出差錯。 否則,人民群眾會仇恨你,你的朋友和家人也會嘲笑你,唾棄你。
(也就是說已經(jīng)推送到遠(yuǎn)程分支的內(nèi)容就不要進(jìn)行變基了,否則會對別人的造成困擾)
變基沖突
我們知道合并時有可能產(chǎn)生沖突,而變基時仍然有可能產(chǎn)生沖突問題。
我們在基底分支和補(bǔ)丁分支修改了同一個文件時就要手動進(jìn)行沖突處理。
如果在變基時發(fā)現(xiàn)沖突,git會停止變基操作要求手動解決沖突。
手動解決沖突后,git會使用手動解決沖突的文件重新建立補(bǔ)丁提交。
交互式變基(interactive)
交互式變基主要用于將多次提交合并成一次提交。
我們通常會在完成一個功能時,合并雜亂的提交從而使提交樹更加簡潔。
交互式變基允許我們自由的選擇提交,并且重新編輯提交說明。
變基 vs. 合并
總的原則是,只對尚未推送或分享給別人的本地修改執(zhí)行變基操作清理歷史,從不對已推送至別處的提交執(zhí)行變基操作。
應(yīng)用提交(cherry-pick)
cherry-pick允許我們提取一個或多個現(xiàn)有的提交,并使用這些提交的快照來創(chuàng)建新的提交。
也就是說我們能夠提取某一次提交的變更,應(yīng)用在其他分支當(dāng)中。
這個功能在處理生產(chǎn)bug時將會非常有用。如果我們在開發(fā)分支正在進(jìn)行開發(fā)時出現(xiàn)了一個生產(chǎn)bug,就需要創(chuàng)建一個bug分支。但是bug分支即要合并到開發(fā)分支進(jìn)行測試,又要合并到生產(chǎn)分支解決問題,顯然使用分支合并方式無法完美的解決這個問題。
上面這種情況使用cherry-pick正合適。在bug分支修改完之后,我們可以將修復(fù)bug的提交分別cherry-pick到生產(chǎn)和開發(fā)分支。由于使用cherry-pick創(chuàng)建的提交標(biāo)識名都是一致的,在生產(chǎn)上線時執(zhí)行變基操作并不會產(chǎn)生沖突,會完美的合并成一次提交。
需要注意的是如果我們對cherry-pick的提交進(jìn)行了交互式變基,那么在合并的時候就無法確認(rèn)兩次提交的關(guān)系,會要求我們手動合并。所以如果考慮到將來要將cherry-pick的兩個分支進(jìn)行合并的話,最好還是不要在cherry-pick提交上進(jìn)行交互式變基操作。
儲藏(stash)
有時,當(dāng)你在項目的一部分上已經(jīng)工作一段時間后,所有東西都進(jìn)入了混亂的狀態(tài),而這時你想要切換到另一個分支做一點別的事情。 問題是,你不想僅僅因為過會兒回到這一點而為做了一半的工作創(chuàng)建一次提交。 針對這個問題的答案是?git stash?命令。
儲藏會處理工作目錄的臟的狀態(tài) - 即,修改的跟蹤文件與暫存改動 - 然后將未完成的修改保存到一個棧上,而你可以在任何時候重新應(yīng)用這些改動。
?
?
參考
Git 分支 - 變基
總結(jié)
- 上一篇: Spring注解编程基石(一)
- 下一篇: ElasticSearch(一)基础知识