# 前言
今天的主題其實在之前開始介紹如何使用 Git 時就可以先說明,不過還是希望能先把基本觀念一次說明到位,讓讀者先對 Git 有一定的認識再說,畢竟設定類的說明,隨時補都來得及。
回到主題,實務上的專案中,總是會有一些「設定檔」、「編譯檔」、「日誌 (log)」,之類不影響專案本體的內容,他們並不需要 (有些甚至不可以) 被記錄到儲存庫中,該如何定義那些「不想被版控」的資料,會是個重要的問題。
為此 Git 提供了一個方式讓我們去定義這些內容:設定 .gitignore
檔案。
# 設定 .gitignore 檔案
首先來說文解字一下, ignore 中文是「忽略」的意思,所以 .gitignore
是用來定義讓 Git 忽略的資料的檔案。
想定義能讓 Git 忽略的資料也很簡單:
- 直接在工作目錄中新增一個 「檔案」,並且把檔名改成
.gitignore
。 (切記,是 檔案 ,不是 資料夾 ) - 把想忽略的「檔名 (含副檔名)」一個一個寫在裡面就好了。
舉例來說,目前工作目錄中的檔案都是剛新增的檔案,是 Git 「尚未追蹤」 的狀態:
如果我想忽略 data.log
以及 app.log
,就直接按照上述步驟操作 (多個檔案「換行」寫入即可):
在 VSCode 中,工作目錄如果看到檔案呈現灰色,則代表 Git 正在忽略這個檔案。
只要把 .gitignore
字樣也寫進 .gitignore
裡面,這個檔案也會被忽略:
不知道讀者有沒有從截圖中注意到,此時的 .gitignore
並沒有被加入版控 (也就是尚未被 commit)?
其實只要工作目錄裡面有 .gitignore
,Git 就會自動去讀取它的內容,並且自動忽略符合內容的檔案。 即使 .gitignore
檔案沒有被版控也會有效果!
不過要設定成功必須達成兩個條件:
.gitignore
的內容必須符合撰寫規則 (後續會詳細說明)。- 想忽略的檔案 不能 被紀錄在「索引」內。
第二點尤其重要,如果檔案已經被加入索引 (就是暫存區),甚至已經被 commit 到儲存庫中 (註 1),那麼無論怎麼在 .gitignore
裡面寫資料,Git 都沒辦法排除這個檔案。
就像你已經認識了一個人,結果有人告訴你:「別認識他,忘掉他吧!」
常理來說是很難做到的吧…
註 1. 觀念重申:已經被 commit 的檔案,在索引 (暫存區) 中也會有資料。
# .gitignore 格式
有個重要的事情先講:
.gitignore
中可以寫註解,不過如果要寫註解,「不可以」寫在跟檔名同一行,否則 Git 會把你想寫的註解視為檔名,導致忽略失效。
為了讓大家可以比較好瀏覽,「下方註解故意寫在『無效的位置』」 ,請各位千萬注意!!!
- 完整檔案名稱:
.env # 環境設定檔 | |
.config # 配置檔案 | |
.DS_Store # macOS Finder 產生的檔案 | |
app.log # 名叫 app 的 log 檔案 |
- 直接定義要忽略的副檔名:
*.log # 忽略所有以 .log 結尾的檔案 | |
*.tmp # 忽略所有以 .tmp 結尾的檔案 |
- 以驚嘆號 (!) 來例外某些檔案:
*.log # 忽略所有以 .log 結尾的檔案 | |
!important.log # 但不忽略 important.log |
- 忽略任何叫某個名字的檔案 (不管副檔名)
hello.* # 忽略任何叫 hello 的檔案,包含 hello.txt、hello.java ... |
- 忽略所有以某個字樣開頭的資料
image* # 忽略所有以 image 開頭命名的資料 (包含檔案與目錄) |
- 只忽略「根目錄」的目錄或檔案:
/Test # 只忽略根目錄 Test ,不忽略子目錄的 Test,例如 Vue/Test 就不會被忽略 |
- 忽略目錄底下的 「所有內容」:
.vscode/ # Visual Studio Code 設定檔 | |
.idea/ # IntelliJ IDEA 設定檔 | |
node_modules/ # Node.js 的依賴模組 | |
build/ # 編譯或建置的結果目錄 | |
dist/ # 發布版程式碼的目錄 |
- 註解 (因為太重要,所以特別寫下來)
# 這是一個有效註解 | |
.vscode/ # 寫在這裡是無效註解,所以上述範例註解會直接使設定無效!! |
以上大概是 .gitignore
常見的寫法,如果還有特殊需求,請各位讀者參考 Git 官網 對於設定的說明。
# 設定全域 .gitignore
如果覺得在不同的工作目錄設定固定的「忽略檔案」很麻煩,可以直接設定「全域」的 .gitignore
。
執行步驟如下:
- 在終端機執行
git config --global core.excludesfile "~/.gitignore_global" |
或是直接在 .gitconfig
檔 [core]
底下加這行,跟執行指令意思一樣 (註 1)。
[core] | |
excludesfile = ~/.gitignore_global |
-
根據不同系統,新增
.gitignore_global
檔案 (註 2):
Mac 請到這個路徑設定:~/.gitignore_global
Windows 在這個路徑:C:\Users\你的使用者名字\.gitignore_global
-
在
.gitignore_global
設定的內容,即為「全域.gitignore
」。
註 1. 如果不知道
.gitconfig
檔案如何操作 ,可以參考 Git 初始化設定文章 常見問題 「第四題」。註 2. 上述的「檔名」與「路徑」都可以自己定義,只要第 1、2 步有辦法對應即可,上面路徑設定只是為了把 全域
.gitignore
跟.gitconfig
放在一樣的路徑。
# 如何忽略已經被版控的檔案?
開頭有提到,已經被版控的檔案會在 索引 (暫存區) 存有資料,所以會導致 .gitignore
無法忽略。
要忽略已被版控的檔案,有三件事情要做 (註 1):
- 在
.gitignore
內設定要忽略的檔名。 - 移除暫存區中的檔案紀錄。
- 執行
git commit
提交一個版本。
可能會有讀者疑惑第三步的動作,明明沒有編輯檔案,是要 commit 什麼東西啊,而且難道不用先 git add
嗎?!
來說明一個小觀念,讓大家細細品嘗 (?):
- 把已經 commit 的檔案移出暫存區,是一個 行為。
- 資料被移除了,是一個 狀態。
在 Git 的世界中,只要有 操作工作目錄或是索引 的「行為」,都會導致「資料狀態」被改變。
資料原本 commit 好的 穩定 狀態如果被改變了,Git 就會認為應該要把這個「被改變的狀態」給記錄起來。
資料:兄弟阿~挖應該嘎你喜無冤無仇啦齁,林北 commit 嘎 喝ㄙㄟˋ喝ㄙㄟˇ ,你無逮無治嘎挖亂叮噹…
我們在執行 git add
時,會把「新增或編輯的檔案狀態」加入索引。
這裡第二步的行為,則是把「移除索引的檔案狀態」加到索引。 (<= 這句話應該最難懂)
兩者在 Git 看來,都是有東西 被加到暫存區 的行為,所以下一步,就可以直接執行 commit 了!
詳細指令操作如下:
-
修改
.gitignore
,並視需求決定是否跟著「第三步」一起 commit。 -
執行下列指令 (註 2),把檔案移出索引。
git rm --cached 檔名 |
- 直接 commit 提交一個版本 (註 3)。
git commit -m "將 XXX 移出「暫存區」" |
如果都順利完成,此時的檔案應該已經能被 Git 忽略了!
註 1. 上述內容也可以在 官網 找到說明。
註 2.
rm (remove)
用來告訴 Git 要刪掉檔案,--cached
則是告訴 Git 不是要從「工作目錄」中刪除,只要刪除「索引」的資料就好。註 3. 再說明一次:透過
git
的指令將資料「移出索引」的行為,資料被移出 的狀態會被加到「暫存區」,所以可以直接 Commit。
# 可能不太重要的觀念補充:把檔案移除索引後的狀態…
我們已經知道 git rm --cached 檔名
是把檔案「移出索引」的指令。
不過執行完指令之後的檔案,有沒有寫進 .gitignore
, git status
出現的資料狀態會不一樣哦!
舉例來說,假設我忘記把 data.log
放到 .gitignore
就 commit 了,現在想把它移除:
於是我執行了這個指令:
git rm --cached data.log |
-
「沒有」把
data.log
寫進.gitignore
時,執行git status
會出現這樣的資料:
data.log
在 「可被提交」 區域呈現 「已刪除」 狀態 (綠字那筆),
同時也會出現在 「未追蹤檔案」 區域 (紅字那筆)。 -
把
data.log
寫進.gitignore
,再執行一次git status
:
神奇的事情出現了,data.log
竟然從 「未追蹤檔案」 區域消失了!!
給大家 10 秒鐘思考是什麼原因。好時間到!
記得「未追蹤」檔案的定義嗎?
只要不在索引 (暫存區) 內的檔案,就是「未追蹤」檔案。
由於 git rm --cached
就是「把檔案從索引移除」的指令,執行完指令後,檔案當然就會是未追蹤狀態。
要是未追蹤的檔案再度被寫到 .gitignore
中,就會讓 Git 會自動「忽略」這個檔案,就好像 Git 「看不到」這個檔案一般,既然對 Git 來說檔案根本就不存在,那也不用探討追蹤與否的問題了。
不過此時的 Git 確實知道有一個叫 data.log
被「移出索引」,而且移出索引的「狀態」還沒有被「紀錄」,所以 git status
會顯示有個已經被刪除的檔案,而且它還沒被 commit 。
我們因此能得知,雖然 .gitignore
不是索引,不過它也會影響到 Git 讀取專案檔案的狀態。
.git/index
索引 (a.k.a. 暫存區) 用來紀錄要讓 Git 「追蹤」的資料;
.gitignore
用來紀錄要讓 Git 「忽略」的資料。
# 常見問題
# 1. git rm --cached
移出檔案,會影響之前 commit 的檔案嗎?
不會。
用另一個角度來看這個指令,執行完指令之後的檔案,就像剛跟 Git 談完分手一樣,兩者變成了最熟悉的陌生人。
他們之間經歷的回憶,就像已經 commit 的內容一樣,Git 不會輕易忘記。
這樣的模式也很合理,commit 行為在 Git 的世界應該算是最神聖的事情,這會把關鍵的時間點封裝成重要的記憶。
如果有任何一個指令,可以一口氣把歷史中 commit 過的某個檔案一次清除,可能也就失去了「版控」的意義。
所以 git rm --cached
依然是一個讓時間「往前走」的指令,他只是某個檔案跟 Git 分手的時刻,但過去被記錄的點點滴滴,不會輕易的被 Git 所遺棄 。
這樣說起來,執行完指令後那個 commit 的行為,就是要發文紀錄一下自己分手了嗎?!
奇怪… 怎麼寫完這段話… 眼角微微泛淚…
是洋蔥,我加了洋蔥
# 2. .gitignore 需要被加入版控嗎?
又是那個老答案:「看需求」。
如果你使用 Git 都是一個人在版控,而且每個工作目錄需要忽略的檔案大同小異,那其實只要建立好 「全域 .gitignore
」 就可以,不一定要在每個專案都定義 .gitignore
,就也不會有要不要把 .gitignore
加入版控的問題了。
不過如果是跟團隊合作某個專案,為了讓整個團隊能擁有一致的「忽略清單」,這種情況就會建議把 .gitignore
加到版控中。
這樣的好處是,如果團隊的專案因為某個需求需要增加「要被忽略的檔案」,只要有人在專案的 .gitignore
檔案定義好,所有人同步之後就搞定了,省得每個人還要額外設定。