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

目錄

🌏 Read the English version


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

你是否曾經:

  • 空目錄無法提交,不知道為什麼 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"
$ 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 提交可能夾帶 CRLFGit 自動轉為 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[❌ 絕不提交<br/>這是 Git 內部資料]
    Q1 -->|否| Q2{是否為官方 Git 檔案?}

    Q2 -->|是| Q3{是全域設定嗎?}
    Q2 -->|否| Q4{是平台特定檔案?}

    Q3 -->|是<br/>.gitconfig| Never2[❌ 不提交<br/>屬於個人設定]
    Q3 -->|否<br/>.gitignore<br/>.gitattributes<br/>.gitmodules| Commit[✅ 應該提交<br/>團隊共享規則]

    Q4 -->|是<br/>.github/<br/>.gitlab-ci.yml| CI[✅ 提交<br/>若使用該平台]
    Q4 -->|否| Q5{是社群慣例檔案?}

    Q5 -->|是<br/>.gitkeep| Keep[⚙️ 視情況<br/>保留空目錄時提交]
    Q5 -->|否| Unknown[⚠️ 不明檔案<br/>先查證用途]

    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/❌ 平台✅ 若用 GitHubGitHub Actions、模板
平台擴充層.gitlab-ci.yml❌ 平台✅ 若用 GitLabGitLab CI/CD
平台擴充層.gitreview❌ 平台✅ 若用 GerritGerrit 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/ 內部結構

快速參考表

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

Sources