前半部分屬於基礎,後半部分屬於進階。從初級到中級再到我都 hold
不住的高級。全文共 12000
餘字,超幹超乾的那種。 然而,寫完一半的時候,我忽然虎軀一震,我是否是在造輪子?隨後我悄悄的搜了一下 git
。 嗯?這麼多 git
文章,我滴天呢,我陷入了沉思,我皺着眉頭點了幾篇文章,有號稱封山之做的2萬字真理,也有完整詳細的 git
系列教程。好像有點輪子啊,可是我繼續看了下他們的內容後,有種茅廁頓悟般的驚喜,由於我發現個人文章仍是很獨樹一幟的。最後得出一個結論,我沒有造輪子,這是一篇高可用高擴展高性能的 git
和 gerrit
文章。用實戰去推進思考,kill
掉大衆化的 git
知識,從經常使用的角度去擴展深層的知識,進而抽象出咱們能夠理解掌握的 git
奧祕。不拘泥於 API
,不畏懼其餘輪子,不要慫,就是幹。php
本文是站在別人的 commit
上去 merge
和 patch
我本身獨具特點的理解,從而生成一個更好的 commit
,而後留給你們往後更好的 merge
和 patch
,技術在一次次 patch
中不斷進步。html
在實際項目開發中,能靈活的使用 git
和 gerrit
是一個很重要的事情,一方面能夠提升團隊開發效率,另外一方面能夠把控項目的代碼質量。不過對於 gerrit
, 可能一些同窗沒有用過,還有 git
的一些你可能沒有掌握的技巧,今天就一塊兒分享給你們,分享的同時,本身也有不少即時收穫。前端
PS: 爲何我會說我本身也會有不少收穫呢,由於是這樣的:當我選擇寫一篇博客的時候,我會本身先去深刻的理解我寫的這篇博客的相關知識點,在深刻理解這個過程當中,我會去閱讀各類資料,而後去分析,最後總結出屬於我本身特點的學習心得,這對我來講,就是一種即時收穫和高級進階。vue
這裏咱們用 git
, 咱們就應該去了解一下 git
出現的背景,具體故事不說了,自行維基。這裏我簡單說一下 git
的出現,在技術層面上的背景。react
git
是一個開源的 分佈式版本控制系統 ,源碼也在 github
上開源了,能夠自行搜索。提到分佈式版本控制系統,那應該聯想到 集中式版本控制系統 ,具備表明性的好比 SVN
, SVN
的全稱是 Subversion
。linux
那這二者究竟有什麼區別呢?用兩張圖對比一下:webpack
SVN: git
GIT: 程序員
從圖中基本能夠分析出二者的主要區別。好比:github
git
能夠離線開發,svn
不能離線git
處理 merge
的優點碾壓 svn
固然其餘的區別還有不少,好比 git
的內容更完整,使用了 SHA-1
算法,git
能夠更加靈活的操做 branch
。 等等,這裏就不造輪子了,參考下面這篇博客:
看到這裏,咱們可能比較偏向於, SVN
比 Git
差一些的觀點,但其實這是兩種不一樣的版本控制系統,不能簡單的認爲誰好誰壞,這裏有一篇爲 SVN
洗白的博客,挺有趣的,你們能夠看看:
要是高度總結一下,那就是: SVN 更適用於項目管理, Git 更適用於代碼管理。
咱們看維基介紹:
言簡意賅:從維基上可知,Gerrit
是一種開源的代碼審查軟件,專門用來 CR
的。
這裏說一下版本控制系統的三板斧:
第一板斧: 存儲內容
第二板斧: 跟蹤內容更改
第三板斧: 與協做者分發內容和歷史記錄
理解了上面的三板斧,你就理解了版本控制系統的精髓,這裏先不作解釋,繼續閱讀,而後本身體會。
如今和未來的前端,必定是和 git
打交道最多的行業之一,上面提到了版本控制系統,那爲了擴展版本控制的知識,咱們有必要去了解一下版本控制系統的發展歷史,歷史大體是這樣的:
從 手動
copy
diff
打patch
, 到 引入了互斥寫入機制的RCS
, 再到 第一次引入了branch
概念的CVS
,再到 實現原子操做級別合併的SVN
,再到 如今的新皇登基git
。每個時代,都有本身的那一份驕傲,這裏推薦一篇很是很是好的博客:
博主大佬:Vamei
這篇文章簡直把版本控制系統的整個歷史解釋的堪稱完美,從最開始的我的手工 copy
壓縮打包,到後面的經過 diff
出 patch
( 也就是咱們常說的 補丁 ),而後經過郵件進行傳達 patch
。而後繼續說了 rcs
cvs
svn
git
在說到 git
時,解釋之精妙,使人佩服。
這裏我修改一下文中最後一段:
和三國志不一樣, VCS
的三國尚未決出最終勝負,或許 SVN
會繼續在一些重要項目上發揮做用,可是 git
最終會一統江山,至少會一統前端江湖。
有時候,咱們可能對爲何叫 git
、 gerrit
不怎麼在乎。可是不少命名都是有本身的故事的,好比 vue
, react
爲何這樣命名。能夠去查閱資料瞭解一下,這有助於咱們更形象化的理解它們。
好比說,git
一詞的由來,能夠從維基百科上的一段話能夠看出:
Quoting Linus: "I'm an egotistical bastard, and I name all my projects after myself. First 'Linux', now 'Git'".('git' is British slang for "pig headed, think they are always correct, argumentative").
翻譯一下就是:我是一個自負的混蛋,我把本身的全部項目命名爲本身。首先是 "Linux" ,如今是 "Git" 。( git
在英國俚語中是豬頭,認爲他們老是正確的,有爭議的 )。
是否是發現其實命名也是有本身的故事的。
再舉個例子,好比 MySQL
中的 My
並非 個人 的意識。MySQL 的命名由來是這樣的,維基上有介紹:
Its name is a combination of "My", the name of co-founder Michael Widenius's daughter,[7] and "SQL", the abbreviation for Structured Query Language.
因爲已經說了 git
的命名由來了,這裏我就言簡意賅點,gerrit
的命名來自於荷蘭設計師赫裏特·裏特費爾德( Gerrit Rietveld
) 的名字首個單詞。
這也是一個值得思考的問題,咱們爲何要用
git
?
直覺上,咱們天然而然的用了,發現也很好用。那咱們能夠問一下本身,git
爲何很好用,若是咱們看了上面提到的博客,可能咱們已經有了答案,其實不少很棒的東西的誕生,都是在誕生的某個維度背景下,解決了大部分同類沒有解決掉的痛點。
因此如今咱們用了 git
,咱們也以爲很好用,可是事實上咱們好像並不清楚 git
的出現,解決了什麼樣的痛點,咱們只知道好用。我說這句話,就是想說明一下,去了解一個東西,最好去了解這個東西誕生時所處的時代背景或者技術背景。哎,好像我沒有回答爲何要用 git
? 不慌,問題不大,其實答案已經在前面提到了。
網上有不少 git-flow
開發流程的博客,這裏不進行講解了,可是我想講的就是:
總結出一個符合本項目的
git
開發模式,纔是真正意義上的git-flow
。
目前的代碼託管平臺主要有:github
、 gitlab
、 Coding
、 碼雲 。 這是我知道的主流的代碼託管平臺( 排除 bitbucket
,由於國內用的很少)。因爲最近 github
容許我的開發也能夠創建私有倉庫,那也就說明這四個代碼託管平臺均可以避免費創建私有倉庫了,這算是一個重要時刻吧。
參與了幾個項目後,我在想一個事情,就是什麼樣子的開發模式 ( 只針對開發 ) 纔是一個好的開發模式,最後我得出一個關鍵的因素,那就是:
一個好的開發模式,能夠提升團隊的開發效率,同時提升團隊的代碼質量。 ( 這不是廢話嗎,手動滑稽 )
咱們上面提到的,無論是 svn
仍是 git
, 都是爲了優化現有的開發模式。那麼,如何去按照本項目的特色去制定屬於本項目 style
的 git-flow
呢?下面我會分享一些我本身的見解。
目前參與一個前端開發者達到幾十人的一個大型項目,使用的是 git
版本控制。本人負責給項目加上 gerrit
和 幫助其餘開發者平穩過渡到 gerrit
開發模式中,說通俗點就是:
有啥
git
和gerrit
操做問題,我負責解決。
根據個人經驗,若是要提升團隊的代碼質量,那必定會下降團隊的開發效率,也就是在平均時間內,工做產出會下降。
爲何這樣說呢?由於這是合理的,我用
V8
來舉個例子:
拿 V8
引擎來講,V8
對 JS
代碼的優化,並無一網打盡似的所有采用 JIT
編譯器 進行優化,而是針對性對一部分代碼使用 JIT
優化,對另外一部分代碼直接生成本地代碼。
緣由很簡單:
優化的越好,就意味着須要的分析和生成代碼的時間就越長。 對 C++
這種編譯型,等待的時間長一點能夠接受,可是對於 JavaScript
來講,哪怕是 200ms
,那對於用戶體驗來講,都是一個考驗。
我舉這個例子是爲了從軟件編程領域去說明一個道理:
就是不能一味的追求質量,而是要把質量和效率結合在一塊兒,去達到一種最優解。
我我的認爲,網上標準的 git-flow
模式 對於那些開源的項目可能比較適合,或者公司內部很重要的項目合適,其實 git
誕生背景,主要就是爲了讓開源的代碼版本控制變得更強大。github
的出現,讓 git
變得很是流行。咱們看一下網上那一套標準的 git-flow
模式,以下圖:
是否是看的眼花繚亂,都有點懼怕,我不就是作個版本控制嗎,有必要這麼複雜嗎?
首先,徹底沒有必要這麼複雜,各位小夥伴不要被這種博客嚇到了,嚇到都不敢用 git
。雖然上圖的 git-flow
模式能夠說是使用 git
進行版本控制的 best practice
。可是我認爲這並不適合大部分的業務項目。
敢問,在大公司內,或者小公司內,使用上述的標準版
git-flow
模式進行開發的前端項目佔比是多少? 我想佔比幾乎沒有,或者說能有10%
,我以爲都是奇蹟。若是項目開發時間緊,迭代快,那幾乎不可能按照這種模式來,那怎麼來呢?
我以爲應該是:在分析項目的時間,和迭代速度後,作出一個既能夠控制代碼質量和版本管理,又可讓開發過程變的不那麼繁瑣,從而保證必定的開發效率。這纔是一個比較好的 git-flow
,
大白話就是:
怎麼舒服怎麼來,自行腦補。
因此當你想學習 git-flow
模式開發的時候,而後去網上搜了一下博客,發現 git-flow
模式有點抽象。這個時候請不要懼怕,我不認爲這種標準抽象的 git-flow
就是屬於你如今項目的 git-flow
。
你應該去學習這種標準
git-flow
模式的思想。
好比經過幾個關鍵性的 branch
來對版本的生命週期進行精細控制,經過 branch
來分割各個生命週期的代碼,保證版本的各個生命週期代碼的純潔性。
純潔性是什麼意識?
舉個例子:下個版本的代碼,你也開發一半了,那這些代碼就不能出如今如今版本的線上代碼中,純潔性就能夠這樣理解。
我想說的就是:
比起要學會如何使用 git-flow
, 咱們更應該去體會一個很棒的版本控制系統 的解決方法,其背後的思想。當深刻理解了思想,那後面用其餘版本控制系統的軟件,也能遊刃有餘了。
這裏以我目前參與的一個大型項目做爲例子,說一下如何在實踐中,總結出屬於本項目的 git-flow
流程。
這裏介紹一下項目的分支結構,沒有所謂的 feature
分支,有 develop
分支,但也是簡寫成 dev
( 怎麼方便怎麼來 )。
dev
分支有兩個做用:
一個是充當 feature
分支,一個是充當 develop
分支。當要發佈一個新的版本的時候,就從 dev
上切一個 dev-xx
系列的分支,用來發佈一個版本。嗯,就是這麼簡單直接。
項目開始的時候:
項目代碼是託管到內部的 gitlab
上的,項目一開始的時候,是沒有 CR
的。全部開發者均可以向 dev
分支上提交代碼。
爲何要這樣呢?
是爲了提升開發效率,由於項目處於一個急速開發的階段,若是太注重質量上的保證的話,就會增長人力成本,下降開發效率,最後和急速開發背道而馳,這也算是符合那句俗語:過早的優化就是地獄。
可是提升開發速度的同時,就意味着要承擔對應的風險。
好比,同事進行了錯誤的操做,致使代碼缺失。我說一下我這邊遇到的一個經典案例 ( 簡要說一下這一部大片 ) 就是:
你 ( 表明一個同事 ) 在 merge
的時候處理不當,而後成功的把其餘同事的不少代碼搞沒了,可是你並不知情,覺得本身操做是對的,而後提交代碼到 dev
分支 。而此時,commit
時間線又在持續的往前走,走了很久,你才發現,而後忽然 at
全體人員,而後咱們就懂了。而後當你發現的時候,你果斷的想本身去處理這個問題,可是你沒有考慮到全面,只想到用 SourceTree
將代碼回滾到 merge
錯誤的索引處,可是你又不當心點錯分支了,將 dev
分支代碼回滾到了上個版本。因而,遠端 dev
分支,從上個版本到如今這個版本的代碼都沒了,記錄也沒了...
上面這個例子基本上算是除了刪庫之外,在
git
操做過程當中出現的最大的問題了,爲何會這樣說,理由能夠歸納爲如下幾點:
merge
到另外一個分支時,處理不當。commit
,而後你懂的。reset
這種可怕的命令,去操做其餘 coder
的 commit
。reset
錯分支了, 致使一個大版本的代碼被幹掉了,遠端記錄都沒了。我給出的理由是否是挺充分的,那麼這個事情怎麼解決的呢 ? 通過討論,有兩種方案:
第一種: 經過將這次分支回滾到 到 merge
錯誤以前的 commit
。 而後將在錯誤後面繼續提交的那些 commit
挨個加進去。這種方式有個問題,因爲遠端記錄都沒了,致使只能依靠有相對完整記錄的某個開發來作這件事,可是誰也不能保證這個記錄就是完整的。
第二種: 留給各個產品線本身去認領,本身解決本身的代碼丟失,哪裏丟失,補哪裏,採用責任制。
最後採用了哪一種方案呢?
經過討論,採用了第二種方案。
有人可能要問了,爲何不使用第一種方案? 理由以下:
第一:遠端記錄都沒了,這點很傷。
第二:相信某個開發的本地記錄是不可靠的,最後還得讓各個產品線去 CR
本身的代碼,看有沒有修復完整。
綜合一下,最後採起了第二種方案,直接讓各個產品線去認領,雖然麻煩了你們,可是可讓各個產品線去
CR
本身的代碼,更爲保險和可靠。
此次事故也充分證實了,在提升開發效率的同時,若是不去合理的限制權限,那麼在未來就可能會出現你不想看到的事故。
有人可能又會問,爲何沒有
CR
機制,好比爲何一開始沒有上gerrit
?
對於這個問題,我我的的觀點是這樣的:
上 gerrit
就意味着操做複雜度的增長和人力成本的增長,好比對於一個 APP
級別的項目,須要騰出更多的人力去 CR
。而通常項目剛開始的時候,人力都是緊張的,那麼這樣作無疑是增長了項目成本。若是你們能經過我的技術素養,保證不會出現代碼問題,那就能夠先不上 CR
機制。在沒有上的狀況下,項目迭代了不少版本,也沒有出現任何版本控制上的問題,從這點也能夠說明,有些優化不必定要從一開始就上,仍是要結合實際狀況去制定符合本身的一套 rule
。 可是隨着人數愈來愈多,出錯的機率大大增長,而後就出錯了(滑稽臉),出錯了怎麼辦,那就上 CR
機制吧。
CR
機制怎麼上,如何去 CR
一個 APP
級別 ( 參與開發達到幾十的規模 ) 的項目,能夠繼續往下看,下面有專門介紹 gerrit
的知識。
上面大體是 git
的科普,還有對項目開發過程當中遇到的問題的一些思考。我把上面的部分稱爲 git
初級。
而下面我要說的就是
git
的中級知識
若是你可以靈活運用 git
知識去解決版本控制過程當中的各類問題,那就能夠說你是屬於中級水平了。
這裏我想說一點:
我是用的命令行形式去進行 git
操做的,固然也有不少人是用的 SourceTree
VsCode
WebStorm
這種軟件去操做 git
。 不過每一個人應該都有主次之分,好比我,就主用命令行,VsCode
我也用。
我通常的使用規律就是:
除了我須要去閱讀文件,對比文件先後版本,或者查看多個歷史版本時,我會用 VsCode
外, 其餘操做都統一用命令行解決。
PS: 用命令行玩轉 git
的話,那基本的 linux
知識仍是要掌握的,若是有興趣能夠去學學 linux
。 推薦書籍:
《鳥哥的 Linux 私房菜: 基礎學習篇》
由於生命不止,學習不止。
不少人只是在記 git
的命令操做,並不清楚這樣作的底層緣由,從而致使了 知其然不知其因此然,最後就沒有辦法在一個大的知識層面上對 git
進行一個更爲抽象和深入的理解。
下面我會站在別人的肩膀上( 不重複勞動 ),根據我所學習的 git
知識來簡要分析一下 git
的一些中級理論知識。
這裏我用網上的一張圖來簡單歸納一下
git
的API
級別的原理,圖片以下:
而後我再展現大佬 Vamei
的兩張 git
分析圖( 圖 加 文字分析 ):
第一張圖:
第二張圖:
上面三張圖分別是一張 API
級別的 圖 和 大佬 Vamei
的兩張 git
原理分析圖。
若是對上面的三張圖理解深入的話,是能從圖中就能感覺到 git
的設計思想和不同凡響的特色。若是能理解深入,那其實也能夠說你已經掌握了中級的理論知識了。
可是不理解不要緊,下面我會簡要分析一下
git
的中級理論知識。
要想知道 git init
幹了什麼,咱們就要去執行 git init
, 而後去分析執行先後的具體變化。
咱們重新建目錄開始,而後初始化一個全新的 git
倉庫,具體執行的代碼以下:
// godkun
mkdir 0112-git-test
// 新建的目錄,用 ls -a 查看,是沒有任何東西的
cd 0112-git-test
git init
cd .git/
ls -a
ls -l
複製代碼
git init
執行完後,如圖所示
git init
命令後,在當前目錄下新建了一個
.git
目錄,咱們再經過
ls -l
能夠看到
.git
目錄下的全部文件和目錄,同時包括這些文件和目錄的權限。
下面我不在命令行下使用其餘
linux
命令去分析具內容了體,我來使用code .
打開VsCode
來具體看一下.git
目錄下的真相,VsCode
中的.git
截圖以下:
咱們從圖中能夠分析出幾個信息
第一個: 在 0112-git-test
空目錄下進執行了 git init
後,生成的 .git
目錄下的 objects
和 refs
目錄和他們的子目錄都是空目錄,很純潔。
第二個: .git
目錄下的 HEAD
文件中寫了一行代碼 ref: refs/heads/master
, 咱們按照這個路徑去找,卻發如今 refs/heads
目錄下並無 master
。
上面的狀況是咱們在空目錄下執行了
git init
的結果,那若是在一個非空目錄下執行git init
呢? 好比:
mkdir 0112-git-test-2
cd 0112-git-test-2
vi 1.txt
// 寫入文件而後保存退出
git init
複製代碼
咱們按照上面分析的步驟去分析非空目錄下進行 git init
的操做,會發現 .git
目錄下沒有任何變化。
咱們會發現
通過兩次分析,咱們能夠看到,在進行 git init
後,無論當前目錄有沒有文件, .git
目錄都是同樣的,同時 HEAD
默認是指向 master
分支,看下圖:
圖中能夠看到,執行完
git init
後,當前分支就指向master
分支了,因此這時候咱們就能解決掉下面這個問題了:
爲何會 git init
後默認指向 master
, 經過上面簡單的操做,咱們就能夠從中級層面去理解這個事情了。
如今咱們繼續推,對非空目錄下執行
gst
, 顯示以下圖:
咱們看箭頭處,會發現這個文件是 untrack
,咱們結合 git init
命令先後的 .git
並無發生任何變化。
能夠推出:
1.txt
沒有被歸入到版本控制系統中,untrack
就表明沒有歸入到版本控制中。
**PS:**咱們在分析 .git
目錄的時候,必定要帶着版本控制的思想去分析。
思考時間
我分析了 git init
,那麼類推一下, git clone
幹了什麼呢? 這裏留給小夥伴們分析吧。
上面咱們經過 git init
後,生成了一個 .git
目錄,可能你對 .git
目錄還比較陌生,若是想掌握好 git
的中級理論知識,那麼 .git
目錄是要去征服的。
從上面
git init
後的目錄截圖咱們能夠知道(簡要介紹一下)
第一:.git
根目錄下,有不少一級子目錄和一級子文件。
第二:看 hooks
目錄,從命名咱們聯繫到 react
最新的 Hook
特性,萬物都是相通的。裏面有不少文件,好比 pre-commit.sample
文件,這是一個樣本文件,咱們按照樣本文件的寫法進行編寫代碼,而後把 .sample
去掉,寫成 pre-commit
,這樣就能夠在你執行 git commit -m 'xxx'
時,去執行 pre-commit
文件中的代碼。這就是 git
中的生命週期鉤子。
第三:看 objects
目錄,這是一個存放各類數據的目錄。咱們的項目,無論是什麼形式的數據,圖片也好,音頻也好,代碼也好,都會被轉換成統一的數據格式存放在 objects
目錄下。
關於 objects
目錄的基本信息,能夠看下面這篇介紹 git-objects
的博客:
第四:refs
目錄下有 heads
和 tags
目錄。以及子文件 HEAD
中寫着 ref: refs/heads/master
, 這是 git
當前指向的分支。
有什麼感覺
我但願在總體分析時,你們能把 .git
目錄當成一個前端工程去分析,好比你能夠把 objects
目錄當成前端項目中的 dist
目錄。其餘類推,只要能有助於你去理解,那都是好的類推。
PS: 這裏是總體分析,沒有去深刻介紹,總體瞭解一下就好。
當我把一個不在版本控制系統中的文件,使用 git add ·
加到暫存區後,我來看一下 .git
目錄的變化,如圖所示:
咱們會發如今 Object
目錄下增長了一個名爲 60
的目錄。該目錄下有一個二進制文件。同時 .git
根目錄下多了一個 index
文件,也是一個二進制文件。
從這裏咱們能夠分析出幾個信息:
第一個:git add
操做會把不在版本控制下的文件歸入到版本控制中,爲何會這樣說,從中級角度看,是由於 .git
目錄有實質性的改變了。
第二個: git add
操做會在 objects
目錄下生成子目錄爲 60
,文件名爲 d4a4434d9218d600c186495057bb9b10df98ad
的一個二進制文件。
第三個:git add
操做會在 .git
根目錄下生成一個命爲 index
的二進制文件。
咱們看一下
d4a4434d9218d600c186495057bb9b10df98ad
文件中的內容是什麼?
執行:
git cat-file -t 60d4a4434d9218d600c186495057bb9b10df98ad
複製代碼
執行結果以下圖所示:
就輸出了一個單詞,blob
。
blob
是什麼?
blob
是 binary large object
翻譯一下就是二進制大對象。那咱們能夠這樣理解,這個文件是一個二進制大對象,OK
,繼續往下分析。
文件爲何要用一串字符串命名
好比文件 d4a4434d9218d600c186495057bb9b10df98ad
,不理解不要緊,繼續往前端上去聯想,是否是想到了 webpack
打包後的文件名,能夠在前面加上 hash
前綴。有種豁然開朗的感受了吧,留給你們自行去分析吧。
git add
和blob
和 文件名d4a4434d9218d600c186495057bb9b10df98ad
的關聯
沒有執行 git add
的時候,目錄下是空的。當 git add
後,多了一個 blob
,同時生成了一個 40
個字符的 hash
串,而後目錄和文件用 hash
表示。也就是說:
git add
後生成了一個 blob
對象,blobId
爲 60d4a4434d9218d600c186495057bb9b10df98ad
。
看到這你是否是又有點感受了,記住一句話:
萬物皆可推。
咱們日常的各類 git commit -m 'xxxxxx'
其實生成一個 commit
對象,同時生成了 commitId
也是40位的 hash
字符,存在 objects
目錄下。
根目錄下多了一個
index
文件,它是什麼?
如今肯定的一點是,當用 git add
把文件放到暫存區的時候,index
文件就生成了,這個 index
文件是一個二進制文件,我使用下面命令去查看 index
的內容:
od -tx1 -tc -Ax index
複製代碼
如圖所示:
上面圖中的那一串數據是 index
文件中的二進制數據。
這裏咱們看一下圖中我標註的紅框。
能夠看到,index
文件中包含了不少信息,好比 1.txt
,2.txt
,還有 TREE
。目前從表現上看,我只能瞭解到這麼多的信息,它們之間確定有某種聯繫。其實瞭解過暫存區的應該能夠聯想到,index
文件就是一個暫存區。
能夠看這篇直接給結論的官方文檔:
Git-Internals-Plumbing-and-Porcelain
留幾個問題給各位小夥伴思考:
若是你的項目尚未一個
commit
的話, 在上面這張狀況下,咱們使用git stash
會發現有如下報錯:
爲何會報這個錯誤提示?
爲何
40
字符的hash
串要拿出前兩位做爲目錄?
這個作法其內部的道理是什麼,這樣作是和算法有關係嗎,目的是爲了更好的性能嗎,前端可不能夠借鑑這種思想,仍是說前端已經有了這種思想,那這種思想是什麼?
爲何
git
要用二進制數據格式來存儲數據?
自行想想,也許會有一些有趣的收穫呢。
這裏我會經過實踐去告訴你們,git stash
在 .git
目錄是如何表現的。
首先我進行一次 commit
, 項目如今只有一個 commitId
,以下圖所示:
這個時候,我使用下面命令:
vim 2.txt
// 編輯 2.txt
git add .
複製代碼
git add
後,咱們看 .git
目錄,以下圖所示:
關注一下上面的箭頭所指的文件。
點擊 ORIG_HEAD
能夠看到是一個字符串 0991ddc42dbda1176858b89008b8dece5f91093b
對照着在 objects
目錄下找,發現確實有,咱們再用下面命令
git cat-file -p 0991ddc42dbda1176858b89008b8dece5f91093b
複製代碼
咱們看到了 tree
,tree
也有一個 treeId
,treeId
爲 33b62884583995b8723d4d5ef75e44aa7d07fbf3
再結合 git log
再看下面這張圖:
對比兩張圖, 會發現 ORIG_HEAD
文件中的 hash
值 相等於 HEAD
中所指向的文件位置中的 hash
值。話不能說太透,後面的自行領悟吧。
執行 git stash 會發生什麼?
看下圖:
圖中的左邊是我把 2.txt
經過 git add
放到暫存區的 index
文件的內容。右邊是我使用 git stash
後的暫存區的 index
文件的內容。能夠看出,git stash
先後的 index
文件差異。
請看下面我演示的 gif
圖:
能夠看到,當我 git commit
的時候,refs/heads
目錄下的 matser
文件中存放的 commitId
變成了最新提交的 commitId
,而 ORIG_HEAD
沒有改變。由此能夠知道,HEAD
文件存放的路徑,其路徑下的文件的 hash
值是當前目錄下最近的一次 commit
。
能夠參考這篇博客:
merge
和 rebase
的問題大概是 git
中最著名的問題了吧,在面試中也是考察的最多的知識點。好比,你知道 merge
和 rebase
的區別嗎?這種相似的問題,不勝枚舉。
網上教程也一大堆,若是你想深入瞭解 git merge
和 git rebase
的話,那就請按照我上面的那種分析方法,一步一步去操做,而後觀察 .git
目錄下的各類變化,而後根據重要的變化來去細緻的分析其中的緣由和道理。
可是,不少教程寫的過於複雜了,我拿
rebase
來作一個我我的理解的通俗解釋。
好比當前分支爲 dev
,而後我執行:
git rebase master
複製代碼
上面的命令怎麼理解
一個最關鍵的一點就是: 要知道 rebase
是變基的意識。rebase master
是以 master
爲 base
,而後把 dev
分支的補丁打到 master
後面,打的過程當中生成的 commitId
是新的 commitId
,dev
原有的 commitId
被丟棄,時間線也就變成了直線。
最終,matser
和個人 dev
分支合併,讓最新的 commmitId
以個人最新提交的爲準( 這裏就是我在 dev
分支上的最新提交 )。因此當我 push
後,我提交的代碼就成爲了基準。
rebase
就這麼簡單。
能夠看看個人兩篇簡潔 issues
:
blob
commit
tag
tree
是怎麼串起來的其實這是一個很是關鍵的問題,不少人都不清除這些 單詞
背後的的真理到底是一種什麼樣子的美麗。
可是我不打算造輪子了,由於好文章太多了,這裏我還想放上面的一張圖,由於這張圖太經典了。
解釋已經在圖中的文字中了,好比知道了這些,你就知道了咱們在給版本打上 tag
的時候,到底是作了什麼。咱們不能浮於表面,只知道要打 tag
,咱們還要知道打 tag
背後的緣由。只有這樣,才能作到知其然知其因此然。
終結 tag : github.com/godkun/git-…
COMMIT_EDITMSG 文件
此文件裏面寫的內容是本地最後一個提交的信息
packed-refs
clone倉庫時全部的引用
我把在使用 git
進行版本控制過程當中,我所用到的全部 git
操做高度提煉一下。
個人這些
git
操做的目的能夠歸納爲如下幾點:
git
操做問題下面簡要分析一下上面各個目的過程當中的一些心得。
git
處理合並和解決衝突的能力 碾壓 svn
。好比 svn
處理一個衝突,因爲是集中式的倉庫管理,倉庫只有遠程一個,可想而知,解決衝突就是一場提交競賽。
我本人是如何在項目中處理各類衝突和合並的呢?
按照個人這幾個步驟來,基本不會存在任何衝突解決失敗的狀況。
首先,當我去 pull
遠端代碼的時候,好比執行
git pull origin dev
複製代碼
執行完以後,我發現的控制檯多了不少 conflict
提示,我看了下,不少都是別人代碼的衝突,這個時候我怎麼會呢?
我會絕不猶豫的 git reset --hard
回滾掉此次 merge
,而後我已經知道了這樣是不行的,可是我又不能去等着別人把衝突修改掉,怎麼呢?我會先在當前分支的基礎上新切一個分支
git checkout -b dev-backup
複製代碼
至關於備份一下目前本地的代碼,dev-backup
分支用來保存本地代碼。而後這時,我
git checkout dev
複製代碼
切換到 dev
上,切換後,我要怎麼辦呢,這時我會將 dev
分支的代碼所有替換成遠端的 dev
分支:
git reset --hard origin/dev
複製代碼
這時,我本地的 dev
分支已經所有采納遠端 dev
分支代碼了,這個時候我還須要將我本地修改的代碼合併進去,可是這個時候我就可使用一個命令:
git checkout dev-backup pages/xxxx
複製代碼
經過上面的命令,咱們就能夠將 dev-backup
分支上的 xxx
目錄下或者 xxx
文件的代碼單獨合併到 dev
, 而這部分代碼就是我本地本身修改的代碼,因此就算有 conflict
, 我也能夠迅速解決掉,而後安全 push
遠端倉庫上。
上面的解決衝突的方法,雖然方式簡單,可是是我我的認爲能夠完美解決掉 git
版本控制中的全部合併和衝突問題。
在版本控制系統中,合併一直都是一個核心節點,咱們要去理解合併和解決衝突在版本控制系統中究竟佔有多大的重要性。
提交代碼這個應該沒什麼問題,但其實你把本地代碼提交到遠端倉庫這一步驟,是一個很是重要的時刻,爲何我說很是重要呢?想必你以前聽過外國一個程序員由於同事常常 git push -f
而把同事給終結掉了,😂。因此懼怕了吧,莫事,不慌,你只要遵照這幾個原則就 ojbk
了:
git push -f
除非你已經作好不想活的準備了。😂commit message
git commit
以前先 git status
看一下,檢查一下有沒有無心間改動了其餘文件。其實我我的的感受就是,若是是本身的業務項目,除了第一點,第二點,第四點須要去注意外,像 第四點,commit message
這種,開心就好吧,不用很刻意的。
談到這個,我想你們都有一些本身的總結吧,在用多了 git
後, 慢慢的會發現有一些能夠加快使用 git
進行版本控制的小技巧。下面我總結一下我本身總結的幾點提高開發效率的方法吧。
配好最適合你本身的
alias
好比我配的有:gst
表明 git status
, 固然你還能夠更加簡單,開心就好。
優化你的
stash
用好 stash
也是一個既簡單又能夠提升開發效率的方法,具體用法不說了,個人 github
有相關詳細資料,它主要是起一個暫存的目的,可是通常你們都是 git stash
談到優化,其實我想說優化是一個相對的概念,若是對 git
控制版本的過程進行優化的話,我我的以爲我目前用到的優化也很少,大概就是如下幾個:
git rebase -i
對個人一些我都看不下去的 commit
進行處理。這個固然是本身蠢了,不當心把東西搞砸了,那就要快速解決掉本身的錯誤操做,怎麼解決,思想也很簡單:
通常我是本着一個原則:以最快的速度把錯誤操做從遠端倉庫中移除掉,從而最大化的減小對其餘
coder
的影響。
好比經過本地切分支快速備份我本身的代碼,而後切換回去,快速把本身的錯誤代碼回滾掉,而後 push
到遠端倉庫,解決遠端倉庫的代碼衝突問題,而後我再繼續解決本地我本身代碼的問題。
git
操做問題我感受若是一個項目很大的話,參與者不少的話,隨時有新的 coder
參與進來,你是沒法保證全部人的 git
操做都會很正確的,而這個過程當中,一些人可能有進行了錯誤的 git
操做,本身也沒法解決,而後會找其餘同事尋求幫助,我也幫助過一些同事。我在幫助其餘同事處理 git
問題的時候,使用的命令仍是比較多的,有時候還得使用一些不經常使用的技巧,好比正則,過濾等,這裏就不細說了。
我以爲,咱們沒有必要在項目開發過程當中把 git
操做複雜化,一些黑科技什麼的,也沒有必要去關注,有句話是這樣說的:能用簡單的操做解決複雜的問題纔是大牛。因此上面我介紹的實戰技巧,能夠說沒有什麼高大深的技巧,當理解的足夠深刻的時候,經過簡單的操做也能夠保證項目的有序進行。
這裏呼應一下文章開始所說的那一句話:
從初級到中級再到我都 hold
不住的高級。
爲何我說我都 hold
不住呢?是由於我真的 hold
不住。可是我仍是去學習了一番,從新簡單翻了一遍 C
和 C++
語言,嘗試着去理解一下。
首先把 github
上的 git
倉庫 clone
下來。
這裏我用到一個代碼行數分析工具 cloc
,能夠經過下面進行安裝:
<!--安裝 homebrew 後-->
brew install cloc
複製代碼
安裝完畢後,在 .git
目錄執行:
cloc *.c *.h *.sh
複製代碼
獲得如圖所示:
從圖中咱們能夠發現,當前 github
上的 git
項目是由不少語言組成的,master
分支的總代碼行數大約 50
萬左右( PO File
不算)。主要語言有 C
sh(Bourne Shell)
Perl
C/C++ Header
。給個人感受有幾點:
第一點:代碼量不算大,50萬行左右,與 linux
內核這種千萬級別的代碼仍是有差距的,只能算是一個工具。
第二點:涉及到的語言不少,可是核心語言基本就 C
sh
C/C++ Header
這三種。
下面我要怎麼分析它呢。
目前因爲目錄過於複雜,我想到了去看 git
項目第一次 commit
的內容,通常來講,第一次 commit
的代碼量是比較小的。我在 github
上找到的截圖以下:
我進入git 目錄 執行了
git checkout e83c5163316f89bfbde7d9ab23ca2e25604af290
複製代碼
去看一下第一次 commit
的代碼內容,以下圖所示:
命令行:
VScode 截圖:
我好奇的使用 cloc
看了下代碼量,下圖所示:
驚了!只有848行,是否是瞬間有了信心。那就開始終結它吧!
按照慣例,我去 README
中看了下項目介紹:
如圖所示:
編輯者是 Linux Torvalds
GIT - the stupid content tracker
"git" can mean anything, depending on your mood.
這兩句是做者本人對 GIT
的介紹,是本尊無疑了。
我閱讀完
README
後,得到了如下幾點信息:
All objects are named by their content, which is approximated by the SHA1 hash of the object itself. Objects may refer to other objects (by referencing their SHA1 hash), and so you can build up a hierarchy of objects.
第一點:全部對象都是用他們本身的內容來命名,經過 SHA1
hash
值來標識本身。對象能夠經過引用其餘對象的 SHA1
hash
來引用其餘對象。因此能夠創建起一個有層次的對象模型。
第二點:對象內容都是用 zlib
進行壓縮,同時 SHA1
哈希始終是 是壓縮後的對象內容的哈希值,而不是原始對象內容的哈希值。
第三點:A "blob" object is nothing but a binary blob of data, and doesn't refer to anything else. 簡單點說就是: blob
沒有任何其餘屬性,僅僅表示文件的內容。
The "current directory cache" is a simple binary file, which contains an efficient representation of a virtual directory content at some random time. It does so by a simple array that associates a set of names, dates, permissions and content (aka "blob") objects together. The cache is always kept ordered by name, and names are unique at any point in time, but the cache has no long-term meaning, and can be partially updated at any time.
第四點:當前目錄緩存,能夠理解爲是暫存區,暫存區也是一個二進制文件,它經過一個簡單的數組來記錄着時間,權限,和對象內容。
第五點:使用了 SHA1
,因此改變和內容是值得信任的。
README
的信息仍是很足的。說明了不少事情。
第一次 commit 的源碼分析
這裏我就不造輪子了,找到了一篇文章,基本把第一次 commit
的源碼各個文件的做用解釋的較透徹。
執行 git checkou master
切到 master 分支
從圖中咱們能夠看到,有不少不少東西,一點都不想分析,那就不分析了,都1萬多字了,寫不動了。就這麼愉快的贊成啦!開開心心過完年後,再單獨寫一篇( 嘿嘿嘿 )。
這個原理知識就不說了,簡單點說就是搭一個 gerrit
服務器,而後經過 UI
界面去進行代碼的 CR
,CR
經過,點擊 submit
就會把代碼同步到 gitlab
上。
本人負責給項目實施 gerrit
, 並解決同事在過渡到 gerrit
方式的過程當中出現的各類問題。我在解決各類問題的時候,對整個 gerrit
的流程和操做都理解了狠多,下面就分享一下我在幫助同事過渡 gerrit
的過程當中遇到的問題和總結的一些心得吧。
這個就不說了,基本的像 ssh
認證 、 remote
設置、郵箱設置、這種我就不造輪子了,按照網上的基本教程來。
這個錯誤,是在過渡到 gerrit
的過程當中出現最多的錯誤,沒有之一,幾乎都會遇到。
錯誤以下圖:
從圖中能夠看到,提示 [8a5fca6] missing Change-Id in commit message footer
什麼意識呢,就是說 commitId
爲 8a5fca6
的提交沒有 Change-Id
,因此就提交失敗了。
同時咱們能夠看到打印信息裏面有給解決這個問題的方法,先執行:
gitdir=$(git rev-parse --git-dir); scp -p -P 29418 name@git.co.com:hooks/commit-msg ${gitdir}/hooks/
再執行:
git commit --amend
可是在解決這個問題的過程當中,我發現上面的提示,有時候並不能成功。我總結出了幾種狀況,下面一一列出。
commitId
是 head 指向的 commitId若是是 head
的話,也就是 git log
的第一個 commitId
。 那能夠直接按照上面提示的命令去執行。
這裏提一下,在執行 git commit --amend
時,會進入 vi
界面,進入後能夠不用修改任何東西的,直接保存退出便可,就能夠從新刷新 head
指向 的 commitId
的 值了。
commitId
不是 head 指向的 commitId若是不是 head
的話,好比是第 6 個 commitId
缺乏 Change-Id
,那怎麼辦呢? 針對這種狀況,有兩種辦法:
第一種:git reset --soft
使用
git reset --soft commitId
將commit
記錄 軟回滾 到缺乏Change-Id
的這個commitId
處,好比上圖的commitId
[8a5fca6] 是第6個commitId
,那此時就能夠這樣操做:
git reset --soft 8a5fca6
git commit --amend
複製代碼
而後就能夠 push
成功了。可是美中不足的地方就是,軟回滾了其餘的 commit
。 可是問題不大,若是都是你本身的 commit
,那就直接 soft
吧,不是的話,能夠採用下面第二種方法。
第二種:git rebase -i commitId
git rebase -i commitId
這個命令能夠幫助你去編輯commit
,好比把幾個commit
合併成一個commit
。而這裏咱們想要作的是,經過這個命令來完成只修改上圖中的指定commit
, 同時不會對其餘已存在的commit
形成任何影響。具體操做是:
注意:git rebase -i commitId
中的 commitId
並非提示的 commitId
。 而是提示中 commitId
的前一個 commitId
。好比執行 git log
:
// .....
commit 7b7b7b7
commit avacaba
commit 8a5fca6
commit godkun666
複製代碼
那這個 commitId
就是 godkun666
。
git rebase -i godkun666
複製代碼
而後進入 VI
界面,以下面:
pick 8a5fca6 i am godkun1
pick avacaba I am godkun2
pick 7b7b7b7 I am godkun3
複製代碼
直接把缺乏 Change-Id
的 commitId[8a5fca6]
前面的 pick
修改成 reward
,而後保存退出就行了。這種方法也試用一次性修改多個缺乏 Change-Id
的 commitId
。保存退出後,就能夠直接 push
了。 對於 rebase -i
的相關知識,請自行谷歌百度,這裏不作講解。
這種狀況出如今一個同事身上了,兩個狀況的解決方法都試了,仍是不行,而後我仔細看了下,在執行:
gitdir=$(git rev-parse --git-dir); scp -p -P 29418 name@git.co.com:hooks/commit-msg ${gitdir}/hooks/
出現了一個報錯,因爲我沒有保存截圖,大體意識就是 hook is not directory
可能我這樣說出來,感受很簡單啊,可是在過程當中,這個提示是很不明顯的,後面我進入 .git
目錄看了下才知道怎麼回事, hooks
是一個文件了,不是目錄,這也是夠秀的,我初步猜想是在複製這個命令的時候,複製的不全,致使生成了 hooks
文件 。而後我刪除 hooks
後,又新建了一個 hooks
目錄,從新執行了上述命令就行了。
commitId
其所綁定的郵箱不正確。須要你先設置正確的郵箱,在設置完正確的郵箱後,咱們繼續其餘操做,我總結的有三種方法能夠解決這個問題:
第一種方法:把這個有問題的 commit
撤銷掉,可使用軟回滾 git reset --soft commitId
回滾掉。
第二種方法:若是這個 commitId
就是 head
的指向,那直接 git commit --amend
刷新這個 commitId
。
第三種方法:若是這個 commitId
就是 head
的指向, 那經過 rebase -i
去 reword
這個 commitId
。
原則:若是按照怎麼舒服怎麼來,那我就用
git -reset --soft
,若是嚴謹點,那我就按條件劃分使用下面兩種方法了。
不造輪子了,基本操做問題都在下面這篇博客中有提到:
可是,沒有本身的見解的話,那和鹹魚有什麼區別呢?
我來分析一下
including parent
和not merge
如何所示:
表面緣由:
coder
本地開發後,產生了 commit
而後 push
到 gerrit
上後,CR
者會根據狀況進行拒絕,若是拒絕了,可是 coder
本地的 commit
並無撤銷,那麼就會致使後續提交的系列 commit
出現上圖這種狀況,由於如今的 commit
依賴前面的幾回 commit
。可是前面提交的 commit
並無贊成。因此就致使了不少 CR
問題。
核心緣由:
coder
和 CR
者的 commit
時間線不一致。
如何解決:
核心是把 commit
時間線作到一致
第一種:
當 coder
成功把本地的 commit push
到 gerrit
上後,記得要 reset
掉,若是不放心,那能夠軟回滾,而後 stash
,等 CR
,若是拉下來發現沒問題,就能夠把 stash
放棄掉。
第二種:
當 push
後,切新分支進行備份,而後切回去,再把本地的 commit reset
掉。這樣就不會存在上面圖中的各個不能合併的問題的。當 CR
後,你 pull
,發現代碼都對的時候,就能夠把備份分支刪掉了。
核心思路:如今 coder
須要把本地的那些已經被 gerrit abandon
掉的那些 commit
幹掉。
第一種:
直接 從新 git clone
第二種:
切一個分支進行備份,而後切回去,使用:
git reset --hard origin/dev
複製代碼
放棄本地全部代碼,所有采用遠端代碼。。而後使用 cherry pick
把備份分支的 你須要的 commit
合到 dev
上。
PS: 固然這些只能是本地 coder
去解決這個問題。
第三種:
使用 rebase
去挨個修改或者使用 git reset --soft
把前面的不少 commit
都回滾掉。 不建議使用第三種方法,操做要求高,容易出錯。
git
倉庫代碼根目錄下執行:
git config remote.origin.push refs/for/dev
複製代碼
小烏龜 push gerrit
時會出現這種錯誤,以下圖所示:
怎麼解決呢?請看下面截圖:
用小烏龜推送 gerrit
的時候應該要在 remote
前邊手動加上 refs/for/
參考博客: TortoiseGit推送代碼到Gerrit的過程
當各個產品線提交的代碼都要你來 CR
的時候,你會發現根本無法去 CR
,由於你自己就不熟悉他們的代碼,怎麼 CR
呢,最後我決定這樣作:
各個產品線的 coder
須要 CR
的話 羣裏 at
我一下,我在 CR
的過程當中,有三個原則:
第一個原則:我默認相信各個產品線對本身負責的代碼作出修改,也就是相信 coder
修改本身負責的代碼,責任制。
第二個原則:我會嚴格關注各個 coder
有沒有改動其餘 coder
代碼,若是改動,我會去私聊詢問,爲何要這樣作。
第三個原則:我會嚴格關注各個 coder
有沒有改動公共部分的代碼,好比登陸模塊,若是改動,我會去私聊詢問,爲何要這樣作。
只要不符合上訴三個原則,一概 abandon
。
發個關於 git FAQ 的連接:git.wiki.kernel.org/index.php/G…
git
的網站上面幾篇關於
git
的文章都是我認爲很不錯的文章,能夠閱讀閱讀,會有驚喜的。
我把我日常在工做和學習中總結的 git
知識整理了一下,把最經常使用的,以 issues
的形式放在了個人 gayhub
上,有須要的小夥伴能夠點擊下面連接自取:
掘金系列技術文章彙總以下,以爲不錯的話,點個 star 鼓勵一下,一個 star 開心一年(手動滑稽) ,也能夠 gayhub
關注我一波,持續輸出精品文章。
我是源碼終結者,歡迎技術交流。