iT邦幫忙

2025 iThome 鐵人賽

DAY 20
0
Software Development

深入一點點認識 Git系列 第 20

Day 20-深入一點點認識 Git:不是把檔案都塞進 .gitignore 就可確保其不被追蹤

  • 分享至 

  • xImage
  •  

在 Day 19 文章尾聲,我們提到不論是 .git/info/exclude 或 .gitignore,都是對「尚未被 git 追蹤的檔案」才有效。在此篇文章,我們將透過實驗,觀察檔案在被 git 追蹤前後分別被放進 .gitignore 時,後續有什麼影響,以及如果不小心讓 git 追蹤到不希望被其追蹤的檔案時,可以如何補救。

實驗一:一開始就檔案放在 .gitignore 裡面

現在我們依序在一個經 git init 的空資料夾,建立兩個檔案。

第一個是 normal.txt

echo "Normal" > normal.txt

第二個是 confidential.txt

echo "Password" > confidential.txt

透過 2. 跟 3.,我們建立了兩個檔案,其中 normal.txt 是希望被 git 追蹤的一般內容、confidential.txt 則不希望被 git 追蹤。

在進行 git addgit commit 前,就先建立好 .gitignore 檔案,並且把不希望被 git 追蹤的 confidential.txt 放進去:

echo "confidential.txt" > .gitignore

首先輸入 git add .,最後面的點表示「把所有的檔案都加到預存區」,這兩個檔案分別會發生什麼變化?
https://ithelp.ithome.com.tw/upload/images/20250919/20178513oCwGIGLEGr.png

git statusgit ls-files --stage 指令都發現:normal.txt 與 .gitginore 兩個檔案都被放到預存區了,那此時 ./git 資料夾長怎樣呢?

.git/
├── ...
│
├── objects/
│   ├── 70/              # .gitignore的blob物件
│   │   └── 00841e8cbb4d8f6fedd815e85154dbc49d4eba
│   ├── b0/              # normal.txt的blob物件
│   │   └── 9e2da4ae42d046da39148c24a21a256fb984a9
│   ├── info/
│   └── pack/
│
├── ...
└── index  # 內容有100644 7000841e8cbb4d8f6fedd815e85154dbc49d4eba 0       .gitignore
           # 以及100644 b09e2da4ae42d046da39148c24a21a256fb984a9 0       normal.txt

這時再下 git commit

git commit -m "Initial commit"

此時 .git/ 資料夾長這樣:

.git/
├── ...
│
├── objects/
│   ├── 70/
│   │   ├── 00841e8cbb4d8f6fedd815e85154dbc49d4eba # confidential.txt的blob物件
│   │   └── dd1d950dd7d9054b670bae6dc6d9b7e9c7b6a4 # 指向兩個blob物件的tree物件
│   ├── b0/                                        # normal.txt的blob物件
│   │   └── 9e2da4ae42d046da39148c24a21a256fb984a9
│   ├── b4/                                        # commit物件
│   │   └── 515db833d05bf2d072357964e95be04f26a45c
│   ├── info/
│   └── pack/
│
├── refs/
│   ├── heads/
│   │   └── main  # 指向b4515db833d05bf2d072357964e95be04f26a45c
│   └── tags/
│
├── ...
└── index # 內容有100644 7000841e8cbb4d8f6fedd815e85154dbc49d4eba 0       .gitignore
          # 以及100644 b09e2da4ae42d046da39148c24a21a256fb984a9 0       normal.txt

圖示如下:
https://ithelp.ithome.com.tw/upload/images/20250919/20178513FTVgqA2CZG.png

實驗一小結

由上述實驗可知,如果 confidential.txt 檔案在一開始就放在 .gitignore 裡面,則自始至終都不會被 git 追蹤,git add 無法將其放到暫存區,後面自然也就沒辦法形成 commit 的一部分。

實驗二:git add 完才把檔案放到 .gitignore 裡

在一個新的空資料夾中,再次依序輸入跟剛剛一樣的指令,但沒有建立 .gitignore 檔案:

git init
echo "Normal" > normal.txt
echo "Password" > confidential.txt
git add .

透過 git add . 把剛剛建立的 normal.txtconfidential.txt 都放到預存區,資料夾結構如下:

.git/
├── ...
│
├── objects/
│   ├── 67/      # confidential.txt的blob物件
│   │   └── 7ffbcc784c01723f5d8a0b27d98770c38b2e32
│   ├── b0/      # normal.txt的blob物件
│   │   └── 9e2da4ae42d046da39148c24a21a256fb984a9
│   ├── info/
│   └── pack/
│
├── ...
└── index # 內容有100644 677ffbcc784c01723f5d8a0b27d98770c38b2e32 0       confidential.txt
          # 以及100644 b09e2da4ae42d046da39148c24a21a256fb984a9 0       normal.txt

如果這時才突然想到:confidential.txt 不應該被 git 追蹤啊!趕快把這個檔案加到 .gitignore

echo "confidential.txt" > .gitignore

問題來了,我們需要重新 git add . 嗎?

  • 沒有重新 git add,直接 git commit
    這時候如果再下 git commit 有沒有救呢?
    https://ithelp.ithome.com.tw/upload/images/20250919/20178513oUomfOqBDq.png

看起來沒有,這個 confidential.txt 還是變成 commit 的一部分了。檢視資料夾結構變成這樣:

.git/
├── ...
│
├── objects/
│   ├── 6d/  # 指向tree的commit物件
│   │   └── 335def541e14e56d9fffee01ac7053aa37c6e6
│   ├── 67/  # confidential.txt的blob物件
│   │   └── 7ffbcc784c01723f5d8a0b27d98770c38b2e32
│   ├── b0/  # normal.txt的blob物件
│   │   └── 9e2da4ae42d046da39148c24a21a256fb984a9
│   ├── bb/  # 代表兩個blob的tree物件
│   │   └── 59dee27eb8705b3df271d49c38506e5b784eab
│   ├── info/
│   └── pack/
│
├── refs/
│   ├── heads/
│   │   └── main # 指向6d335def541e14e56d9fffee01ac7053aa37c6e6
│   └── tags/
│
├── ...
└── index # 保持100644 677ffbcc784c01723f5d8a0b27d98770c38b2e32 0       confidential.txt
          # 以及100644 b09e2da4ae42d046da39148c24a21a256fb984a9 0       normal.txt

圖示如下,看來當已經透過 git add 把檔案放到預存區,再匆匆忙忙把不想被追蹤的檔案塞進 .gitignore,是完全沒有幫助的,反而是 .gitignore 根本沒有被 git 追蹤:
https://ithelp.ithome.com.tw/upload/images/20250919/20178513RrOVWL5fjT.png

  • 重新 git add 後再 git commit
    我們一樣在一個新資料夾中,輸入以下指令:
git init
echo "Normal" > normal.txt
echo "Password" > confidential.txt
git add .

這時想到要把 confidential.txt 放到 .gitignore 中:

echo "confidential.txt" > .gitignore

截至目前為止都跟剛剛一樣,但這次我們學乖了,重新 git add . 看預存區如何變化:
https://ithelp.ithome.com.tw/upload/images/20250919/20178513lCZXhH37D5.png

單用 git status 檢視會發現 confidential.txt 依然在預存區裡面,那 ./git 資料夾結構長怎樣呢?

.git/
├── ...
│
├── objects/
│   ├── 67/   # confidential.txt的blob物件
│   │   └── 7ffbcc784c01723f5d8a0b27d98770c38b2e32
│   ├── 70/   # .gitignore的blob物件
│   │   └── 00841e8cbb4d8f6fedd815e85154dbc49d4eba
│   ├── b0/   # normal.txt的blob物件
│   │   └── 9e2da4ae42d046da39148c24a21a256fb984a9
│   ├── info/
│   └── pack/
│
├── ...
└── index  # 內容有100644 7000841e8cbb4d8f6fedd815e85154dbc49d4eba 0       .gitignore
           # 以及100644 677ffbcc784c01723f5d8a0b27d98770c38b2e32 0       confidential.txt
           # 還有100644 b09e2da4ae42d046da39148c24a21a256fb984a9 0       normal.txt

果然,三個檔案都還在預存區裡,我們不希望 git 追蹤的 confidential.txt 檔案依舊存在,這時如果再下 git commit 會發生什麼事?
https://ithelp.ithome.com.tw/upload/images/20250919/201785135joA3amAEv.png

看來還是有成為 commit 的一部分,現在資料夾結構如下:

.git/
├── ...
│
├── objects/
│   ├── 36/   # 指向tree的commit物件
│   │   └── 8dee0457cb78f829456c53089c89ceb65c4263
│   ├── 67/   # confidential.txt的blob物件
│   │   └── 7ffbcc784c01723f5d8a0b27d98770c38b2e32
│   ├── 70/   # .gitignore的blob物件
│   │   └── 00841e8cbb4d8f6fedd815e85154dbc49d4eba
│   ├── b0/   # normal.txt的blob物件
│   │   └── 9e2da4ae42d046da39148c24a21a256fb984a9
│   ├── db/   # 指向三個blob物件的tree物件
│   │   └── b791466a4bc004188c835678281b5d469b9999
│   ├── info/
│   └── pack/
│
├── refs/
│   ├── heads/
│   │   └── main  # 指向368dee0457cb78f829456c53089c89ceb65c4263
│   └── tags/
│
├── ...
└── index # 內容有100644 7000841e8cbb4d8f6fedd815e85154dbc49d4eba 0       .gitignore
          # 以及100644 677ffbcc784c01723f5d8a0b27d98770c38b2e32 0       confidential.txt
          # 還有100644 b09e2da4ae42d046da39148c24a21a256fb984a9 0       normal.txt

圖示如下:
https://ithelp.ithome.com.tw/upload/images/20250919/20178513xzEASQJoRN.png

這次 .gitignore 雖然有被追蹤,但 confidential.txt 依舊成為了 commit 的一部分。

實驗二小結

當不想被 git 追蹤的檔案已經進到預存區,才把該檔案加進 .gitignore,此時不論有沒有重做 git add .,預存區的檔案都還是會被 git 追蹤,再下 git commit 後,在 .gitignore 裡的檔案是會成為 commit 的一部分。

因此,正確的做法是把該檔案先移出預存區,步驟如下:

  1. git reset <filename> 把不想被追蹤的檔案退出預存區(若要確保該檔案之後都不被 git 追蹤,則使用 git rm --cached <filename>)。
  2. 重新 git add . 把 .gitignore 檔案本身及其他不在 .gitignore 裡面的檔案都重新加入預存區。
  3. git commit 把 2. 加入預存區的檔案都形成一個 commit。

圖示如下:
https://ithelp.ithome.com.tw/upload/images/20250919/20178513F1fxx5znk5.png

此時資料夾結構如下:

.git/
├── ...
│
├── objects/
│   ├── 67/   # confidential.txt的blob物件還在
│   │   └── 7ffbcc784c01723f5d8a0b27d98770c38b2e32
│   ├── 70/
│   │   ├── 00841e8cbb4d8f6fedd815e85154dbc49d4eba # gitignore檔案的blob物件
│   │   └── dd1d950dd7d9054b670bae6dc6d9b7e9c7b6a4 # 指向7000841...與b09e2da...兩個blob的tree物件
│   ├── b0/   # normal.txt的blob物件
│   │   └── 9e2da4ae42d046da39148c24a21a256fb984a9
│   ├── c8/   # 指向tree物件的commit物件
│   │   └── b77ee6bf52bd316025e90bf6f786c3eb11fff5
│   └── pack/
│
├── refs/
│   ├── heads/
│   └── tags/
│
├── ...
└── index # 包含100644 7000841e8cbb4d8f6fedd815e85154dbc49d4eba 0       .gitignore
          # 以及100644 b09e2da4ae42d046da39148c24a21a256fb984a9 0       normal.txt

圖示如下,留意 confidential.txt 的 blob 物件其實仍在,只是已經變成懸置物件(dangling object),其他物件不會指向它,過一段時間會被 git 清除:
https://ithelp.ithome.com.tw/upload/images/20250919/20178513zjer42loVe.png

那如果不希望被 git 追蹤的檔案已經變成 commit 的一部分呢?那就是多一步 git reset 退回被追蹤前的 commit,並在確保檔案被移出預存區、且放進 .gitignore 之後,再重新 git addgit commit

小結

.gitignore(連同 Day 19 提到的 .git/info/exclude)都只對「尚未被 git 追蹤的檔案」有效,如果已經被 git 追蹤才放進 .gitignore,那這份檔案還是會持續被追蹤。

因此,如果不小心讓希望放進 .gitignore(或 .git/info/exclude)的檔案被 git 追蹤,則要先透過 git resetgit rm --cached 指令,將其退出預存區後,再重新下 git add 讓 git 追蹤剩下的檔案;被退出預存區檔案原本做出的 blob 物件會成為懸置物件。

參考資料

  1. gitignore
  2. What is the difference between git rm --cached and git reset file?
  3. git rm –cached vs git reset HEAD? When should each be used?
  4. 【狀況題】有些檔案我不想放在 Git 裡面…

上一篇
Day 19-深入一點點認識 Git:兩種不讓檔案被 Git 追蹤的方式:.gitignore 與 .git/info/exclude
下一篇
Day 21-深入一點點認識 Git:用 git 指令把檔案刪掉
系列文
深入一點點認識 Git22
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言