“深入解析 Git 隱藏檔案:.gitkeep、.gitignore、.gitattributes 的真正用途與最佳實踐”

🌏 Read the English version


Table of Contents

開場:你遇過這些問題嗎?

你是否曾經:

  • 空目錄無法提交,不知道為什麼 logs/ 資料夾在 Git 中消失了?
  • CRLF vs LF 衝突,每次 pull 都看到一堆「看起來沒改但 diff 顯示整個檔案都變了」?
  • .gitignore 無效,明明加了規則但檔案還是被追蹤?
  • CI/CD 失敗,在本地跑得好好的,上 GitHub Actions 就出錯?
  • 不知道該不該提交 .github/.gitkeep.gitattributes

這些問題的根源都來自於「不理解 Git 檔案系統的邊界」。

Key Insight: Git 隱藏檔案分為「官方支援」與「社群約定」兩類,混淆兩者是跨團隊協作問題的主要來源。

在幾乎所有的開發專案中,我們都能看到許多以 .git 開頭的檔案或資料夾。根據 Git 官方文檔,有些是 Git 官方的一部分(如 .gitignore.gitattributes),有些是平台擴充(如 .github/.gitlab-ci.yml),也有一些是社群慣例(如 .gitkeep)。

本篇文章將從實戰角度系統性整理 官方 vs 非官方 Git 檔案,並提供多人協作、DevOps 整合、跨平台開發的最佳實踐。


一、Git 官方檔案總覽(Git 原生支援)

這些檔案或目錄是 Git 原生設計的一部分,其語法、用途與效力均受 Git 官方支援。

檔案名稱 官方屬性 用途 常見範例
.git/ ✅ 核心 儲存整個版本控制資料庫(物件、索引、分支、設定) .git/config, .git/refs/, .git/objects/
.gitignore ✅ 官方 定義哪些檔案或目錄不應被 Git 追蹤 node_modules/, .env, *.log
.gitattributes ✅ 官方 設定文字檔正規化、合併策略、LFS 管理 *.sh text eol=lf, *.zip binary
.gitmodules ✅ 官方 記錄子模組(Submodule)的來源與路徑 [submodule "lib"]
.gitconfig ✅ 官方 儲存使用者層級或系統層級設定 [user] name=...
.mailmap ✅ 官方 統一作者名稱與 email(歷史貢獻統計) 合併不同 email 為同一作者
.git-blame-ignore-revs ✅ 半官方 指定 git blame 忽略的 commit 忽略格式化 commit

二、Git 檔案層級架構全景圖

以下 Mermaid 圖表展示了 Git 檔案系統的完整分層結構:

graph TB
    subgraph "Git 生態系統"
        A[Git 核心層]
        B[版本控制層]
        C[平台擴充層]
        D[社群慣例層]
        E[開發者私有層]
    end

    A --> A1[.git/ 目錄]
    A --> A2[.gitconfig]

    B --> B1[.gitignore]
    B --> B2[.gitattributes]
    B --> B3[.gitmodules]
    B --> B4[.mailmap]

    C --> C1[.github/]
    C --> C2[.gitlab-ci.yml]
    C --> C3[.gitreview]

    D --> D1[.gitkeep]
    D --> D2[.placeholder]

    E --> E1[.git/info/exclude]
    E --> E2[.gitignore_global]

    style A fill:#667eea,color:#fff
    style B fill:#764ba2,color:#fff
    style C fill:#f7f7fd,stroke:#667eea
    style D fill:#e8e8f5,stroke:#764ba2
    style E fill:#d4d4f0,stroke:#666

關鍵觀念:五層分離原則

層級 誰管理 是否提交 範例
核心層 Git 自己 ❌ 絕不 .git/
版本控制層 團隊共享 ✅ 必須 .gitignore, .gitattributes
平台擴充層 平台特定 ✅ 依平台 .github/, .gitlab-ci.yml
社群慣例層 非官方約定 ⚙️ 視情況 .gitkeep
開發者私有層 個人環境 ❌ 不要 .git/info/exclude

三、實戰場景一:解決「.gitignore 為什麼無效?」

問題現場

$ echo "secret.key" >> .gitignore

$ git add .gitignore

$ git commit -m "Add gitignore"
# 但 secret.key 還是被追蹤!

$ git status

modified:   secret.key

根本原因

Key Insight: .gitignore 只對「尚未被追蹤的檔案」生效。已被追蹤的檔案必須先用 git rm --cached 移除追蹤狀態。

根據 Git 官方 gitignore 文檔,如果檔案已經 git add 過,必須先移除追蹤:

# 停止追蹤但保留本地檔案

git rm --cached secret.key
# 確認已加入 .gitignore

echo "secret.key" >> .gitignore
# 提交變更

git add .gitignore

git commit -m "Stop tracking secret.key"

進階技巧:三層忽略策略

# 1. 團隊共享:.gitignore(提交到版本庫)

node_modules/

dist/

*.log
# 2. 專案私有:.git/info/exclude(不會被提交)

# 適合個人 IDE 設定

.vscode/

.idea/

.DS_Store
# 3. 全域設定:~/.gitignore_global(所有專案通用)

# 設定方式:

git config --global core.excludesfile ~/.gitignore_global

最佳實踐: – ✅ 團隊協作檔案(如 node_modules/)寫在 .gitignore – ✅ 個人環境檔案(如 .DS_Store)寫在 ~/.gitignore_global – ✅ 臨時測試檔案寫在 .git/info/exclude


四、實戰場景二:跨平台開發的 CRLF 地獄

問題現場

Windows 開發者提交程式碼後,Mac/Linux 開發者 pull 下來發現:

$ git diff

-#!/bin/bash^M

-echo "Hello"^M

+#!/bin/bash

+echo "Hello"

整個檔案顯示「全部改動」,但其實只是行尾符號不同(CRLF vs LF)。

解決方案:使用 .gitattributes 強制正規化

Best Practice: 每個專案都應該有 .gitattributes 檔案,使用 * text=auto 自動正規化行尾符號。 – 原因:避免跨平台開發時的 CRLF/LF 衝突 – 例外:純 Windows 專案可不設定

根據 Git 官方 gitattributes 文檔,建議的設定如下:

# .gitattributes

# 自動偵測文字檔並正規化

* text=auto
# 強制特定檔案使用 LF(Unix 風格)

*.sh text eol=lf

*.py text eol=lf

*.md text eol=lf
# 強制特定檔案使用 CRLF(Windows 風格)

*.bat text eol=crlf

*.ps1 text eol=crlf
# 二進位檔案不處理

*.png binary

*.jpg binary

*.zip binary

為什麼需要這樣做?

情境 不使用 .gitattributes 使用 .gitattributes
Windows 提交 可能夾帶 CRLF Git 自動轉為 LF 儲存
Mac 檢出 收到 CRLF,diff 爆炸 統一 LF,diff 乾淨
Shell 腳本 可能無法執行(CRLF 錯誤) 強制 LF,確保正常執行

最佳實踐: – ✅ 每個專案都應該有 .gitattributes – ✅ 提交後執行 git add --renormalize . 重新正規化 – ✅ 團隊統一設定 core.autocrlf


五、實戰場景三:「.gitkeep 是什麼?我該用嗎?」

問題現場

你想提交這個目錄結構:

project/
├── src/
│   └── main.py
└── logs/          # 空目錄,但 Git 不追蹤

提交後,logs/ 資料夾消失了!因為 Git 不追蹤空目錄

社群解決方案:.gitkeep

# 建立空檔案讓 Git 追蹤目錄

touch logs/.gitkeep

git add logs/.gitkeep

git commit -m "Add logs directory"

重要觀念:.gitkeep 不是 Git 官方功能

Definition: .gitkeep 是一個社群慣例檔案,用於讓 Git 追蹤空目錄。它不是 Git 官方規範的一部分,任何檔名(如 .placeholder.empty)都能達到相同效果。

項目 說明
官方支援 ❌ 不是 Git 規範的一部分
作用原理 只是個空檔案,讓目錄「非空」所以能被追蹤
替代方案 可以用 .placeholder.emptyREADME.md 等任何檔名
是否提交 ✅ 可以提交(如果目錄必須存在)

實際案例:何時需要 .gitkeep?

# 案例 1:應用程式需要的空目錄

uploads/.gitkeep       # 使用者上傳檔案目錄

cache/.gitkeep         # 快取目錄

logs/.gitkeep          # 日誌目錄
# 案例 2:建置過程需要的目錄

dist/.gitkeep          # 但應該加入 .gitignore

build/output/.gitkeep  # 編譯輸出目錄
# 案例 3:Docker volume 掛載點

docker/volumes/db/.gitkeep

最佳實踐: – ✅ 如果目錄是「運行時需要」,使用 .gitkeep – ❌ 如果目錄是「建置產物」,不要提交(加入 .gitignore) – ⚙️ 也可以在 README.md 或腳本中 mkdir -p 自動建立


六、實戰場景四:CI/CD 與 Git 檔案的整合

GitHub Actions 範例

# .github/workflows/ci.yml

name: CI Pipeline
on:

  push:

    branches: [ main ]

  pull_request:

    branches: [ main ]
jobs:

  test:

    runs-on: ubuntu-latest

    steps:

      - uses: actions/checkout@v3
      # 檢查 .gitattributes 是否有效

      - name: Check line endings

        run: |

          git ls-files --eol | grep 'i/crlf' && exit 1 || exit 0
      # 檢查是否有未追蹤的敏感檔案

      - name: Check for secrets

        run: |

          if git ls-files | grep -E '\.(env|key|pem)$'; then

            echo "❌ 發現敏感檔案被追蹤!"

            exit 1

          fi

GitLab CI 範例

# .gitlab-ci.yml

stages:

  - validate

  - test
validate_git_files:

  stage: validate

  script:

    # 確認 .gitattributes 存在

    - test -f .gitattributes || (echo "缺少 .gitattributes" && exit 1)
    # 確認沒有 CRLF 問題

    - git ls-files --eol | grep 'i/crlf' && exit 1 || exit 0

Docker 與 .gitignore 的協作

# Dockerfile

FROM node:18
WORKDIR /app
# 複製 package.json(不受 .gitignore 影響)

COPY package*.json ./
# 安裝依賴

RUN npm ci
# 複製原始碼(會遵循 .dockerignore)

COPY . .
# 建置

RUN npm run build
# .dockerignore(類似 .gitignore,但給 Docker 用)

node_modules/

.git/

.github/

*.log

.env

關鍵差異: | 項目 | .gitignore | .dockerignore | |——|———–|—————| | 作用對象 | Git 版本控制 | Docker 建置上下文 | | 何時使用 | 決定哪些檔案不追蹤 | 決定哪些檔案不傳給 Docker | | 常見內容 | node_modules/, .env | .git/, README.md, *.md |


七、.git/ 內部結構深入解析(進階)

雖然你不應該手動編輯 .git/ 目錄,但理解其結構能幫助你除錯:

.git/

├── HEAD                  # 指向當前分支

├── config               # 專案層級設定

├── description          # 給 GitWeb 用的描述

├── hooks/               # Git hooks 腳本

│   ├── pre-commit       # 提交前執行

│   ├── post-merge       # 合併後執行

│   └── pre-push         # 推送前執行

├── info/

│   └── exclude          # 私有忽略清單

├── objects/             # Git 物件資料庫(commit、tree、blob)

│   ├── pack/            # 壓縮後的物件

│   └── [0-9a-f]{2}/     # 物件依 hash 前兩碼分類

├── refs/

│   ├── heads/           # 本地分支

│   ├── remotes/         # 遠端分支

│   └── tags/            # 標籤

└── index                # 暫存區(staging area)

實用除錯指令

# 檢視當前分支

cat .git/HEAD

# 輸出:ref: refs/heads/main
# 檢視分支指向的 commit

cat .git/refs/heads/main

# 輸出:a1b2c3d4...(commit hash)
# 檢視物件類型

git cat-file -t a1b2c3d4

# 輸出:commit / tree / blob
# 檢視物件內容

git cat-file -p a1b2c3d4
# 檢查資料庫完整性

git fsck --full
# 清理不必要的物件

git gc --aggressive

八、.gitattributes 進階應用:合併策略與 Git LFS

自訂合併策略

# .gitattributes
# package-lock.json 合併時使用 union 策略(保留雙方)

package-lock.json merge=union
# 資料庫遷移檔案永遠使用我們的版本

db/migrations/* merge=ours
# 設定檔永遠使用他們的版本

config/production.yml merge=theirs

Git LFS(Large File Storage)整合

當專案有大型二進位檔案(如影片、3D 模型、資料集)時:

# 安裝 Git LFS

git lfs install
# .gitattributes 設定

*.psd filter=lfs diff=lfs merge=lfs -text

*.mp4 filter=lfs diff=lfs merge=lfs -text

*.zip filter=lfs diff=lfs merge=lfs -text

*.bin filter=lfs diff=lfs merge=lfs -text
# 檢視 LFS 追蹤的檔案

git lfs ls-files
# 檢視 LFS 儲存用量

git lfs env

為什麼需要 Git LFS? – ❌ 一般 Git:每次提交都儲存完整檔案,倉庫快速膨脹 – ✅ Git LFS:只儲存指標,實際檔案存在 LFS 伺服器


九、檔案提交決策流程圖

當你不確定某個檔案是否該提交時,使用這個決策樹:

flowchart TD
    Start([發現 .git 開頭的檔案]) --> Q1{是否在 .git/ 目錄內?}

    Q1 -->|是| Never[❌ 絕不提交
這是 Git 內部資料] Q1 -->|否| Q2{是否為官方 Git 檔案?} Q2 -->|是| Q3{是全域設定嗎?} Q2 -->|否| Q4{是平台特定檔案?} Q3 -->|是
.gitconfig| Never2[❌ 不提交
屬於個人設定] Q3 -->|否
.gitignore
.gitattributes
.gitmodules| Commit[✅ 應該提交
團隊共享規則] Q4 -->|是
.github/
.gitlab-ci.yml| CI[✅ 提交
若使用該平台] Q4 -->|否| Q5{是社群慣例檔案?} Q5 -->|是
.gitkeep| Keep[⚙️ 視情況
保留空目錄時提交] Q5 -->|否| Unknown[⚠️ 不明檔案
先查證用途] style Never fill:#ff6b6b,color:#fff style Never2 fill:#ff6b6b,color:#fff style Commit fill:#51cf66,color:#fff style CI fill:#51cf66,color:#fff style Keep fill:#ffd43b,color:#333 style Unknown fill:#ff922b,color:#fff

十、常見問題 FAQ

Q1: .gitignore 無法忽略已追蹤的檔案怎麼辦?

A: 使用 git rm --cached 移除追蹤狀態:

# 移除單一檔案追蹤

git rm --cached secret.key
# 移除整個目錄追蹤

git rm -r --cached logs/
# 套用新的 .gitignore 規則

git add .gitignore

git commit -m "Update gitignore and stop tracking files"

檢查是否生效

git check-ignore -v secret.key

# 輸出:.gitignore:5:secret.key    secret.key

Q2: 為什麼我的 Shell 腳本在 CI/CD 上無法執行?

A: 通常是 CRLF 問題。檢查並修復:

# 檢查檔案的行尾符號

git ls-files --eol | grep script.sh
# 修復方式 1:使用 .gitattributes 強制 LF

echo "*.sh text eol=lf" >> .gitattributes

git add --renormalize .

git commit -m "Fix line endings for shell scripts"
# 修復方式 2:本地轉換(臨時)

dos2unix script.sh  # Linux/Mac

Q3: .github/ 和 .git/ 有什麼區別?

A: 完全不同的兩個東西:

項目 .git/ .github/
屬性 Git 官方核心目錄 GitHub 平台擴充目錄
作用 儲存版本控制資料庫 儲存 GitHub Actions、Issue 模板
是否提交 ❌ 絕不提交 ✅ 應該提交(若使用 GitHub)
跨平台 ✅ 所有 Git 環境通用 ❌ 僅 GitHub 平台有效
範例 .git/config, .git/objects/ .github/workflows/ci.yml

Q4: 如何在多個平台(GitHub + GitLab)使用不同的 CI/CD?

A: 兩者可以共存:

project/
├── .github/
│   └── workflows/
│       └── ci.yml       # GitHub Actions
├── .gitlab-ci.yml       # GitLab CI
└── azure-pipelines.yml  # Azure DevOps

注意事項: – ✅ 兩個平台會各自執行自己的 CI/CD – ⚠️ 需要分別維護兩套設定檔 – 建議使用相同的測試指令(如 npm test)保持一致


Q5: .gitattributes 的 merge 策略有哪些?

A: 主要有三種:

# 1. union:保留雙方變更

package-lock.json merge=union
# 2. ours:永遠使用我們的版本

db/schema.sql merge=ours
# 3. theirs:永遠使用他們的版本

config/production.yml merge=theirs
# 4. binary:視為二進位檔,衝突時必須手動解決

*.docx binary

實際應用

# 自動產生的檔案使用 union

package-lock.json merge=union

yarn.lock merge=union
# 資料庫遷移檔案使用 ours(保護生產環境)

migrations/*.sql merge=ours

Q6: 如何讓 git blame 忽略格式化 commit?

A: 使用 .git-blame-ignore-revs 檔案:

# .git-blame-ignore-revs

# 格式化 commit(Prettier)

a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6
# 重構 commit(ESLint autofix)

b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6a1
# 使用方式

git blame --ignore-revs-file .git-blame-ignore-revs file.js
# 設為預設

git config blame.ignoreRevsFile .git-blame-ignore-revs

Q7: 如何檢查專案中所有的 Git 設定?

A: 分層檢查:

# 1. 系統層級設定

git config --system --list
# 2. 全域設定(使用者層級)

git config --global --list
# 3. 專案層級設定

git config --local --list
# 4. 檢視特定設定值

git config user.name

git config core.autocrlf
# 5. 檢視所有設定(包含來源)

git config --list --show-origin

Q8: 如何清理 Git 倉庫中的敏感資訊?

A: 使用 git filter-repo(推薦)或 BFG Repo-Cleaner:

# 安裝 git-filter-repo

pip3 install git-filter-repo
# 移除特定檔案的歷史

git filter-repo --path secret.key --invert-paths
# 移除特定文字(如 API Key)

git filter-repo --replace-text <(echo "SECRET_API_KEY==>REDACTED")
# 強制推送(警告:會改寫歷史)

git push origin --force --all

git push origin --force --tags

⚠️ 重要:此操作會改寫歷史,所有協作者必須重新 clone。


Q9: 為什麼 .gitmodules 必須提交?

A: 因為它記錄了子模組的設定,團隊成員才能正確初始化:

# .gitmodules 範例

[submodule "libs/common"]

    path = libs/common

    url = https://github.com/company/common-lib.git

    branch = main
# 初始化子模組(需要 .gitmodules)

git submodule update --init --recursive

沒有 .gitmodules 會怎樣? – ❌ 其他人 clone 後看不到子模組目錄 – ❌ git submodule update 會失敗 – ❌ CI/CD 無法自動初始化子模組


Q10: 如何安全地檢視 .git/ 內部結構?

A: 使用唯讀指令:

# 檢視目錄結構(限制深度避免過多輸出)

tree .git/ -L 2
# 檢視 HEAD 指向

cat .git/HEAD
# 檢視最近的 commit 內容

git cat-file -p HEAD
# 檢視物件類型

git cat-file -t HEAD
# 檢查倉庫完整性

git fsck --full
# 檢視所有 refs

git show-ref

⚠️ 永遠不要手動編輯 .git/ 內的檔案,除非你完全理解後果。


十一、官方與非官方檔案完整對照表

類別 檔案 官方狀態 是否提交 用途
核心層 .git/ ✅ 官方 ❌ 絕不 Git 資料庫
核心層 .gitconfig ✅ 官方 ❌ 個人設定 使用者設定
版本控制層 .gitignore ✅ 官方 ✅ 必須 忽略檔案規則
版本控制層 .gitattributes ✅ 官方 ✅ 必須 文字正規化、合併策略
版本控制層 .gitmodules ✅ 官方 ✅ 必須 子模組設定
版本控制層 .mailmap ✅ 官方 ✅ 可選 作者名稱統一
版本控制層 .git-blame-ignore-revs ✅ 半官方 ✅ 可選 忽略格式化 commit
平台擴充層 .github/ ❌ 平台 ✅ 若用 GitHub GitHub Actions、模板
平台擴充層 .gitlab-ci.yml ❌ 平台 ✅ 若用 GitLab GitLab CI/CD
平台擴充層 .gitreview ❌ 平台 ✅ 若用 Gerrit Gerrit Code Review
社群慣例層 .gitkeep ❌ 慣例 ⚙️ 視情況 保留空目錄
私有層 .git/info/exclude ✅ 官方 ❌ 不提交 私有忽略清單
私有層 ~/.gitignore_global ✅ 官方 ❌ 不提交 全域忽略規則

十二、最佳實踐檢查清單

✅ 每個專案應該有的檔案

✅ .gitignore           # 忽略 node_modules、.env、*.log

✅ .gitattributes       # 統一行尾符號(* text=auto)

✅ README.md            # 專案說明

⚙️ .gitkeep             # 若有空目錄需求(如 logs/、uploads/)

⚙️ .git-blame-ignore-revs  # 若有大規模格式化 commit

✅ 跨平台開發團隊必須有的設定

# .gitattributes

* text=auto

*.sh text eol=lf

*.bat text eol=crlf

*.png binary

*.jpg binary

✅ CI/CD 專案應該有的設定

# .github/workflows/validate-git.yml

name: Validate Git Configuration
on: [push, pull_request]
jobs:

  validate:

    runs-on: ubuntu-latest

    steps:

      - uses: actions/checkout@v3
      - name: Check .gitattributes exists

        run: test -f .gitattributes
      - name: Check for CRLF issues

        run: |

          if git ls-files --eol | grep 'i/crlf'; then

            echo "❌ 發現 CRLF 問題"

            exit 1

          fi
      - name: Check for untracked secrets

        run: |

          if git ls-files | grep -E '\.(env|key|pem)$'; then

            echo "❌ 發現敏感檔案被追蹤"

            exit 1

          fi

十三、結論與關鍵要點

理解 Git 檔案系統的邊界,能幫助你:

  • 避免衝突:透過 .gitattributes 統一行尾符號
  • 保護隱私:正確使用 .gitignore.git/info/exclude
  • 提升協作:團隊共享 .gitignore.gitattributes
  • 優化 CI/CD:善用 .github/.gitlab-ci.yml
  • 除錯更快:理解 .git/ 內部結構

快速參考表

需求 使用檔案 範例
忽略檔案 .gitignore node_modules/, *.log
統一行尾 .gitattributes * text=auto, *.sh eol=lf
保留空目錄 .gitkeep logs/.gitkeep
私有忽略 .git/info/exclude .vscode/, .idea/
CI/CD .github/.gitlab-ci.yml GitHub Actions
大檔案 .gitattributes + Git LFS *.psd filter=lfs

Sources


Leave a Comment