# git ###### tags: `git` ## head 代表 git 當前的 commit 指向,預設指向 master ,藉由移動 Head 位置更新當前分之。 ## git reset reset 代表前往檔某一個commit 節點上,實際上 reset 並不會改變commit狀態只是改變Head的位置而已,以下是reset範例 ### 前往前兩個commit ``` $ git reset HEAD~2 ``` ### 前往master的上一個 ``` $ git reset master^ ``` ### 前往上一個commit ``` $ git reset HEAD^ ``` ### 前往某一個commit id ``` $ git reset 85e7e30 ``` ## reset 模式 `git reset` 指令可以搭配參數使用,常見到的三種參數,分別是 --mixed、--soft 以及 --hard,不同的參數執行之後會有稍微不太一樣的結果。 ### mixed 模式 `mixed` 是預設參數,只會更動暫存區的資料,工作區的內容並不會改變,範例如下: 透過git add將檔案夾到暫存區,此時還沒 `commit` 到 `local` ![](https://i.imgur.com/6AEpteS.png) 輸入 `git reset` 後會自動把暫存區的檔案移除 ![](https://i.imgur.com/PlrCC63.png) ### soft 模式 這個模式下的 reset,工作目錄跟暫存區的檔案都不會被丟掉,所以看起來就只有 HEAD 的移動而已。也因此,Commit 拆出來的檔案會直接放在暫存區。 ### hard 模式 在這個模式下,不管是工作目錄以及暫存區的檔案都會丟掉。 **畫個表格整理一下** | 暫存區修改 | 資料夾修改 | 行為 | | ---- | ---- | ---- | | 丟掉 | 不變 | 丟掉 | | 不變 | 不變 簡單來說呢 | Commit 拆出來的檔案 | mixed 模式 | soft 模式 | hard 模式 | | --- | --- | --- | --- | | 丟回工作目錄 | 不變 | 不變 | 不變 | ## add file to current commit ``` git commit --amend --no-edit ``` ## 移除提交在遠端的檔案 ``` git rm -r --cached node_modules ``` ## git ssh 生成 ssh key ```typescript >ssh-keygen ``` ssh key 有分公鑰跟私鑰會在 ~/.ssh資料夾底下,如果有此資料夾代表 ssh key 生成成功摟 id_rsa.pub:公開金鑰 (可以讓大家都知道) id_rsa:私密金鑰 (無論如何不能洩漏) 之後讀取 key ```typescript > cat ~/.ssh/id_rsa.pub ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQC5E6AJLMztTmfR5eXZcEpI4dd2+b49eerIUPGedKqDNlDFSxEKKan5eSl/jTNK+gl4YJrP1RkNPDN1edVwxdQkIjIiF2gcexULnVqi+OjdJth/dkBCCiuh0BFZxCCvIT0AlAD7SF6oxX8sRJO6Y3GrXWlVgtQxzCfJmgb2zH/RrAhrV8PtWf5Bd0Z2RFzCzWZi4+wFf2Jqr8pNrvGDFNiX0vw+Rs9iD//vELsiqwJS53v1RWoGuzKqJ2SFOrIrxMUSkNTiDgDSu6vAKUjVZLXufJmQoo/uTx2WyS0q8bLIdMNSUvl5U15nI9XO9t6qoQMyc1Ar/NkcQk8YM1pjQdvCfOKfWqGEpwSlf/kWL6rZgVf1mtTh9tNUtIbhC4Sv3fVKD4H/21JZeLEeF3MJuC7bfJFLk3V9eBRYb+M+x1G0oWvi/Fljnhv7sN6LvEUh9Kj7zddqTe6h0hMOUmIr6300WYOk+d4aNQhJEzan+8UDajwc+RS0zIPjjbJBVmBNP18= danny@dannydeMacBook-Pro.local ``` 然後把 ssh 貼到 github上就可以瞜~最後測試 ssh 連線如果出現 hello 就代表你電腦成功 ssh 連線到 github 了。 ```typescript > ssh -T git@github.com Hi Danny101201! You've successfully authenticated, but GitHub does not provide shell access. ``` ## git file commands 一個 git 推出新的 file monitor ,解決 monorep git command 因為 trace file change 執行時間過長問題, 一張圖顯示有使用 fsmonitor--daemon 的差別。 備註 : fsmonitor 是 Git version 2.37.0. 推出的新功能所以不用額外安裝預設就有。 ![](https://hackmd.io/_uploads/ryAr2nc8h.png) [圖片來源](https://github.blog/2022-06-29-improve-git-monorepo-performance-with-a-file-system-monitor/) ### 執行指令 ```javascript= // 在背景執行 fsmonitor (推薦) > git fsmonitor--daemon start // 在 terminal 執行 fsmonitor (推薦) > git fsmonitor--daemon run // 停止 fsmonitor > git fsmonitor--daemon stop // 查看 fsmonitor 當前狀態 > git fsmonitor--daemon status ``` ## 資源比較 ```javascript // 使用 time 查看當前指令執行時間 $ time git status On branch feat/table_paginaction Your branch is up to date with 'origin/feat/table_paginaction'. Changes not staged for commit: (use "git add <file>..." to update what will be committed) (use "git restore <file>..." to discard changes in working directory) modified: src/components/pages/LoginPage.tsx no changes added to commit (use "git add" and/or "git commit -a") git status 0.00s user 0.01s system 86% cpu 0.014 total // 設定 config 讓 fsmonitor 追蹤檔案 $ git config core.fsmonitor true $ git config core.untrackedcache true $ time git status On branch feat/table_paginaction Your branch is up to date with 'origin/feat/table_paginaction'. Changes not staged for commit: (use "git add <file>..." to update what will be committed) (use "git restore <file>..." to discard changes in working directory) modified: src/components/pages/LoginPage.tsx no changes added to commit (use "git add" and/or "git commit -a") git status 0.00s user 0.01s system 67% cpu 0.018 total ``` 只是簡單修改幾個檔案 fsmonitor 就大大減少 system 佔用率 ## fsmonitor 的優化 由於 git status 他會去追蹤整個專案檔案有無改變,所以每次執行都會全補掃描一次,所以如果你的檔案數越大,花費查詢的時間會更多。 * fsmonitor 他是一直長時間進行的 process * fsmonitor 他將當前目錄的路徑添加內存中的時間排序 * 透過時間排序進行系統註冊就可以通知哪些檔案修排的時間點 * 透過 IPC connections 在 client 端執行 * 可以立即響應 client 端最近修改過的 file 的請求 IPC connections 有點像是多個 process 間通訊的一種協議他可以大大增加process執行效率,這裡不多做說明。 ![](https://hackmd.io/_uploads/HJUXVpcLn.png) [圖片來源](https://github.blog/2022-06-29-improve-git-monorepo-performance-with-a-file-system-monitor/) ## FSMonitor token * 定義時間排序時的id 或是timestamp * 每當系統事件發生,例如crud file都會生成一個new token * 將多個 token 分配到各自的 file system 中 * git cammand 就可以透過這個token 去找到對應分組的file system中 ## git workTree git status 它會查找已跟踪、未跟踪和忽略的文件 ![](https://hackmd.io/_uploads/rJNqBp5Ih.png) * 已跟踪 :已經被 git 放進去的 commit 的file,同時放進去的檔案會對應到一個index。 * 未跟踪 : git 不知道他們,同時也沒被用 index 標記,可能是臨時文件或是新文件,所以才會用 git add 將未跟踪檔案放到已跟踪 。 * 忽略 : 忽略文件是一個特殊的未跟踪文件,可能是臨時文件或是編譯生成的文件,例如你放在`.gitignore`文件中一樣,雖然他不會被 git add 加到已跟踪 ,但也是會被 git search 的 file,造成file 讀取慢的原因。 備註: `git status` 通常不打印忽略的文件 可以透過以下的指令查詢 Ignored 的 file change ```javascript git status --ignored On branch feat/table_paginaction Your branch is up to date with 'origin/feat/table_paginaction'. Changes not staged for commit: (use "git add <file>..." to update what will be committed) (use "git restore <file>..." to discard changes in working directory) modified: src/components/pages/LoginPage.tsx Ignored files: (use "git add -f <file>..." to include in what will be committed) .env .husky/_/ .vscode/ node_modules/ no changes added to commit (use "git add" and/or "git commit -a") ``` ## 你不知道的 git status ### 第一階段 refresh_index 由 git workTree 可以看到每個 **已追蹤** file 都會對應一個index,可以透過以下 command 去查詢。 ```javascript $ git ls-files --stage --debug 100644 581043ded3decaf69a6733c029e5f26d41e626f2 0 vitest-setup.ts ctime: 1684486432:233418146 mtime: 1684486432:233418146 dev: 16777229 ino: 64898707 uid: 501 gid: 20 size: 510 flags: 200000 ``` 那 refresh_index 是什麼意思,其實他是 git 用來判別 file 是否是有無蹤中的標記手段如下圖: ![](https://hackmd.io/_uploads/HyBzoaqIh.png) git 要確定檔案是否要放到追蹤內存中前,都會先做以上的 scan ,scan包含讀取、清除、等等文件當前的內容去做hash 標記,這時如果hash相同就會被放到追蹤的檔案中,如果不同就會放到未暫存的修改。 如以下的 src/components/pages/LoginPage.tsx ```javascript $ git status On branch feat/table_paginaction Your branch is up to date with 'origin/feat/table_paginaction'. Changes not staged for commit: (use "git add <file>..." to update what will be committed) (use "git restore <file>..." to discard changes in working directory) modified: src/components/pages/LoginPage.tsx no changes added to commit (use "git add" and/or "git commit -a") ``` 理論上 refresh_index 就是 每次 hash 的結果,而且會重複針對已追蹤的檔案無限次的操作 可以看到當file 大小到 400k 左右時 Full Scan 會執行非常久 | Worktree| Files | Full Scan | | -------- | -------- | -------- | | Chromium | 393K | 3072s | 備註: Chromium 是 git file的放置內存的地方 ### FSMonitor 對於 refresh_index 優化 ![](https://hackmd.io/_uploads/B1Y_Ca5In.png) 使用FSMonitor時Git所執行的操作: 1. 啟用git fsmonitor--daemon後台進程,以監聽文件系統變化事件。 2. 將FSMonitor索引擴展添加到index中,用於在git status命令之間跟踪FSMonitor和git狀態數據。 3. 執行git status命令時,使用FSMonitor index 初始化 index 條目的標記狀態。 4. 從後台進程中獲取自上次命令以來已經更改的文件列表。 5. 基於從後台進程中獲取的文件列表,更新索引條目的標記狀態。 6. 使用refresh_index命令可快速地基於FSMonitor檢測到的更改更新Git索引。 啟用後 Full Scan 就會優化很多 | Worktree| Files | Full Scan | | -------- | -------- | -------- | | Chromium | 393K | 0.024s | ### 第二階段 untracked file 但 untracked file 他不像 tracked file 那樣都有自己的 index 所以相比 refresh_index 是根據 tracked file 做full scan,untracked file 則是整個工作區的掃描。 Git如何查找未跟踪文件的步驟: 1. 遍歷整個工作樹,列舉每個目錄和文件。 2. 構建工作樹中每個文件和目錄的路徑名的完整列表。 3. 在索引中進行二進制搜索,查找相應的索引條目,並將已經被跟踪的文件從路徑名列表中省略。 4. 對剩餘的路徑名應用.gitignore模式匹配規則,將被忽略的文件從列表中省略。 5. 最終的結果列表就是一組未被跟踪的文件。 ### FSMonitor 如何幫助untracked file cache 1. 使用FSMonitor:啟用FSMonitor可以通過只檢查最近修改過的文件來提高Git查找未跟踪文件的效率,從而減少不必要的搜索和過濾。 2. 使用未跟踪緩存:未跟踪緩存可以在搜索未跟踪文件時提供大約2倍的速度。 3. 排除臨時文件:將臨時文件(如編譯器中間文件)排除在工作樹之外,可以避免在未跟踪搜索過程中對它們進行匹配和過濾,從而提高Git的性能。 4. 優化.gitignore文件:適當配置.gitignore文件可以減少需要過濾的文件數量,從而提高Git的性能。 需要注意的是,這些方法的適用情況各不相同,取決於工作樹的大小和結構,以及要處理的文件類型等因素。因此,需要根據具體情況選擇適當的優化方法。 | Worktree| Files | Untracked without Untracked-Cache | Untracked with Untracked-Cache in 使用FSMonitor| | -------- | -------- | -------- |-------- | | Chromium | 393K | 5.1s |0.024s | ### 在git hook 中使用 (Watchman)看好辣個男人 Watchman 他是一個外部系統的文件監控工具。 1. 安裝Watchman https://facebook.github.io/watchman/docs/install.html 2. 告訴 Watchman 看你的工作樹: ```javascript $ watchman watch . { "version": "2022.01.31.00", "watch": "/Users/jeffhost/work/chromium", "watcher": "fsevents" } ``` 3. 安裝示例鉤子腳本來教 Git 如何與 Watchman 溝通: https://github.com/git/git/blob/master/templates/hooks--fsmonitor-watchman.sample ```javascript $ cp .git/hooks/fsmonitor-watchman.sample .git/hooks/query-watchman ``` 4. 告訴 Git 使用鉤子: ```javascript $ git config core.fsmonitor .git/hooks/query-watchman ``` 這樣 FSMonitor 就可以在remote 中使用了 **文件來源:** https://github.blog/2022-06-29-improve-git-monorepo-performance-with-a-file-system-monitor/ ### husky vs simple git hooks 每個 git 專案底下都有 .git/hooks 以下是吃預設的 hook script ![](https://hackmd.io/_uploads/HJdRNflPn.png) 相上方對於 Watchman 就是將 smonitor-watchman.sample (預設)的script給watchman 去使用,這是題外話 ``` $ cp .git/hooks/fsmonitor-watchman.sample .git/hooks/query-watchman ``` 但你可能會想simple git hooks 跟 husky 有什麼差別? simple git hooks 比較: 優點: * Zero dependency 沒有其他依賴像使用 * 簡易設置 (1 object in package.json) * 輕量級 | Package | Unpacked size | With dependencies | |-------------------|--------------|-------------------| | Husky v4 4.3.8 | 53.5 kB | ~1 MB | | Husky v6 6.0.0 | 6.86 kB | 6.86 kB | | pre-commit 1.2.2 | ~80 kB | ~850 kB | | simple-git-hooks 2.2.0 | 10.1 kB | 10.1 kB | 缺點: 因為 simple git hooks 會去覆蓋 .git/hooks 的資料夾,所以如果專案會使用到 .git/hooks 的預設內容時,你會需要手動添加上去,所以這是不建議使用 simple git hooks 原因。 但對於大部分使用狀況,用 simple git hooks 或是 husky 並沒有太大差別,主要還是看打包大小。 透過 simple-git-hooks 改寫 .git/hooks/ ```javascript # [Optional] These 2 steps can be skipped for non-husky users git config core.hooksPath .git/hooks/ rm -rf .git/hooks # Update ./git/hooks npx simple-git-hooks ``` 此時 hooks 資料夾就乾淨多了 ![](https://hackmd.io/_uploads/B1PO3fgw2.png) ## clone sub-directory in github ```typescript 1.git init 2.git config core.sparsecheckout true // Set to allow clone subdirectories. 3.echo 'XXXX*' >> .git/info/sparse-checkout // XXXX means The name of the folder you want to download, * means all files within the folder. 4.git remote add -f origin XXXX // XXXX means the URL of your repo. 5.git pull origin master // Clone the specific folder. ``` ## template https://gist.github.com/adeekshith/cd4c95a064977cdc6c50 ### step 1 `add $HOME/.gitmessage.txt ` ```typescript $HOME/.gitmessage.txt # <type>: (If applied, this commit will...) <subject> (Max 50 char) # |<---- Using a Maximum Of 50 Characters ---->| # Explain why this change is being made # |<---- Try To Limit Each Line to a Maximum Of 72 Characters ---->| # Provide links or keys to any relevant tickets, articles or other resources # Example: Github issue #23 # --- COMMIT END --- # Type can be # feat (new feature) # fix (bug fix) # refactor (refactoring production code) # style (formatting, missing semi colons, etc; no code change) # docs (changes to documentation) # test (adding or refactoring tests; no production code change) # chore (updating grunt tasks etc; no production code change) # -------------------- # Remember to # - Capitalize the subject line # - Use the imperative mood in the subject line # - Do not end the subject line with a period # - Separate subject from body with a blank line # - Use the body to explain what and why vs. how # - Can use multiple lines with "-" for bullet points in body # -------------------- # For updated template, visit: # https://gist.github.com/adeekshith/cd4c95a064977cdc6c50 # Licence CC ``` ### step2 `set confing` ```typescript > git config --global commit.template $HOME/.gitmessage.txt ``` ### step3 這樣每次打 ```typescript > git commit ``` ## --force-with-lease vs --force 如果其他協助的開發者同時 `push commit` 會直接刪除他人的提交 ```typescript > git push --force ``` 使用 `--force-with-lease` 當`git` 發現有其他人同時 `push commit` 將會自動 `abort` 這確保直接刪除他人的 `commit` ,效果跟用 `git push ` 一樣。 ```typescript > git push --force-with-lease ``` ```typescript danny$ git push --force-with-lease To https://github.com/walterlv/walterlv.github.io.git ! [rejected] master -> master (fetch first) error: failed to push some refs to 'https://github.com/walterlv/walterlv.github.io.git' ``` 如發生 `abort` 先要做 `git fetch` 最新分支。 ```typescript > git fetch ``` 這樣就可以成功 `push`了 ```typescript danny$ git push --force-with-lease Counting objects: 4, done. Delta compression using up to 8 threads. Compressing objects: 100% (4/4), done. Writing objects: 100% (4/4), 363 bytes | 363.00 KiB/s, done. Total 4 (delta 3), reused 0 (delta 0) remote: Resolving deltas: 100% (3/3), completed with 3 local objects. To https://github.com/walterlv/walterlv.github.io.git 219a6d5..dff94a5 master -> master ``` ## cherry-pick 假設我們有一個情境是,我們開發一個新功能起一個 `branch` 叫做 `feat/v2.0` ,總共包含 3 個 `commit` 。 ![螢幕截圖 2024-01-17 09.33.34](https://hackmd.io/_uploads/HkUD134tp.png) 然後也開了 `mr` ![螢幕截圖 2024-01-17 09.39.34](https://hackmd.io/_uploads/S1cyx3VFT.png) 這時 `pm` 來跟你說,我們這次不需要上 `feat:fff.txt` 的內容喔~ 那有沒有方式可以把 `commit` 記錄拿掉 `feat: fff.txt` 這個 `commit` 外,還保留 `feat/v2.0` 的 `mr` 紀錄呢? 答案是有的~ ### 流程會是: 開新 `branch` --> `cherry-pick` 到特定的 `commit-id` --> `checkout` 到 `replace` 的 `branch` --> `reset` 到 `cherry-pick` 的 `branch` --> `git push -f` #### 開新 `branch 這邊要先刪除 `locale` 的 `branch` ![螢幕截圖 2024-01-18 17.46.36](https://hackmd.io/_uploads/BydbSOItp.png) 重新再開 `feat/v2.0` 的 `branch` ```typescript > git checkout -b 'feat/v2.0' ``` ![螢幕截圖 2024-01-18 17.46.58](https://hackmd.io/_uploads/Bk2Mr_Ita.png) #### cherry-pick` 到特定的 `commit-id` ```typescript > git cherry 7887374c30aac8e03726c9ced3f07e86276c804f ``` ![螢幕截圖 2024-01-17 09.48.46](https://hackmd.io/_uploads/SkJmMh4Kp.png) #### `git push -f` ```typescript ❯ git push -f origin feat/v2.0 ``` ![螢幕截圖 2024-01-17 09.56.31](https://hackmd.io/_uploads/rkKkV3EYT.png) 最後結果 `feat/v2.0` 的 `mr commit` 紀錄就會更改成 `cherry-pick` 的 `commit` 了,紀錄就會乾淨許多~ ![螢幕截圖 2024-01-17 09.57.09](https://hackmd.io/_uploads/HyEZN2NKp.png) ## hot-fix ```typescript 有 hotfix branch 1. hotfix rebase v3.7.1 (.v3.7.1 有 bug) 2. hotfix mr v3.7.1 3. v3.7.1 mr dev 用處:用於記錄 v3.7.1 的 hotfix 沒 hotfix branch 1. v3.7.1 -> v3.7.1-hotfix 2. v3.7.1-hotfix -> mr v3.7.1 3. v3.7.1-hotfix -> mr dev 用處:用於記錄 所有 release 的 hotfix 相同: 兩次 mr ,都要到 dev 跟 v3.7.1 相異: hotfix 是在 v3.7.1 開 ,還是原本就有 hotfix ``` ## gitbucket https://gitbutler.com/ # git commands work ![S__224550917](https://hackmd.io/_uploads/rkWAM_Bn6.jpg) ## git 同步刪除不存在 remote 的 local branch git branch -vv | grep gone | awk '{print $1}' | xargs git branch -d 要清除本地分支中不再存在於遠端的分支,可以使用以下指令: 1. 首先,使用 `git fetch --prune` 來更新本地對遠端分支的追蹤資訊,並移除已在遠端被刪除的分支追蹤。 2. 接著,使用以下指令來找出並刪除本地分支中不再存在於遠端的分支: ```bash git branch -vv | grep gone | awk '{print $1}' | xargs git branch -d ``` 這個指令的作用是: - `git branch -vv`:列出所有本地分支及其追蹤的遠端分支資訊。 - `grep gone`:過濾出已經在遠端被刪除的分支(即輸出中包含 "gone" 的行)。 - `awk '{print $1}'`:從過濾後的輸出中取出分支名稱。 - `xargs git branch -d`:將分支名稱作為參數傳遞給 `git branch -d` 指令,刪除這些分支。 如果你希望強制刪除未合併的本地分支,可以將最後的 `git branch -d` 改為 `git branch -D`。 這個方法可以幫助你清理本地分支,使其與遠端分支保持同步,並移除不再需要的分支。請注意,這個解法可能無法在 Windows 上使用,因為它依賴於 Unix 風格的管道和命令行工具 ## version control package * major version : 0 * minior version : 21 * patch version : 1 ### "axios": "^0.21.1", **允許安裝版本以上兼容的 `minior version` ,例如可以安裝 `0.21.2` 或是 `0.22.0`** ### "axios": "*", **允許安裝任意版本的 `lib` 但會有 `lib` 間兼容性問題。** ### "axios": "~0.21.1", **允許安裝版本以上兼容的 `patch version` ,例如可以安裝 `0.21.2` 或是 `0.21.9`,但不會更新到 `0.22.0`** ### "axios": "^>0.21.1", **允許安裝版本以上且向後兼容且版本號大於指定版本的最新版本,例如你可以更新到 `1.0.0` 或是 `0.25.1`** ### "axios": "0.21.1", **指定特定版號** ### "axios": "1.2.0 || >=1.2.2 <1.3.0" **可以安裝 `1.2.0` 的版本,或是 `1.3.0` 跟 `1.2.2` 之間的版號** ### * "axios": "next" ### * "axios": "latest" **安裝最新的版本** ## 修改特定 commit 的 author ```typescript $ git commit --amend --author "Danny <danny.wu@funpodium.net>" ``` ## 命名 ## learn https://www.gitkraken.com # 提交修改檔案到特殊 commit https://blog.csdn.net/sky8336/article/details/108237952 ## clone 特定的 git repo [來源](https://github.com/orgs/community/discussions/102639) ```typescript git init git remote add -f origin <url> git branch -M main git config core.sparseCheckout true echo "some/dir/" >> .git/info/sparse-checkout git pull origin main ``` ## clone repo branch history to another repo 在原本的 `repo` 中加一個新 `repo` ```typescript > git remote add [new-repo-name] [new-rebp-link] ``` `push` `main` 這個分支到新的 `repo` ```typescript >git push [new-repo-name] main ``` 將所有的 `branch history` 推到新的 `repo` ```typescript > git push --all [new-repo-name] ``` ## git 對象有哪些 主要由 `commit` 、`tree`、`blob`、`tag` `cat-file` 是 `git` 的底層指令可以用來查看多個 `git` 對象的內容,如這邊提的 `commit` 、`tree`、`blob` 等等 如下是四者的關係圖: 簡單來說我們常見的目前就是 `tag` 跟 `commit` ,不熟悉的是其他底層的內容,可以知道 每個`commit` 對應不同的 `tree` 對象,`tree` 對象又保留 `blob` 的訊息,接著我們再好好解釋各個對象是什麼。 ```typescript ┌─────────────────────────────────────────────────────────────┐ │ Git 對象模型 │ ├─────────────────────────────────────────────────────────────┤ │ │ │ tag ──────► commit ──────► tree ──────► blob │ │ │ │ │ │ ▼ ▼ │ │ commit tree ──────► blob │ │ │ │ │ │ ▼ ▼ │ │ ... blob │ │ │ └─────────────────────────────────────────────────────────────┘ ``` ## Tree `Tree` 對象代表目錄結構,包含指向 `blob` 和其他 `tree` 的指針,並記錄文件名和權限等等,這邊我將用 `git` 的 `cat-file` 只指令來讀取所有的對象內容,簡單介紹一下這格指令 `-p` :Pretty-print `HASH` : 這邊可以是任何的 `commit hash` 、`tree hash`、`blob hash` ```typescript > git cat-file -p HASH ``` 接著假設我先讀取一個 `commit-hash` ```typescript >git cat-file -p 70802560e7799d3fd21b08b5445c62de47b67a67 ``` 你會看到這邊紀錄這這個 `commit` 的 `auth` 以及他的 `parant` 還有最主要的 `tree` 對象,來代表這個 `commit` 的內容。 ```typescript tree 5129ba8d56edb0196618d89447077196be2ee9cb parent e69767ea77cb34f2312ed8e03ec2dba6bea3b143 author Danny <hiunji64@gmail.com> 1767343897 +0800 committer Danny <hiunji64@gmail.com> 1767343897 +0800 docs: add CHANGELOG-INIT (BEA-85555/PSS-7323) ``` ## Blob Blob 是 Git 最基本的對象,用於存儲文件內容。它只包含文件的原始數據,不包含文件名、權限或任何元數據。 這邊我們用一樣的指令來讀取 `tree` 對象的內容 ```typescript > git cat-file -p 5129ba8d56edb0196618d89447077196be2ee9cb ``` 你會看到這個 `tree` 對象有多個 `blob` 對象,也就是對應的檔案,可以知道一個檔案只會對應到一個 `blob` 對象 ```typescript 100644 blob efa8ffc942e955a973c81f409d855f5730ef12f8 .cursorignore 100644 blob 8948ad938ea2b90b7e4a447cae2df99f8e509baf .dockerignore 100644 blob d95c1ab4dfc1270535475cbf87ffaea9519494fd .env ``` 之後只要再讀取 `blob` 對象後出來的內容就會是檔案的文字內容了 ```typescript >git cat-file -p d95c1ab4dfc1270535475cbf87ffaea9519494fd ``` ```typescript # Fixed variables PORT=3000 NEXT_TELEMETRY_DISABLED=1 # Auth Providers Related AD_NAME=${AD_NAME} AD_CLIENT_ID=${AD_CLIENT_ID} AD_TENANT_ID=${AD_TENANT_ID} //.... ``` ### 總結: 相同檔案只會有一個 `blob` 對象,如果多個 `commit` 修改相同的 `file` 那麼各自的 `tree` 都會對應到相同的 `blob` 對象,直到有新的檔案出現才會創建新的 `blob` 對象,另外要提到 `blob` 對象只要一產生就不會消失,儘管檔案被移除,原本的 `commit` 對應到 `blob` 對象也會固定住,除非把整個 `commit` `reset` 或是用 `git` 的 `gc` 來移除,以上也就是 `git` 優化存儲的大小,不會因為多個 `commit` 到相同的檔案就複製新的 `blob` 對象。 ![截圖 2026-01-04 晚上8.51.53](https://hackmd.io/_uploads/Sk9iGJuE-g.png)