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

核心思想:

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

一、.gitignore 文件详解

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

1.1 工作原理

Git 在执行 git addgit 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_StoreThumbs.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_modulesvenv 等。 规范行尾、标记二进制文件、定义合并策略、自定义差异比较、GitHub Linguist 属性。
文件内容 模式列表,指示要忽略的文件。 模式与属性对列表,指示如何处理文件。

四、总结

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

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

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