在 Day 19 文章尾聲,我們提到不論是 .git/info/exclude
或 .gitignore
,都是對「尚未被 git 追蹤的檔案」才有效。在此篇文章,我們將透過實驗,觀察檔案在被 git 追蹤前後分別被放進 .gitignore
時,後續有什麼影響,以及如果不小心讓 git 追蹤到不希望被其追蹤的檔案時,可以如何補救。
現在我們依序在一個經 git init
的空資料夾,建立兩個檔案。
第一個是 normal.txt
:
echo "Normal" > normal.txt
第二個是 confidential.txt
:
echo "Password" > confidential.txt
透過 2. 跟 3.,我們建立了兩個檔案,其中 normal.txt
是希望被 git 追蹤的一般內容、confidential.txt
則不希望被 git 追蹤。
在進行 git add
與 git commit
前,就先建立好 .gitignore
檔案,並且把不希望被 git 追蹤的 confidential.txt
放進去:
echo "confidential.txt" > .gitignore
首先輸入 git add .
,最後面的點表示「把所有的檔案都加到預存區」,這兩個檔案分別會發生什麼變化?
由 git status
與 git 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
圖示如下:
由上述實驗可知,如果 confidential.txt
檔案在一開始就放在 .gitignore
裡面,則自始至終都不會被 git 追蹤,git add
無法將其放到暫存區,後面自然也就沒辦法形成 commit 的一部分。
在一個新的空資料夾中,再次依序輸入跟剛剛一樣的指令,但沒有建立 .gitignore
檔案:
git init
echo "Normal" > normal.txt
echo "Password" > confidential.txt
git add .
透過 git add .
把剛剛建立的 normal.txt
跟 confidential.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 commit
有沒有救呢?看起來沒有,這個 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 追蹤:
git init
echo "Normal" > normal.txt
echo "Password" > confidential.txt
git add .
這時想到要把 confidential.txt
放到 .gitignore
中:
echo "confidential.txt" > .gitignore
截至目前為止都跟剛剛一樣,但這次我們學乖了,重新 git add . 看預存區如何變化:
單用 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
會發生什麼事?
看來還是有成為 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
圖示如下:
這次 .gitignore
雖然有被追蹤,但 confidential.txt
依舊成為了 commit 的一部分。
當不想被 git 追蹤的檔案已經進到預存區,才把該檔案加進 .gitignore,此時不論有沒有重做 git add .,預存區的檔案都還是會被 git 追蹤,再下 git commit 後,在 .gitignore 裡的檔案是會成為 commit 的一部分。
因此,正確的做法是把該檔案先移出預存區,步驟如下:
git reset <filename>
把不想被追蹤的檔案退出預存區(若要確保該檔案之後都不被 git 追蹤,則使用 git rm --cached <filename>
)。git add .
把 .gitignore
檔案本身及其他不在 .gitignore
裡面的檔案都重新加入預存區。git commit
把 2. 加入預存區的檔案都形成一個 commit。圖示如下:
此時資料夾結構如下:
.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 清除:
那如果不希望被 git 追蹤的檔案已經變成 commit 的一部分呢?那就是多一步 git reset
退回被追蹤前的 commit,並在確保檔案被移出預存區、且放進 .gitignore
之後,再重新 git add
跟 git commit
。
.gitignore
(連同 Day 19 提到的 .git/info/exclude
)都只對「尚未被 git 追蹤的檔案」有效,如果已經被 git 追蹤才放進 .gitignore
,那這份檔案還是會持續被追蹤。
因此,如果不小心讓希望放進 .gitignore
(或 .git/info/exclude
)的檔案被 git 追蹤,則要先透過 git reset
或 git rm --cached
指令,將其退出預存區後,再重新下 git add
讓 git 追蹤剩下的檔案;被退出預存區檔案原本做出的 blob 物件會成為懸置物件。