序
前半部分屬於基礎,後半部分屬於進階。從初級到中級再到我都 hold
不住的高階。全文共 12000
餘字,超幹超乾的那種。
然而,寫完一半的時候,我突然虎軀一震,我是不是在造輪子?隨後我悄悄的搜了一下 git
。 嗯?這麼多 git
文章,我滴天呢,我陷入了沉思,我皺著眉頭點了幾篇文章,有號稱封山之作的2萬字真理,也有完整詳細的 git
系列教程。好像有點輪子啊,但是我繼續看了下他們的內容後,有種茅廁頓悟般的驚喜,因為我發現我的文章還是很獨樹一幟的。最後得出一個結論,我沒有造輪子,這是一篇高可用高擴充套件高效能的 git
和 gerrit
文章。用實戰去推動思考,kill
掉大眾化的 git
知識,從常用的角度去擴充套件深層的知識,進而抽象出我們可以理解掌握的 git
奧祕。不拘泥於 API
,不畏懼其他輪子,不要慫,就是幹。
本文是站在別人的 commit
上去 merge
和 patch
我自己獨具特色的理解,從而生成一個更好的 commit
,然後留給大家日後更好的 merge
和 patch
,技術在一次次 patch
中不斷進步。
開門見山
在實際專案開發中,能靈活的使用 git
和 gerrit
是一個很重要的事情,一方面可以提高團隊開發效率,另一方面可以把控專案的程式碼質量。不過對於 gerrit
, 可能一些同學沒有用過,還有 git
的一些你可能沒有掌握的技巧,今天就一起分享給大家,分享的同時,自己也有很多即時收穫。
PS: 為什麼我會說我自己也會有很多收穫呢,因為是這樣的:當我選擇寫一篇部落格的時候,我會自己先去深入的理解我寫的這篇部落格的相關知識點,在深入理解這個過程中,我會去閱讀各種資料,然後去分析,最後總結出屬於我自己特色的學習心得,這對我來說,就是一種即時收穫和高階進階。
為什麼會出現 git
這裡我們用 git
, 我們就應該去了解一下 git
出現的背景,具體故事不說了,自行維基。這裡我簡單說一下 git
的出現,在技術層面上的背景。
git
是一個開源的 分散式版本控制系統 ,原始碼也在 github
上開源了,可以自行搜尋。提到分散式版本控制系統,那應該聯想到 集中式版本控制系統 ,具有代表性的比如 SVN
, SVN
的全稱是 Subversion
。
那這兩者究竟有什麼區別呢?用兩張圖對比一下:
SVN:
GIT:
從圖中基本可以分析出兩者的主要區別。比如:
git
可以離線開發,svn
不能離線git
處理merge
的優勢碾壓svn
當然其他的區別還有很多,比如 git
的內容更完整,使用了 SHA-1
演算法,git
可以更加靈活的操作 branch
。 等等,這裡就不造輪子了,參考下面這篇部落格:
看到這裡,我們可能比較偏向於, SVN
比 Git
差一些的觀點,但其實這是兩種不同的版本控制系統,不能簡單的認為誰好誰壞,這裡有一篇為 SVN
洗白的部落格,挺有趣的,大家可以看看:
要是高度總結一下,那就是: SVN 更適用於專案管理, Git 更適用於程式碼管理。
為什麼會出現 gerrit
我們看維基介紹:
言簡意賅:從維基上可知,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 命名的由來
有時候,我們可能對為什麼叫 git
、 gerrit
不怎麼在意。但是很多命名都是有自己的故事的,比如 vue
, react
為什麼這樣命名。可以去查閱資料瞭解一下,這有助於我們更形象化的理解它們。
git 的命名
比如說,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.
gerrit 的命名
由於已經說了 git
的命名由來了,這裡我就言簡意賅點,gerrit
的命名來自於荷蘭設計師赫裡特·裡特費爾德( Gerrit Rietveld
) 的名字首個單詞。
為什麼要用 git
這也是一個值得思考的問題,我們為什麼要用
git
?
直覺上,我們自然而然的用了,發現也很好用。那我們可以問一下自己,git
為什麼很好用,如果我們看了上面提到的部落格,可能我們已經有了答案,其實很多很棒的東西的誕生,都是在誕生的某個維度背景下,解決了大部分同類沒有解決掉的痛點。
所以現在我們用了 git
,我們也覺得很好用,但是事實上我們好像並不清楚 git
的出現,解決了什麼樣的痛點,我們只知道好用。我說這句話,就是想說明一下,去了解一個東西,最好去了解這個東西誕生時所處的時代背景或者技術背景。哎,好像我沒有回答為什麼要用 git
? 不慌,問題不大,其實答案已經在前面提到了。
談談 git-flow 流程
網上有很多 git-flow
開發流程的部落格,這裡不進行講解了,但是我想講的就是:
總結出一個符合本專案的
git
開發模式,才是真正意義上的git-flow
。
如何去制定一個好的 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 怎麼實施
這裡以我目前參與的一個大型專案作為例子,說一下如何在實踐中,總結出屬於本專案的 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
初級。
而下面我要說的就是
git
的中級知識
如果你能夠靈活運用 git
知識去解決版本控制過程中的各種問題,那就可以說你是屬於中級水平了。
這裡我想說一點:
我是用的命令列形式去進行 git
操作的,當然也有很多人是用的 SourceTree
VsCode
WebStorm
這種軟體去操作 git
。 不過每個人應該都有主次之分,比如我,就主用命令列,VsCode
我也用。
我一般的使用規律就是:
除了我需要去閱讀檔案,對比檔案前後版本,或者檢視多個歷史版本時,我會用 VsCode
外, 其他操作都統一用命令列解決。
PS: 用命令列玩轉 git
的話,那基本的 linux
知識還是要掌握的,如果有興趣可以去學學 linux
。 推薦書籍:
《鳥哥的 Linux 私房菜: 基礎學習篇》
因為生命不止,學習不止。
git 中級之理論知識
很多人只是在記 git
的命令操作,並不清楚這樣做的底層原因,從而導致了 知其然不知其所以然,最後就沒有辦法在一個大的知識層面上對 git
進行一個更為抽象和深刻的理解。
下面我會站在別人的肩膀上( 不重複勞動 ),根據我所學習的 git
知識來簡要分析一下 git
的一些中級理論知識。
這裡我用網上的一張圖來簡單概括一下
git
的API
級別的原理,圖片如下:
然後我再展示大佬 Vamei
的兩張 git
分析圖( 圖 加 文字分析 ):
第一張圖:
第二張圖:
上面三張圖分別是一張 API
級別的 圖 和 大佬 Vamei
的兩張 git
原理分析圖。
如果對上面的三張圖理解深刻的話,是能從圖中就能感受到 git
的設計思想和與眾不同的特點。如果能理解深刻,那其實也可以說你已經掌握了中級的理論知識了。
但是不理解沒關係,下面我會簡要分析一下
git
的中級理論知識。
git init 幹了什麼
要想知道 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 目錄
上面我們通過 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 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 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
。
可以參考這篇部落格:
git merge 和 git rebase
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
:
git 中的 blob
commit
tag
tree
是怎麼串起來的
其實這是一個非常關鍵的問題,很多人都不清除這些 單詞
背後的的真理究竟是一種什麼樣子的美麗。
但是我不打算造輪子了,因為好文章太多了,這裡我還想放上面的一張圖,因為這張圖太經典了。
解釋已經在圖中的文字中了,比如知道了這些,你就知道了我們在給版本打上 tag
的時候,究竟是做了什麼。我們不能浮於表面,只知道要打 tag
,我們還要知道打 tag
背後的原因。只有這樣,才能做到知其然知其所以然。
終結 tag : github.com/godkun/git-…
其他零碎的知識點
COMMIT_EDITMSG 檔案
此檔案裡面寫的內容是本地最後一個提交的資訊
packed-refs
clone倉庫時所有的引用
git 中級之實戰技巧
我把在使用 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
操作複雜化,一些黑科技什麼的,也沒有必要去關注,有句話是這樣說的:能用簡單的操作解決複雜的問題才是大牛。所以上面我介紹的實戰技巧,可以說沒有什麼高大深的技巧,當理解的足夠深入的時候,通過簡單的操作也可以保證專案的有序進行。
git 高階 -- 你可能不知道的 git 知識
這裡呼應一下文章開始所說的那一句話:
從初級到中級再到我都 hold
不住的高階。
為什麼我說我都 hold
不住呢?是因為我真的 hold
不住。但是我還是去學習了一番,重新簡單翻了一遍 C
和 C++
語言,嘗試著去理解一下。
簡單看一下 github 上的 git 原始碼
首先把 github
上的 git
倉庫 clone
下來。
先看一下 git 專案 程式碼量
這裡我用到一個程式碼行數分析工具 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 原始碼
執行 git checkou master
切到 master 分支
從圖中我們可以看到,有很多很多東西,一點都不想分析,那就不分析了,都1萬多字了,寫不動了。就這麼愉快的同意啦!開開心心過完年後,再單獨寫一篇( 嘿嘿嘿 )。
gerrit 原理知識
這個原理知識就不說了,簡單點說就是搭一個 gerrit
伺服器,然後通過 UI
介面去進行程式碼的 CR
,CR
通過,點選 submit
就會把程式碼同步到 gitlab
上。
gerrit 實戰總結
本人負責給專案實施 gerrit
, 並解決同事在過渡到 gerrit
方式的過程中出現的各種問題。我在解決各種問題的時候,對整個 gerrit
的流程和操作都理解了狠多,下面就分享一下我在幫助同事過渡 gerrit
的過程中遇到的問題和總結的一些心得吧。
gerrit 基本設定
這個就不說了,基本的像 ssh
認證 、 remote
設定、郵箱設定、這種我就不造輪子了,按照網上的基本教程來。
提交 gerrit 時提示缺失 Change-Id
問題描述
這個錯誤,是在過渡到 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
但是在解決這個問題的過程中,我發現上面的提示,有時候並不能成功。我總結出了幾種情況,下面一一列出。
缺少 Change-Id 的 commitId
是 head 指向的 commitId
如果是 head
的話,也就是 git log
的第一個 commitId
。 那可以直接按照上面提示的命令去執行。
這裡提一下,在執行 git commit --amend
時,會進入 vi
介面,進入後可以不用修改任何東西的,直接儲存退出即可,就可以重新重新整理 head
指向 的 commitId
的 值了。
缺少 Change-Id 的 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
目錄,重新執行了上述命令就好了。
commiter email address xxxx does not match your user account
出現這種問題,是因為圖中提示的commitId
其所繫結的郵箱不正確。需要你先設定正確的郵箱,在設定完正確的郵箱後,我們繼續其他操作,我總結的有三種方法可以解決這個問題:
第一種方法:把這個有問題的 commit
撤銷掉,可以使用軟回滾 git reset --soft commitId
回滾掉。
第二種方法:如果這個 commitId
就是 head
的指向,那直接 git commit --amend
重新整理這個 commitId
。
第三種方法:如果這個 commitId
就是 head
的指向, 那通過 rebase -i
去 reword
這個 commitId
。
原則:如果按照怎麼舒服怎麼來,那我就用
git -reset --soft
,如果嚴謹點,那我就按條件劃分使用下面兩種方法了。
gerrit cannot merge and Submit including parents
不造輪子了,基本操作問題都在下面這篇部落格中有提到:
但是,沒有自己的看法的話,那和鹹魚有什麼區別呢?
我來分析一下
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
都回滾掉。
不建議使用第三種方法,操作要求高,容易出錯。
how to make SourceTree push to Gerrit
git
倉庫程式碼根目錄下執行:
git config remote.origin.push refs/for/dev
複製程式碼
how to make TortoiseGit push to Gerrit
小烏龜 push gerrit
時會出現這種錯誤,如下圖所示:
怎麼解決呢?請看下面截圖:
用小烏龜推送 gerrit
的時候應該要在 remote
前邊手動加上 refs/for/
參考部落格: TortoiseGit推送程式碼到Gerrit的過程
如何快速高效的 CR ( coder review )
當各個產品線提交的程式碼都要你來 CR
的時候,你會發現根本沒法去 CR
,因為你本身就不熟悉他們的程式碼,怎麼 CR
呢,最後我決定這樣做:
各個產品線的 coder
需要 CR
的話 群裡 at
我一下,我在 CR
的過程中,有三個原則:
第一個原則:我預設相信各個產品線對自己負責的程式碼做出修改,也就是相信 coder
修改自己負責的程式碼,責任制。
第二個原則:我會嚴格關注各個 coder
有沒有改動其他 coder
程式碼,如果改動,我會去私聊詢問,為什麼要這樣做。
第三個原則:我會嚴格關注各個 coder
有沒有改動公共部分的程式碼,比如登入模組,如果改動,我會去私聊詢問,為什麼要這樣做。
只要不符合上訴三個原則,一律 abandon
。
git FAQ 傳送
發個關於 git FAQ 的連結:git.wiki.kernel.org/index.php/G…
參考連結
- github.com/git/git
- fabiensanglard.net/git_code_re…
- nvie.com/posts/a-suc…
- git-scm.com/book/en/v2/…
- schacon.github.io/git/user-ma…
- blogs.msdn.microsoft.com/devops/2018…
- aosabook.org/en/git.html 這是一個很不錯的介紹git的網站
- learngitbranching.js.org/ 這是一個線上實驗
git
的網站 - mirrors.edge.kernel.org/pub/softwar…
- mirrors.edge.kernel.org/pub/softwar…
- mirrors.edge.kernel.org/pub/softwar…
- mirrors.edge.kernel.org/pub/softwar…
上面幾篇關於
git
的文章都是我認為很不錯的文章,可以閱讀閱讀,會有驚喜的。
備註
- 有一些知識是點到為止,就這都寫了12000多字了,理解一下 ( 笑哭 )。
- 文章內容肯定有錯誤,歡迎小夥伴討論指出哈。
- 文章有點長,閱讀體驗可能不佳,但是又不忍心分開寫,先就這樣吧 ( 心 塞 )。
交流 + 福利
我把我平常在工作和學習中總結的 git
知識整理了一下,把最常用的,以 issues
的形式放在了我的 gayhub
上,有需要的小夥伴可以點選下面連結自取:
掘金系列技術文章彙總如下,覺得不錯的話,點個 star 鼓勵一下,一個 star 開心一年(手動滑稽) ,也可以 gayhub
關注我一波,持續輸出精品文章。
我是原始碼終結者,歡迎技術交流。