.gitignore 和 .gitattributes 是 Git 版本控制系统中两个重要的配置文件,它们帮助开发者精细地控制 Git 如何处理工作目录中的文件。gitignore 主要用于忽略不应该被版本控制的文件,而 gitattributes 则用于定义不同文件的属性,影响 Git 存储和比较文件的方式。理解和正确使用这两个文件对于维护干净、高效且一致的 Git 仓库至关重要。

核心思想:

  • .gitignore 告诉 Git 哪些文件或目录应该被忽略,不纳入版本控制。
  • .gitattributes 告诉 Git 如何对待特定类型的文件,例如行尾符、合并策略、文本转换等。

一、.gitignore 文件详解

.gitignore 文件用于指定 Git 应该忽略哪些文件或目录。 这些被忽略的文件不会被 Git 跟踪,也不会被添加到仓库中。这对于排除构建产物、日志文件、敏感配置、IDE 特定文件等内容非常有用,可以保持仓库的整洁,避免提交不必要的文件,并减少仓库大小。

1.1 工作原理

Git 在执行 git add 或 git commit 等命令时,会检查工作目录中是否存在 .gitignore 文件。它会自底向上地查找(从当前目录到父目录),并应用找到的所有 .gitignore 文件中的规则。最接近文件的 .gitignore 规则优先级最高。

你可以在项目的任意层级创建 .gitignore 文件。通常,一个项目只有一个位于根目录的 .gitignore 文件。此外,Git 还支持一个全局的 .gitignore 文件,通过 git config --global core.excludesfile ~/.gitignore_global 配置。

1.2 规则格式

.gitignore 文件中的每行都代表一个忽略规则。规则支持通配符和各种模式:

  • 空行或以 # 开头的行:被视为空白或注释,会被 Git 忽略。
  • 普通文件名:忽略指定名称的文件。
    1
    mysecret.conf
  • 目录名:忽略指定目录及其所有内容。
    1
    2
    bin/          # 忽略项目根目录下的 bin 目录
    /logs # 忽略项目根目录下的 logs 目录
  • 通配符 *:匹配零个或多个字符。
    1
    2
    *.log         # 忽略所有 .log 文件
    *build/ # 忽略所有以 build 结尾的目录
  • 通配符 ?:匹配单个字符。
    1
    file?.txt     # 匹配 file1.txt, fileA.txt 等
  • 方括号 []:匹配括号内任何一个字符。
    1
    file[0-9].txt # 匹配 file0.txt 到 file9.txt
  • 感叹号 !:用于否定之前的规则,强制包含某个文件或目录。
    1
    2
    *.log         # 忽略所有 .log 文件
    !important.log # 但不忽略 important.log
  • 斜杠 /:
    • 在模式的开头表示只匹配项目根目录的文件或目录。
      1
      /temp         # 只忽略项目根目录下的 temp 目录 (不会忽略 subdir/temp)
    • 在模式的末尾表示只匹配目录。
      1
      build/        # 忽略名为 build 的目录
  • 双星号 **:匹配任意中间目录,即递归匹配。
    1
    2
    **/logs       # 忽略任意层级下的 logs 目录
    docs/**/*.md # 忽略 docs 目录下任意子目录中的所有 .md 文件

1.3 示例

在 Go 项目中一个典型的 .gitignore 文件可能会包含以下内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
# IDE specific files
.idea/
.vscode/

# Build artifacts
*.exe
*.dll
*.so
*.o
*.a
*.obj
# Go build cache
go.mod.bak
go.sum.orig
/out/
/dist/
/bin/

# Test output
testdata/
*.test

# Compiled Go packages
*.p
*.b
.cache/

# Log files
*.log
logs/
tmp/

# External dependencies (if not using Go Modules correctly or for vendoring)
vendor/ # 如果使用 Go Modules 且不希望提交 vendor 目录

# Sensitive environment variables or config files
.env
config.local.yaml

在 Python 项目中一个典型的 .gitignore 文件可能会包含以下内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# Byte-code files
*.pyc
__pycache__/

# Virtual environment
venv/
.venv/
env/
.env/

# IDE specific files
.idea/
.vscode/

# Test output
.pytest_cache/
htmlcov/
.coverage

# Database files
*.sqlite3
db.sqlite3

# Log files
*.log

# Build artifacts
dist/
build/
*.egg-info/

# Jupyter Notebook files
.ipynb_checkpoints/

1.4 注意事项

  • 只忽略未被 Git 跟踪的文件:.gitignore 规则只对尚未被 Git 跟踪的文件生效。如果一个文件已经被 Git 跟踪(即已经通过 git add 添加到仓库并提交),即使后来将其添加到 .gitignore 中,Git 也不会将其忽略。你需要先从仓库中移除该文件 (但不从工作目录删除),然后 Git 才会遵守 .gitignore 规则:
    1
    2
    git rm --cached <file_path>
    git commit -m "Remove <file_path> from gitignore"
  • 全局 .gitignore (Optional):可以设置一个全局的 .gitignore 文件来忽略所有项目通用的文件 (如操作系统生成的 .DS_Store、Thumbs.db,或个人 IDE 配置文件)。这通过修改 Git 配置完成:
    1
    git config --global core.excludesfile ~/.gitignore_global

二、.gitattributes 文件详解

.gitattributes 文件用于为特定文件或路径模式定义属性。 这些属性会影响 Git 处理文件的方式,例如行尾转换、差异比较、合并策略、二进制文件处理等。它允许你为不同类型的文件指定不同的 Git 行为。

2.1 工作原理

与 .gitignore 类似,Git 在检查文件时也会查找 .gitattributes 文件,并应用其定义的规则。它也是自底向上地查找,最接近文件的规则优先级最高。

.gitattributes 文件通常位于项目的根目录,但也允许存在于子目录中。它也可以被配置为全局文件,但这种情况较少见,大多数属性通常是项目特定的。

2.2 规则格式

.gitattributes 文件中的每行由一个文件模式和一系列属性组成。

pattern attribute1 attribute2=value ...

  • pattern:与 .gitignore 类似的 glob 模式,用于匹配文件。
  • attribute:定义对匹配文件应用的属性。

2.3 常用属性

以下是一些常见的 gitattributes 属性及其用途:

2.3.1 text 属性:行尾转换

这是最常用的属性之一,用于处理不同操作系统之间的行尾符差异。Windows 使用 CR LF (\r\n),Unix-like 系统使用 LF (\n)。Git 默认会尝试在检出时将 LF 转换为 CR LF (Windows),在提交时将 CR LF 转换为 LF。但这种自动转换有时会导致问题,text 属性允许你明确控制:

  • text (或 text=auto):Git 会根据文件内容尝试自动处理行尾。这是 Git 的默认行为。
  • text eol=lf:强制 Git 始终将行的结束存储为 LF。这适用于所有文本文件,确保在所有平台都使用 LF。在检出时,Windows 用户会转换为 CRLF。
  • text eol=crlf:强制 Git 始终将行的结束存储为 CRLF。较少使用,通常只在特定 Windows 遗留系统上需要。
  • -text:强制 Git 将文件视为二进制文件,完全不进行行尾转换。通常用于图像、视频等二进制文件,或被加密的文件。
  • text=auto eol=lf:一个推荐的配置,将所有文件都视为 text=auto,但如果 Git 确定它是文本文件,则在提交时确保其内部行尾是 LF。

2.3.2 binary 属性:二进制文件

  • binary:这是一个宏属性,等同于 -text -diff -merge。它告诉 Git 将文件视为二进制文件,不进行行尾转换,不尝试对它们进行差异比较(只显示修改),也不尝试进行合并(如果发生冲突,通常会选择一方的版本)。这对于图像、编译后的可执行文件、压缩包等非常重要。

2.3.3 diff 属性:差异比较

  • diff:告诉 Git 如何生成文件的差异。
    • diff:允许对文件进行差异比较 (默认)。
    • -diff:禁用对该文件的差异比较。与 binary 属性的一部分相同。
    • diff=tool:为特定类型的文件指定外部差异工具或特定的差异生成算法(例如,为文本型的代码文件使用按行比较,为 JSON/XML 等结构化文件使用智能差异)。
      1
      *.json diff=json

2.3.4 merge 属性:合并策略

  • merge:指定文件在合并冲突时的处理策略。
    • merge=text:默认,使用 Git 的标准三路合并策略。
    • merge=binary:不尝试合并,如果发生冲突,以 BASE 或 HEAD 版本的整个文件作为结果。与 binary 属性的一部分相同。
    • merge=union:尝试在有冲突时合并所有有冲突的行,这对于配置文件 (如 Dockerfile) 可能有用。
    • merge=ours:如果发生冲突,使用我们(当前分支)的版本。
    • merge=theirs:如果发生冲突,使用他们(传入分支)的版本。

2.3.5 linguist-language, linguist-vendored, linguist-generated 属性:GitHub Linguist

这些属性主要由 GitHub 用来识别仓库中的代码语言,并生成语言统计图。

  • linguist-language=<language>:强制指定文件的语言。
    1
    *.json linguist-language=JSON
  • linguist-vendored:将文件标记为被 vendored(供应商提供的),这样它们就不会计入仓库的语言统计。
    1
    vendor/** linguist-vendored
  • linguist-generated:将文件标记为自动生成,同样不会计入语言统计。
    1
    *.pb.go linguist-generated # Protocol Buffer 生成的 Go 文件

2.4 示例

在一个 Go 项目中,一个典型的 .gitattributes 文件可能包含以下内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
# Set default line endings to LF for all text files where Git deems it appropriate.
# This ensures consistency across different OS.
*.go text eol=lf
*.mod text eol=lf
*.sum text eol=lf
*.md text eol=lf
*.yaml text eol=lf
*.yml text eol=lf
*.json text eol=lf

# Mark generated files to be ignored by GitHub Linguist for language statistics
# For example, Go files generated from Protocol Buffers
*.pb.go linguist-generated

# Mark image files as binary to avoid any text processing
*.png binary
*.jpg binary
*.jpeg binary
*.gif binary

# Mark compiled binaries as binary
*.exe binary
*.dll binary
*.so binary
*.a binary
# Explicitly set -text, -diff, -merge for general compiled binaries
# This essentially duplicates the binary macro
bin/* -text -diff -merge

# Optional: configure merge strategy for specific files
# For example, if you have a deployment script that always needs to be 'ours' in case of conflict
# deploy.sh merge=ours

# Optional: Custom diff driver for specific types (e.g., to highlight JSON changes better)
# *.json diff=json

2.5 推荐配置 (.gitattributes 的推荐配置)

为了最大程度地避免跨平台行尾问题,以下是一个推荐的 .gitattributes 配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# 设置自动行尾转换,并确保文本文件在仓库中是 LF
# Git 将尝试识别哪些文件是文本,哪些是二进制。
# 对于文本文件,当提交时,会将所有行尾转换为 LF。
# 当检出到工作区时:
# - 在 Windows 上,LF 会自动转换为 CRLF。
# - 在 Linux/macOS 上,LF 保持 LF。
#
# 这通常是最好的默认行为,使开发人员在自己的操作系统上工作,
# 同时在 Git 仓库中保持一致的 LF 行尾。
* text=auto

# 明确强制所有 Go 文件使用 LF 行尾
*.go text eol=lf

# 排除不需要自动转换的文件类型 (例如,已被标记为二进制或可能有特定行尾要求的文件)
# 例如,一些旧的 Windows 脚本可能需要 CRLF
# *.bat -text

# 以下是 binary 属性的常用示例
*.png binary
*.jpg binary
*.jpeg binary
*.gif binary
*.zip binary
*.tar.gz binary
*.mp4 binary
*.exe binary
*.bin binary

这个配置意味着 Git 会尝试自动检测文件类型。对于它认为是文本的文件,在提交到仓库时,所有行尾都会被标准化为 LF。在 Windows 上检出时,LF 会被转换为 CRLF;在其他系统上,LF 保持不变。这解决了大多数行尾问题。

三、.gitignore 与 .gitattributes 的区别

特性 .gitignore .gitattributes
主要目的 告诉 Git 不要跟踪某些文件或目录。 告诉 Git 如何处理/对待某些已跟踪的文件。
作用对象 只对未被 Git 跟踪的文件生效。 对已被 Git 跟踪的文件和即将被跟踪的文件都生效。
影响范围 影响文件是否进入 Git 仓库。 影响文件在仓库中的存储方式 (如行尾)、差异比较、合并行为等。
常见用途 忽略编译产物、日志、IDE 配置文件、node_modules、venv 等。 规范行尾、标记二进制文件、定义合并策略、自定义差异比较、GitHub Linguist 属性。
文件内容 模式列表,指示要忽略的文件。 模式与属性对列表,指示如何处理文件。

四、总结

.gitignore 和 .gitattributes 都是 Git 项目中非常重要的配置文件,它们各自承担着不同的职责,但共同协作以确保 Git 仓库的整洁、一致性和功能性。

  • 使用 .gitignore 来管理不应该进入版本控制的文件,保持仓库的精简。
  • 使用 .gitattributes 来定义文件级别的 Git 行为,特别是处理跨平台行尾问题、标记二进制文件以及优化差异和合并操作。

正确地配置这两个文件可以避免许多常见的 Git 问题,尤其是在跨平台协作和处理生成的代码时,从而提高开发效率和代码质量。务必将这两个文件纳入版本控制,以便团队成员之间共享这些重要的配置。