一塊兒寫一個 Linux 第一版的 git 吧!

本文只是拋磚引玉; Phtml

在下在 Shopee 工做,以爲水深火熱不喜歡加班的同窗能夠考慮一下git

拒絕 996,那就來 shopee,待遇 work life balance 兩不: www.v2ex.com/t/672561#re…算法

Naive Git

國際勞動節又稱 「五一國際勞動節」、「國際示威遊行日」(International Workers' Day 或者 May Day),是世界上 80 多個國家的全國性節日。定在每一年的五月一日。它是全世界勞動人民共同擁有的節日。數據庫

1884 年 10 月,美國和加拿大的八個國際性和全國性工人團體,在美國芝加哥舉行一個集會,決定於 1886 年 5 月 1 日舉行總罷工,迫使資本家實施八小時工做制。這一天終於來到了。1886 年 5 月 1 日,美國 2 萬多個企業的 35 萬工人停工上街,舉行了聲勢浩大的示威遊行,各類膚色,各個工種的工人一齊進行總罷工。僅芝加哥一個城市,就有 4.5 萬名工人涌上街頭。這下,美國的主要工業部門便處於癱瘓狀態,火車變成了僵蛇,商店更是鴉雀無聲,全部的倉庫也都關門並貼上封條。api

1866 年,第一國際日內瓦會議提出八小時工做制的口號。 [4] 1886 年 5 月 1 日,以美國芝加哥爲中心,在美國舉行了約 35 萬人參加的大規模罷工和示威遊行,示威者要求改善勞動條件,實行八小時工做制。1886 年 5 月 3 日芝加哥政府出動警察進行鎮壓,開槍打死兩人,事態擴大,5 月 4 日罷工工人在乾草市場廣場舉行抗議,因爲不明身份者向警察投擲炸彈,最終警察開槍,前後共有 4 位工人、7 位警察死亡,史稱 「乾草市場暴亂」(Haymarket Riot)或 「乾草市場屠殺」(Haymarket Massacre)。在隨後的宣判中有 8 位無政府主義者以謀殺罪被起訴,4 位無政府主義者被絞死,1 位在牢中自殺。bash

爲記念此次偉大的工人運動及抗議隨後的宣判,在世界範圍內舉行了工人的抗議活動。這些活動成爲了 「國際勞動節」 的前身。服務器

1889 年 7 月,在恩格斯組織召開的第二國際成立大會上宣佈將每一年的五月一日定爲國際勞動節網絡

通過艱苦的流血鬥爭,終於得到了勝利。爲記念此次工人運動,1889 年 7 月 14 日,由各國馬克思主義者召集的社會主義者表明大會,在法國巴黎隆重開幕。大會上,會表明一致贊成:把 5 月 1 日定爲國際無產階級的共同節日。這一決定當即獲得世界各國工人的積極響應。1890 年 5 月 1 日,歐美各國的工人階級率先走向街頭,舉行盛大的示威遊行與集會,爭取合法權益。今後,每逢這一天世界各國的勞動人民都要集會、遊行,以示慶祝,並公衆放假。數據結構

五一國際勞動節,一塊兒寫一個簡單的Git吧!分佈式

Git的原理是怎麼樣呢?

Git is a distributed version-control system for tracking changes in source code during software development.

各位讀者就算不了解git的原理,想必也會用三把斧git add; git commit; git push,下面就簡單說一下git是怎麼作的版本管理的:跟蹤文件的變化,使用commit做爲標記,與遠程服務器同步。

跟蹤文件變化

假如你來開發git這個工具,在初始化一個文件夾(repository)後,爲了記錄以後可能的修改,你須要記錄當前全部須要跟蹤的文件內容,最簡單的就是所有複製一份好了。

文件是否變化了?比較一下文件哈希好了。

Commit做標記

顧言思義,就是將當前的repository狀態存儲起來,做爲commit。你能夠經過commit恢復到任意狀態,git tag本質也只是給這個commit一個tag(別名),git branch 也是同樣。

恢復到某一個commit,就是將它所表明的repository狀態恢復起來,就是將文件所有內容以及當前commit恢復到那個狀態。

與遠程服務器同步

git說本身是分佈式的版本管理系統,是由於假如A、B、C三我的一塊兒合做,理論上每一個人都有一份server的版本,並且能夠獨立開發,解決衝突。

Git具體是怎麼作的呢?

原理說完了,但commit的管理是要用東西來存儲讀取管理的,Git沒有用數據庫,直接將其內容放到.git文件夾裏。

裏面有什麼內容呢?

.
  |-- HEAD //指向branch、tag (ref: refs/heads/devbranch)
  |-- index
  |-- objects
  |   |-- 05
  |   |   `-- 76fac355dd17e39fd2671b010e36299f713b4d
  |   |-- 0c
  |   |   `-- 819c497e4eca8e08422e61adec781cc91d125d
  |   |-- fe
  |   |   `-- 897108953cc224f417551031beacc396b11fb0
  |   |-- fe
  |   |   `-- 897108953cc224f417551031beacc396b11fb0
  |   |-- info
  |  
  `-- refs
      |-- heads //各個branch的heads
      |   `-- master //此分支最新的commit id
      |   `-- devBranch // checkout -b branch就會生成的branch
      `-- tags
          `-- v0.1
複製代碼

各位再結合

下面我展開講講:

  • HEAD: 指向branch或者tag,標記當前是在哪一個分支或者tag上;
  • index: TODO
  • objects:記錄文件的內容,每一個文件夾名稱是該object的sha1值的前兩位,文件夾下的文件名稱是sha1值的後18位;(tips:sha1算法,是一種加密算法,會計算當前內容的哈希值,做爲object的文件名,獲得的哈希值是一個用十六進制數字組成的字符串(長度爲40))
  • refs
    • heads: heads裏的就是各個分支的HEAD分別指向哪一個commit id;簡單說,就是 各個branch分別最新的commit是什麼,這樣子git checkout branch就能夠切換到對的地方
    • tags: 同理,這個文件夾裏存的都是各個tag

那麼,新建一個branch的時候,只要在refs/heads文件夾裏新建branch 名字的文件,並將當前commit id存進去便可;

新建一個commit時,只要根據HEAD文件,找到當前的branch或者tag 是什麼,修改裏面的內容便可。

有點很差懂?咱給出一個git的實例,默認在一個文件夾執行git init後,添加一個文件並commit的信息, commit id爲017aa3d7851e8bbff78a697566b5f827b183483c

$ cat .git/HEAD
ref: refs/heads/master
$ cat .git/refs/heads/master
017aa3d7851e8bbff78a697566b5f827b183483c
複製代碼

如上,HEAD指向了master,而master的commit id正是剛剛commit的id。

存儲讀取解決了,那麼commit怎麼組織呢?

將當前的repository狀態存儲起來,做爲commit。你能夠經過commit恢復到任意狀態,git tag本質也只是給這個commit一個tag(別名),git branch 也是同樣。

恢復到某一個commit,就是將它所表明的repository狀態恢復起來,就是將文件所有內容以及當前commit恢復到那個狀態。

上面說了,管理文件夾(repository)狀態,可是文件夾是能夠嵌套的,與文件不同,須要有這層級關係,同時也要存文件內容,怎麼作來區分呢?

咱們能夠引入如下概念:

  • Tree:表明文件夾,由於git init時,就是把當前文件夾./做爲項目來管理,那麼接下來全部要追蹤的項目無非就是./裏的文件或者文件夾而已;

  • Blob:文件,Tree裏能夠包含它;

關係以下圖:

給點咱們寫的數據結構代碼你看看,要注意的是,tree能夠擁有blob或者tree,因此用了unionparentnext做爲鏈表使用,做爲文件夾目錄管理;

struct tree_entry_list {
    struct tree_entry_list *next;
    union {
        struct tree *tree;
        struct blob *blob;
    } item;
    struct tree_entry_list *parent;
};

struct tree {
    struct tree_entry_list *entries;
};
複製代碼

commit跟樹同樣,也是有層級的單鏈表,不過只有

struct commit {
    struct commit *parents;
    struct tree *tree;

    char *commit_id[10];
    char *author;
    char *committer;
    char *changelog;
};
複製代碼

一圖勝千言,看圖吧:

雲風的遊戲資源倉庫及升級發佈

雲風參考過git的原理作過一個遊戲資源倉庫管理,我下面講一下它跟git的區別,他的文章我以爲比較繞,沒有背景知識的人很難看明白。

背景

咱們的引擎的一個重要特性就是,在 PC 上開發,在移動設備上運行調試。咱們須要頻繁的將資源同步到設備上

程序以 c/s 結構運行時,在移動設備上先創建一個空的鏡像倉庫,同步 PC 端的資源倉庫。運行流程是這樣的:

首先在客戶端啓動的時候,向服務器索取一個根索引的 hash ,在本地鏡像上設定根。

客戶端請求一個文件路徑時,從根開始尋找對應的目錄索引文件,逐級查找。若是本地有所需的 hash 對象,就直接使用;不然向服務器請求,直到最後得到目標文件。api 的設計上,open 一個資源路徑,要麼返回最終的文件,要麼返回一個 hash ,表示當前還缺乏這個 hash 對象;這樣,能夠經過網絡模塊請求這個對象;得到該對象後,無須理會這個對象是什麼,簡單寫入鏡像倉庫,而後從新前面的過程,再次請求未完成的路徑,最終就能打開所需的資源文件。

場景是:Client <- 他的遊戲服務器 ,單向同步;

他是這樣子作的,客戶端的倉庫是key-value的文件數據庫,key是文件的hash,value就是文件內容;

同步時,會從根到具體hash全量同步文件下載到數據庫

假如客戶端使用資源時,發現缺少這個文件,就用hash去服務器拉下來。

換言之,由於不須要管理本地版本,而且同步到上游,因此無需在本地記錄全量的版本狀態

跟Git的區別:

場景是:Client <-> gitHub ,雙向同步;

git 須要本地組織commit,切換本地有但服務器沒有的版本(就是離線操做) ,同時還須要將變動同步到上游。

最後的建議

若是看完該文,讓你躍躍欲試的話,請不要用C寫,請不要用C寫,請不要用C寫。

從零開始寫過幾個大一點項目,每次都以爲用C寫項目太難受了,此次我寫git commit時,發現要讀寫文件,解析內容,我發出了心裏的感嘆:

太難了,不是寫這個難,是C太難用了。。

想到我要遍歷這些文件,根據目錄獲得tree的hash,而後還要update這棵樹,把tree跟commit還要blob反序列存到文件裏,還要讀出來,以後還要組織鏈表操做,用C寫就以爲百般阻撓。。。

相關文章
相關標籤/搜索