Git 配置问题排查:常见修复与最佳实践

通过追踪设置来源、解决身份、别名、钩子、换行符、拉取和凭据问题,修复 Git 配置问题。

Git 配置问题排查:常见修复与最佳实践

大多数 Git 配置问题看起来比实际更奇怪。Git 通常只是在执行其某个配置文件告诉它的操作。难点在于找到是哪个文件,因为设置可能存在于仓库、用户配置、系统配置、包含文件或仅适用于某个目录的条件包含中。

我首先使用的命令是:

git config --list --show-origin --show-scope

如果你的 Git 版本不支持 --show-scope,请使用:

git config --list --show-origin

来源告诉你每个值来自哪个文件。这比单独的值更重要。看到 [email protected] 是不够的;你需要知道它是来自 .git/config~/.gitconfig/etc/gitconfig 还是包含的工作配置文件。

Git 配置有优先级顺序。本地仓库配置通常覆盖全局用户配置,全局配置覆盖系统配置。命令行选项和环境变量可以为单个命令覆盖所有这些。条件包含增加了另一层:全局文件可以说,“当我在这个目录内时,也加载这个其他配置。”

你可以检查一个设置及其来源:

git config --show-origin --get user.email
git config --show-origin --get core.autocrlf
git config --show-origin --get-regexp '^alias\.'

这个习惯可以避免大量无效编辑。如果仓库本地设置了 user.email,更改 git config --global user.email 不会影响该仓库中的提交。

提交时姓名或电子邮件错误

症状很简单:提交显示错误的作者。这通常发生在你在同一台笔记本电脑上同时用于工作和个人项目时,或者在克隆公司仓库之前没有设置工作电子邮件。

检查 Git 在当前仓库中将使用什么:

git config user.name
git config user.email
git config --show-origin --get user.email

全局设置默认身份:

git config --global user.name "Your Name"
git config --global user.email "[email protected]"

对于单个仓库,本地设置:

git config --local user.name "Your Name"
git config --local user.email "[email protected]"

如果你希望 Git 在未明确配置身份时拒绝提交,请使用:

git config --global user.useConfigOnly true

这个设置对于在身份之间切换的人很有用。它可能会让初学者感到烦恼,因为 Git 将停止从系统用户名和主机名猜测。当你宁愿提交失败也不愿在错误的地址下创建提交时使用它。

为了更清晰的工作/个人分离,使用条件包含:

# ~/.gitconfig
[user]
    name = Your Name
    email = [email protected]

[includeIf "gitdir:~/work/"]
    path = ~/.gitconfig-work

然后将以下内容放入 ~/.gitconfig-work

[user]
    email = [email protected]

gitdir:~/work/ 中的尾部斜杠很重要,因为它表示该目录下的仓库。如果它没有触发,从工作仓库内部运行 git config --list --show-origin 并检查包含的文件是否出现。

如果你已经用错误的电子邮件进行了提交,更改配置只会修复未来的提交。对于未发布的提交,你可以修改或变基。对于已经推送到共享分支的提交,在重写历史之前请先询问。

不起作用的别名

Git 别名存储在 alias.* 下。像这样列出它们:

git config --get-regexp '^alias\.'

普通别名扩展为 Git 子命令:

git config --global alias.st 'status -sb'
git st

如果别名需要 shell 管道、变量扩展、cd 或其他非 Git 命令,它必须以 ! 开头:

git config --global alias.recent '!git for-each-ref --sort=-committerdate --count=10 --format="%(refname:short)" refs/heads/'

没有 !,Git 会尝试将第一个单词视为 Git 命令。ls -la 变成“运行名为 ls 的 Git 命令”,这不是你的本意。

引号是另一个常见失败点。如果你从 shell 定义别名,尽可能对整个别名使用单引号,然后在需要时在内部使用双引号。不同的 shell 处理引号的方式不同,尤其是 PowerShell 和 cmd.exe。如果复杂的别名一直出错,直接编辑配置文件:

git config --global --edit

一个实用的调试技巧是从 Git 外部开始命令。一旦它工作,将其粘贴到别名中。如果它是 shell 别名,用 ! 前缀并再次测试。

还要注意那些掩盖心智模型的别名。名为 pullmerge 的别名可能会使 Git 行为令人惊讶。Git 不允许别名直接覆盖内置命令,但 shell 别名和包装脚本可以。如果 git pull 行为异常,也要检查你的 shell 配置:

type git
alias | grep git

从不运行的钩子

钩子失败有三个无聊的原因:Git 在查找不同的钩子目录,文件不可执行,或者脚本假设了一个它没有的环境。

检查配置的钩子路径:

git config --show-origin --get core.hooksPath

如果这打印出 .githooks,Git 将使用 .githooks/pre-commit,而不是 .git/hooks/pre-commit。如果它什么也不打印,Git 使用 .git/hooks

在 macOS 和 Linux 上,检查权限:

ls -l .git/hooks/pre-commit .githooks/pre-commit 2>/dev/null
chmod +x .githooks/pre-commit

以非零状态退出的钩子会阻止 Git 操作。在顶部添加临时跟踪:

#!/usr/bin/env bash
set -x
pwd
env | sort

不要在共享钩子中留下嘈杂的跟踪。它会很快变得不可读。

路径问题在 GUI 客户端中很常见。在你的终端中工作的钩子可能在 IDE 中失败,因为 IDE 没有加载你的 shell 配置文件。尽可能使用项目本地命令:

./node_modules/.bin/eslint .

或者检查命令并打印有用的消息:

if ! command -v npm >/dev/null 2>&1; then
  echo "npm is required for this hook but was not found in PATH."
  exit 1
fi

行尾不断变化

行尾问题表现为文件在检出后立即显示为已修改,每个行都更改的大差异,或者在 Windows 上编辑后在 Linux 上失败的脚本。

检查你的设置:

git config --show-origin --get core.autocrlf
git config --show-origin --get core.eol

常见的选择是:

# Windows 友好检出,仓库中为 LF
git config --global core.autocrlf true

# macOS/Linux 友好:仅在提交时将 CRLF 转换为 LF
git config --global core.autocrlf input

# 不自动转换
git config --global core.autocrlf false

对于团队来说,.gitattributes 比告诉每个开发者正确设置全局变量更可靠。将项目规则放入仓库:

* text=auto
*.sh text eol=lf
*.bat text eol=crlf
*.png binary
*.jpg binary
*.pdf binary

在添加或更改 .gitattributes 后,有意识地规范化文件:

git add --renormalize .
git status

仔细检查那个差异。它可能会触及许多文件,你不想将行尾规范化与功能工作混合在一起。

Pull、Push 或 Merge 使用了错误的默认值

有时配置问题不是身份或行尾。而是 Git 选择了你意想不到的拉取策略。

检查与拉取相关的设置:

git config --show-origin --get pull.rebase
git config --show-origin --get pull.ff
git config --show-origin --get branch.main.rebase

如果 git pull 在你期望合并时一直变基,则可能启用了 pull.rebase 或分支特定设置。如果它拒绝非快进拉取,则可能设置了 pull.ff=only。这些设置本身并没有错;它们只需要匹配团队的工作流程。

设置显式默认值,而不是忍受警告和意外:

# 拉取时合并
git config --global pull.rebase false

# 拉取时变基
git config --global pull.rebase true

# 仅允许快进拉取
git config --global pull.ff only

对于单个分支:

git config branch.main.rebase true

凭据助手混淆

身份验证问题通常看起来像远程问题,但实际上来自本地配置。Git 可能使用了一个缓存了旧令牌的凭据助手。

检查助手:

git config --show-origin --get-all credential.helper

你可能会看到 osxkeychainmanagermanager-corestorecache。可能存在多个助手。如果 Git 一直发送错误的凭据,请从操作系统钥匙串或凭据管理器中删除它,而不是更改随机的远程 URL。

还要检查远程:

git remote -v

SSH 和 HTTPS 使用不同的身份验证路径。如果一个仓库使用 [email protected]:org/repo.git,另一个使用 https://github.com/org/repo.git,它们不一定使用相同的凭据。

可靠的调试例程

当 Git 配置感觉不一致时,使用例程而不是猜测:

  1. 从仓库根目录运行失败的命令。
  2. 使用 git config --show-origin --get <name> 检查确切的设置。
  3. 使用 git config --list --show-origin --show-scope 列出相关设置。
  4. 在更改全局配置之前检查 .git/config 中的本地配置。
  5. 如果问题只发生在一个目录下,检查条件包含。
  6. 进行最小的配置更改并重新运行原始命令。

Git 配置之所以强大,是因为它可以分层。这也是它变得令人困惑的原因。解决方法是使层可见,然后更改实际生效的层。

当配置在 IDE 中不同时

一类令人困惑的 Git 问题只出现在编辑器或 GUI 中。终端使用一个身份,但 IDE 使用另一个身份提交。钩子在你的 shell 中通过,但从提交面板失败。凭据提示出现在终端中,而 GUI 用旧令牌静默重试。

原因通常是环境。你的交互式 shell 可能加载了 .bashrc.zshrc、SDK 管理器、语言版本管理器和自定义 PATH 条目。从桌面启动的 GUI 可能不会加载任何这些。Git 本身读取相同的配置文件,但钩子和凭据助手可能会看到一个不同的世界。

要调试它,创建一个临时钩子,将环境打印到本地文件:

#!/usr/bin/env bash
{
  date
  pwd
  git --version
  git config --list --show-origin
  env | sort
} > /tmp/git-hook-debug.log
exit 1

从 IDE 运行 Git 操作,然后检查 /tmp/git-hook-debug.log。之后删除钩子。这告诉你 Git 和钩子实际看到了什么,而不是你的终端看到了什么。

对于 GUI 客户端中的身份问题,检查该工具是否有自己的 Git 设置。一些客户端使用系统 Git;其他客户端捆绑 Git 或将用户身份存储在应用程序偏好设置中。如果客户端通过 Git 提交,git log --format=fuller -1 将显示生成的作者和提交者。这有助于区分“Git 配置错误”和“GUI 使用了它自己的设置”。

当有疑问时,对重要的项目显式设置仓库本地设置:

git config --local user.email "[email protected]"
git config --local core.hooksPath .githooks

本地配置减少了意外,因为它随该机器上的仓库元数据一起移动。它仍然不会被提交,因此共享规则应存在于跟踪的文件中,例如 .gitattributes.editorconfig、钩子脚本和项目文档。

保持一个已知良好的基线

当配置问题不断出现时,为你自己的机器保存一个小的已知良好的基线。它不需要很花哨。一个带有注释的 ~/.gitconfig,包含你的身份、编辑器、默认分支行为、凭据助手和包含,就足够了。然后,当新笔记本电脑行为不同时,你可以进行比较,而不是重新发现每个设置。

有用的基线检查包括:

git config --global --list --show-origin
git config --global --get core.editor
git config --global --get init.defaultBranch
git config --global --get-all credential.helper

小心从互联网或同事那里复制配置。别名可能假设你没有的工具。凭据助手是平台特定的。行尾设置可能适合他们的操作系统,但不适合你的。将 Git 配置视为 shell 配置:借鉴想法,但在保留之前理解每一行。

对于项目维护者来说,最好的修复是将共享期望从个人配置中移出。将行尾放在 .gitattributes 中,格式放在格式化程序配置中,忽略的文件放在 .gitignore 中,以及所需的检查放在 CI 中。项目依赖不可见的全局设置越少,它产生的配置工单就越少。

当你更改设置以修复问题时,记下它是本地还是全局。许多未来的 Git 谜团始于一个良好的临时本地修复,但六个月后没有人记得。