如何學習優秀的開源代碼?目前大部分的優秀開源代碼,代碼量都已經非常龐大,比如git。以git為例,git最新版本代碼有279814行,
而git0.7版本已經大部分實現了現在git版本的基本功能,而代碼量卻只有4950行,
借助ai工具幫忙分析和整理,加上自己的代碼閱讀和學習驗證,就可以從這些開源大神的代碼中學到很多,從而提高自己。
學習源碼之前,先編譯和研究下現有編譯的程序的使用。
編譯
本人使用的筆記本是macbook,所以下面的是在macsos下編譯,window和linux需自行驗證。
執行make命令,報錯如下:
“編譯失敗了,錯誤信息是 openssl/sha.h: No such file or directory。”
Makefile 默認使用 OpenSSL 的 SHA1 實現,但我的macos環境中沒有安裝OpenSSL的開發庫,
不過項目本身在mozilla-sha1/和ppc/目錄下提供了SHA1的實現。
可以修改Makefile來使用這些內置的實現。
修改Makefile
修改如下:
diff --git a/cpp/git-0.7/Makefile b/cpp/git-0.7/Makefile
index a4987f3..aa52a05 100755
--- a/cpp/git-0.7/Makefile
+++ b/cpp/git-0.7/Makefile
@@ -39,7 +39,7 @@ LIB_H += diff.hLIB_OBJS += diff.oLIBS = $(LIB_FILE)
-LIBS += -lz -lcrypto
+LIBS += -lzifdef MOZILLA_SHA1SHA1_HEADER="mozilla-sha1/sha1.h"
@@ -50,7 +50,7 @@ ifdef PPC_SHA1LIB_OBJS += ppc/sha1.o ppc/sha1ppc.oelseSHA1_HEADER=<openssl/sha.h>
- ?LIBS += -lssl
+ ?LIBS += -lssl -lcryptoendifendif
重新編譯
make MOZILLA_SHA1=1
ok,編譯成功了。
會在當前目錄生成很多git-*的可執行程序。
0.7版本的git,命令都是以git-開頭的,比如git-init-db,git-update-cache,git-write-tree,git-commit-tree等。不像現在的git,命令都是git開頭的,比如git init,git add,git commit等。
學習使用
先使用,才能更好地去學習源碼。把當前目錄加入PATH中方便使用命令(后續命令都是按加入PATH中)。
根據 README 中的 “Workflow” 部分,一個基本的使用流程如下:
第一步:初始化一個新的 “git” 倉庫
創建一個 .git 目錄,里面包含了對象數據庫 (.git/objects) 和其他必要的文件。
mkdir ~/git_test
cd ~/git_test
git-init-db
第二步:將文件添加到暫存區 (index)
創建一個測試的新文件 hello.txt。
- touch hello.txt
- echo “hello world” > hello.txt
- git-update-cache --add hello.txt
其中git-update-cache命令會:
- 為 hello.txt 創建一個 blob 對象,并將其存入對象數據庫。
- 在 index 文件中記錄 hello.txt 的信息(文件名、權限、SHA1 等)。
第三步:創建一個 tree
對象
tree 對象代表了當前暫存區 (index) 的狀態。
git-write-tree
這個命令會輸出一個40個字符的SHA1哈希,這就是新創建的tree對象的 ID。需要記下這個ID。
第四步:創建一個 commit
對象
這個 commit 對象會將上一步創建的 tree 對象與一個提交信息和父提交(如果有的話)關聯起來。
- 假設上一步得到的 tree SHA1 是 <tree_sha1>
- -p <parent_sha1> 是可選的,第一次提交沒有父提交
- echo “Initial commit” | ./git-commit-tree <tree_sha1>
注意git0.7這個版本,tree_sha1是需要輸入完整的字符的。
這個命令會輸出一個新的 SHA1 哈希,這是 commit 對象的 ID。可以將這個ID保存到一個文件里,比如.git/HEAD,來跟蹤當前的分支。
其他常用命令
-
git-cat-file
* 功能:?
1. 顯示對象類型 (-t
選項):
* 給定一個對象的 SHA1 哈希,它會告訴你這個對象是 blob (文件內容)、tree (目錄結構) 還是 commit (提交記錄)。
2. 顯示對象內容 (指定類型):
* 給定一個對象的 SHA1 哈希和其類型(blob、tree 或 commit),它會打印出該對象的原始內容。
* 用法:?
git-cat-file -t 查看對象類型。
git-cat-file 查看對象內容 -
git-ls-tree
* 功能:
1. 列出tree
對象的內容: 它會解析一個 tree 對象的二進制數據,并以人類可讀的格式顯示其包含的條目。
2. 顯示文件和子目錄: 對于 tree 對象中的每個條目,它會顯示其模式(權限)、類型(blob 或 tree)、SHA1 哈希以及對應的文件名或目錄名。
3. 遞歸顯示 (可能): 現代 Git 的 ls-tree 命令通常支持遞歸顯示子目錄內容,這個早期版本可能也有類似的功能.
* 用法: git-ls-tree <tree_sha1> 這會列出 tree 對象中的文件和目錄。
git-read-tree
* 功能:?
1. 更新暫存區: 它的主要作用是用一個指定的 tree 對象所代表的目錄結構和文件內容來完全替換或更新當前的暫存區(.git/index 文件)。
2. 準備工作目錄: 當你需要將倉庫歷史中的某個特定狀態(由一個 tree 對象表示)恢復到暫存區時,git-read-tree
是第一步。例如,在切換分支、合并或檢出舊版本時,你首先會使用 git-read-tree 來更新暫存區,然后可能再使用 git-checkout-cache 將暫存區的內容寫入工作目錄。
* 用法: git-read-tree <tree_sha1>
<tree_sha1>: 你想要加載到暫存區中的 tree 對象的 40 位 SHA1 哈希值。git-read-tree<tree_sha1>這會用指定的tree對象更新 index。
4. git-checkout-cache
* 功能:?
1. 檢出 index 中的文件: 它會從 index 中檢出文件到工作目錄,類似于 git checkout。
2. 檢出 index 中的所有文件: 它會檢出 index 中的所有文件到工作目錄。
* 用法: git-checkout-cache -a 這會檢出 index 中的所有文件到工作目錄。
-
git-diff-files
* 功能: 比較工作目錄中的文件與暫存區(index)中的對應文件之間的差異。
* 用法: git-diff-files 這會顯示工作目錄中所有已修改但尚未添加到暫存區的文件差異。 -
git-diff-tree
* 功能: 比較兩個 tree 對象之間的差異,或者一個 tree 對象與工作目錄/暫存區之間的差異。
* 用法:
git-diff-tree <tree_sha1_1> <tree_sha1_2>
比較兩個 tree 對象
git-diff-tree <tree_sha1>
比較一個 tree 對象與當前暫存區/工作目錄 (具體行為可能需要查看源碼或幫助) -
git-rev-tree
* 功能: 遍歷一個 tree 對象及其所有子對象(包括 blob 和嵌套的 tree),并打印它們的 SHA1 哈希和路徑。
* 用法: git-rev-tree <tree_sha1> 這類似于 git ls-tree -r 的功能。 -
git-show-files
* 功能: 顯示暫存區(index)中所有文件的信息,包括模式、SHA1 和文件名。
* 用法: git-show-files 這類似于 git ls-files --stage。 -
git-check-files
* 功能: 檢查工作目錄中的文件是否與暫存區中的文件匹配。它會報告哪些文件在工作目錄中被修改、刪除或新增。
* 用法: git-check-files -
git-merge-base
* 功能: 查找兩個或多個提交(commit)的最近共同祖先。這是進行三方合并(three-way merge)的基礎。
* 用法: git-merge-base <commit_sha1_1> <commit_sha1_2> 會輸出共同祖先的 SHA1 哈希。 -
git-merge-cache
* 功能: 執行三方合并,將三個 tree 對象(通常是共同祖先、分支 A 和分支 B)合并到暫存區(index)中。
* 用法: git-merge-cache <base_tree_sha1> <our_tree_sha1> <their_tree_sha1>
這個命令會將合并結果寫入暫存區。如果存在沖突,暫存區會包含沖突標記,需要手動解決。 -
git-unpack-file
* 功能: 從對象數據庫中解壓一個 blob 對象并將其內容寫入標準輸出。
* 用法: git-unpack-file <blob_sha1> > output_file.txt -
git-export
* 功能: 將一個 tree 對象的內容導出到指定目錄。
* 用法: git-export <tree_sha1> <output_directory> -
git-diff-cache
* 功能: 比較暫存區(index)與一個 tree 對象之間的差異。
* 用法: git-diff-cache <tree_sha1> -
git-rev-list
* 功能: 遍歷提交歷史,并以逆序(從最新到最舊)打印提交的 SHA1 哈希。
* 用法: git-rev-list <commit_sha1> 可以用來查看一個提交的所有祖先。 -
git-mktag
* 功能: 創建一個 tag 對象。在 Git 中,標簽可以指向一個提交、一個樹或一個 blob,通常用于標記重要的版本。
* 用法: echo “My tag message” | ./git-mktag <object_sha1> <object_type> 它會輸出新創建的 tag 對象的 SHA1 哈希。 -
git-tar-tree
* 功能: 將一個 tree 對象的內容打包成一個 tar 歸檔文件。
* 用法: git-tar-tree <tree_sha1> > archive.tar
總結一下,一個完整的從無到有的提交流程是:(fish shell為例)
- git-init-db
- 創建或修改文件
- git-update-cache --add …
- set -x TREE_ID $(git-write-tree)
- set -x COMMIT_ID $(echo “My commit message” | git-commit-tree $TREE_ID)
- echo $COMMIT_ID > .git/HEAD
這就是這個早期 git 0.7版本的基本用法。它比現代的git要底層和手動得多,但核心概念是一致的。