---
title: "同一文件为什么出现整页 diff：用 Git 属性锁定换行符"
date: 2026-11-13
slug: git-gitattributes-line-endings
canonical: https://kernelranch.com/blog/2026/11/git-gitattributes-line-endings
markdown_url: https://kernelranch.com/blog/2026/11/git-gitattributes-line-endings.md
tags: ["git", "windows", "shell"]
---Windows 编辑器保存后，Git diff 突然显示整页变化。先用 `git ls-files --eol` 看索引和工作区的换行符，再查命中哪些属性。项目可以用 `.gitattributes` 约束常见文本类型，避免不同机器的全局设置给同一个路径不同的检出结果。

## 1. 本机复现

保存为 `eol_lab.py`，运行 `python eol_lab.py`，需能在 PATH 找到 Git。脚本只操作临时仓库，直接比较字节，避免终端或文本读取自动转换换行符。

```python
from pathlib import Path
import subprocess
from tempfile import TemporaryDirectory

with TemporaryDirectory() as tmp:
    root = Path(tmp)

    def git(*args):
        return subprocess.run(
            ["git", *args], cwd=root, capture_output=True, check=True
        ).stdout

    git("init", "-q")
    git("config", "core.autocrlf", "false")
    script = root / "job.sh"
    batch = root / "job.cmd"
    script.write_bytes(b"echo ok\r\n")
    batch.write_bytes(b"echo ok\r\n")
    git("add", "job.sh", "job.cmd")
    assert git("show", ":job.sh") == b"echo ok\r\n"
    print("before: staged job.sh uses CRLF")

    (root / ".gitattributes").write_bytes(b"*.sh text eol=lf\n*.cmd text eol=crlf\n")
    git("add", ".gitattributes")
    git("add", "--renormalize", "job.sh", "job.cmd")
    for name in ("job.sh", "job.cmd"):
        assert git("show", ":" + name) == b"echo ok\n"

    script.unlink()
    batch.unlink()
    git("checkout-index", "--", "job.sh", "job.cmd")
    assert script.read_bytes() == b"echo ok\n"
    assert batch.read_bytes() == b"echo ok\r\n"
    print("after: both staged blobs use LF")
    print("checkout: job.sh=LF job.cmd=CRLF")
```

本地实测输出如下。

```text
before: staged job.sh uses CRLF
after: both staged blobs use LF
checkout: job.sh=LF job.cmd=CRLF
```

## 2. 在已有项目中引入规则

`text eol=crlf` 的文本在索引中仍会以 LF 规范化，CRLF 用于检出。用 `git check-attr text eol -- job.sh` 查看属性的实际取值，再用 `git ls-files --eol -- job.sh` 对照 `i/` 和 `w/`。如果看到 `mixed`，先查编辑器或生成脚本的输出。

在干净工作区里提交属性文件，并把 `git add --renormalize -- <路径>` 限定到准备迁移的已跟踪路径。查看 `git diff --cached`，把换行迁移单独评审，避免和业务改动混在一起。字节敏感的测试样本或二进制可用 `-text` 明确关闭换行转换，并用项目已有的校验方式确认字节。实验先移除两个临时样本，再从索引重新检出，分别验证暂存时的规范化和检出时的转换。

## 小结

把换行符当成项目级的文件属性管理。用 Git 自己的索引和检出结果验证迁移。

## 数据来源与检索时间

检索时间 2026-11-13。

| 来源 | URL | 对应内容 |
|---|---|---|
| Git gitattributes | https://git-scm.com/docs/gitattributes#_checking_out_and_checking_in | text、eol、索引规范化和工作区换行 |
| Git ls-files | https://git-scm.com/docs/git-ls-files#Documentation/git-ls-files.txt---eol | 显示索引和工作区的换行符 |

## 核验说明

- Windows、Python 3.11.9、Git 2.54.0.windows.1 比较了两个样本的原始字节。项目现有文件或编辑器配置需要按路径另行核验。
