Git IncludeIf:优雅地管理多个 Git 身份

作为开发者,你是否遇到过这样的尴尬:在公司项目中不小心用个人邮箱提交了代码?或者在开源项目中暴露了工作邮箱?如果你同时维护着工作项目和个人项目,手动切换 Git 配置不仅繁琐,还容易出错。

幸运的是,Git 提供了一个强大而优雅的解决方案:includeIf。这个功能可以让 Git 根据不同的条件自动加载不同的配置文件,彻底解决多身份管理的难题。

为什么需要 includeIf?

在日常开发中,我们常常需要在不同的身份之间切换:

  • 工作项目:使用公司邮箱(work@example.com)、公司的 GPG 签名密钥、公司的 SSH 密钥
  • 个人项目:使用私人邮箱(personal@example.com)、个人的 GPG 密钥、个人的 SSH 密钥
  • 开源贡献:可能又是另一套配置

如果没有 includeIf,你需要:

  1. 每次切换项目时记得运行 git config user.email xxx@xxx.com
  2. 或者在每个仓库中手动设置本地配置
  3. 提交前反复检查当前使用的是否是正确的身份

这种手动方式不仅麻烦,而且极易出错。一旦用错身份提交并推送到远程仓库,修改起来会非常麻烦。

includeIf 是什么?

includeIf 是 Git 2.13(2017 年 5 月)引入的条件配置包含功能。它允许你根据特定条件(如仓库路径、远程 URL、当前分支等)自动加载不同的配置文件。

基本语法如下:

[includeIf "条件"]
    path = 要包含的配置文件路径

当 Git 读取配置时,会评估条件。如果条件满足,指定路径的配置文件内容就会被包含进来,其中的配置会覆盖之前的设置。

includeIf 的几种常用条件

1. 基于目录路径(gitdir)

这是最常用的方式,根据仓库所在的目录来决定使用哪个配置:

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

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

注意事项

  • 路径末尾的 / 很重要!~/work/ 会匹配该目录下的所有子目录
  • 如果写成 ~/work(没有斜杠),只会精确匹配这个路径
  • 波浪号 ~ 会自动展开为用户主目录

2. 大小写不敏感的路径匹配(gitdir/i)

在 Windows 系统上特别有用:

[includeIf "gitdir/i:C:/Work/"]
    path = C:/Users/username/.gitconfig-work

3. 基于分支名称(onbranch)

根据当前所在的分支来加载配置:

[includeIf "onbranch:main"]
    path = ~/.gitconfig-main

4. 基于远程 URL(hasconfig:remote.*.url)⭐

这是 Git 2.36(2022 年 4 月)引入的强大功能,可以根据仓库的远程 URL 来决定配置。这也是本文重点介绍的方式。

[includeIf "hasconfig:remote.*.url:https://github.com/**"]
    path = ~/.gitconfig-github

[includeIf "hasconfig:remote.*.url:https://gitlab.company.com/**"]
    path = ~/.gitconfig-work

实战:使用 hasconfig:remote.*.url 区分身份

现在让我们看一个完整的配置示例,展示如何优雅地管理工作和个人两种身份。

主配置文件 ~/.gitconfig

[core]
    autocrlf = input
[commit]
    gpgsign = true
[gpg]
    format = ssh

[includeIf "hasconfig:remote.*.url:work:<name>/**"]
    path = /home/nite/.ssh/work.gitconfig

[includeIf "hasconfig:remote.*.url:personal:<name>/**"]
    path = /home/nite/.ssh/personal.gitconfig

这里的 work:<name>personal:<name> 是使用了 Git 的 URL 别名功能。

工作配置 ~/.gitconfig-work

[user]
    name = "Your Work Name"
    email = "work@example.com"
    signingKey = "~/.ssh/id_work.pub"

[commit]
    gpgsign = true

[core]
    sshCommand = "ssh -i ~/.ssh/id_work -o IdentitiesOnly=yes"

这个配置文件会在你操作公司 GitLab 仓库时自动生效,包含:

  • 工作邮箱
  • 工作用的 SSH 签名密钥
  • 强制 GPG 签名
  • 指定使用工作专用的 SSH 密钥进行认证

个人配置 ~/.gitconfig-personal

[user]
    name = "Your Personal Name"
    email = "personal@example.com"
    signingKey = "~/.ssh/id_personal.pub"

[commit]
    gpgsign = false

[core]
    sshCommand = "ssh -i ~/.ssh/id_personal -o IdentitiesOnly=yes"

个人项目配置类似,但使用:

  • 个人邮箱
  • 个人 SSH 签名密钥
  • 可以选择不强制签名
  • 使用个人 SSH 密钥进行认证

hasconfig vs gitdir:该用哪个?

两种方式各有优劣:

特性 gitdir hasconfig:remote.*.url
判断依据 文件系统路径 远程仓库 URL
最低 Git 版本 2.13+ (2017) 2.36+ (2022)
评估速度 非常快 稍慢(需要扫描)
灵活性 需要组织目录结构 位置无关
适用场景 项目有固定目录 项目分散在各处

推荐策略

如果你习惯将工作项目和个人项目放在不同的目录下,使用 gitdir 最简单:

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

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

如果你的项目分散在各处,或者需要根据不同的 Git 托管平台区分身份,使用 hasconfig:remote.*.url 更灵活:

[includeIf "hasconfig:remote.*.url:https://gitlab.company.com/*/**"]
    path = ~/.gitconfig-work

[includeIf "hasconfig:remote.*.url:https://github.com/personal/**"]
    path = ~/.gitconfig-personal

你也可以两者结合使用,兼顾简单性和灵活性。

常见问题

问题 1:配置没有生效

检查以下几点:

  • 路径末尾是否有斜杠(~/work/ 而不是 ~/work
  • 配置文件路径是否正确(使用绝对路径更保险)
  • Git 版本是否足够新(git --version
  • includeIf 是否放在了配置文件的末尾

问题 2:hasconfig 模式不匹配

# 检查远程 URL
git remote -v

# 检查 URL 格式
git config --get remote.origin.url

确保你的模式能正确匹配远程 URL 的格式。

问题 3:多个配置冲突

Git 配置的优先级从高到低:

  1. 命令行参数(git -c user.email=xxx@xxx.com
  2. 本地仓库配置(.git/config
  3. includeIf 包含的配置
  4. 全局配置(~/.gitconfig
  5. 系统配置(/etc/gitconfig

后读取的配置会覆盖先读取的配置。

参考资料