# 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`

輸入 `git reset` 後會自動把暫存區的檔案移除

### 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://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://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 它會查找已跟踪、未跟踪和忽略的文件

* 已跟踪 :已經被 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 是否是有無蹤中的標記手段如下圖:

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 優化

使用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

相上方對於 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 資料夾就乾淨多了

## 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` 。

然後也開了 `mr`

這時 `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`

重新再開 `feat/v2.0` 的 `branch`
```typescript
> git checkout -b 'feat/v2.0'
```

#### cherry-pick` 到特定的 `commit-id`
```typescript
> git cherry 7887374c30aac8e03726c9ced3f07e86276c804f
```

#### `git push -f`
```typescript
❯ git push -f origin feat/v2.0
```

最後結果 `feat/v2.0` 的 `mr commit` 紀錄就會更改成 `cherry-pick` 的 `commit` 了,紀錄就會乾淨許多~

## 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

## 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` 對象。
