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