别再把 Git 代理设成全局了------一份更干净的 .gitconfig
十年开发踩坑记。这是我见过最频繁、最隐蔽、又最容易一劳永逸解决掉的一类 Git 问题。
一个再熟悉不过的场景
刚入职一家公司,你兴冲冲给自己的 Mac 装好了代理工具(Clash、V2Ray、Surge,随意),然后复制粘贴一套"网上到处都能搜到"的命令:
bash
git config --global http.proxy http://127.0.0.1:7897
git config --global https.proxy http://127.0.0.1:7897
GitHub 秒下、brew install 顺畅、go get 也欢快。
过了两天,你开始拉公司内网的 GitLab:
vbnet
fatal: unable to access 'https://git.internal.company.com/...':
Failed to connect to 127.0.0.1 port 7897: Connection refused
或者你在某个没开代理的机器上复用这份配置,再或者代理挂了一次,于是所有 Git 操作------包括拉 Gitee、拉公司内网、拉国内镜像------全数倒下。
这锅 Git 不背。是全局代理这个选择从一开始就错了。
全局代理的原罪
Git 不懂你的网络拓扑。你跟它说 --global http.proxy,它就老老实实把每一个 HTTP/HTTPS 请求都塞进代理。它不会帮你判断:
- 这是不是公司内网域名?
- 这是不是 Gitee、Coding、华为 CodeArts 这类国内服务?
- 代理进程还活着吗?
境外源走代理顺畅,境内源绕一圈境外反而慢、甚至直接走不通。本来只想修一个"拉 GitHub 慢"的问题,结果把十条路都堵上了。
这是典型的"为了解决一个问题,引入了更多问题"。
正确姿势:按域名配代理
Git 其实原生支持"只给特定 URL 配代理"。语法长这样:
ini
[http "https://github.com"]
proxy = http://127.0.0.1:7897
匹配规则是 URL 前缀 。只要请求的 URL 以 https://github.com 打头,就走这个代理;其他域名一律直连。精确、可预测、可关闭。
一个常见误会 :网上很多教程会让你同时写
[http "..."]和[https "..."]两段,但 Git 的配置里根本没有https这个 section ------叫http的这一组,同时管 HTTP 和 HTTPS 两种协议的请求。多写的[https "..."]Git 完全不读,纯属以讹传讹。想验证也简单:GIT_TRACE_CURL=1 git ls-remote https://github.com/xxx/xxx跑一下,日志里能看到它用的就是http.proxy。
一份我在用的 .gitconfig
把"需要代理的域名"一个个列清楚,比写 --global 省心多了:
ini
[user]
name = your-name
email = you@company.com
# ========== 需要走代理的 GitHub 家族 ==========
# 主站:clone / push / fetch
[http "https://github.com"]
proxy = http://127.0.0.1:7897
# Raw 文件:很多脚本、CI 会直接 curl 这个地址
[http "https://raw.githubusercontent.com"]
proxy = http://127.0.0.1:7897
# Gist
[http "https://gist.github.com"]
proxy = http://127.0.0.1:7897
# API:gh CLI、各类机器人、自动化脚本都走它
[http "https://api.github.com"]
proxy = http://127.0.0.1:7897
# 源码打包下载:git archive、npm install github:user/repo 会用
[http "https://codeload.github.com"]
proxy = http://127.0.0.1:7897
# Release 资源 CDN:下载二进制、release 附件时的真实宿主
[http "https://objects.githubusercontent.com"]
proxy = http://127.0.0.1:7897
前四组大家都知道,后两组 codeload 和 objects.githubusercontent.com 经常被漏------但它们恰恰是"为什么我 npm install 某个 GitHub 包失败"、"为什么我下 release 二进制卡住"的元凶。把它们也加上,GitHub 相关的操作就真的齐活了。
Gitee、公司内部 GitLab、国内镜像仓库完全不在这份名单里------它们走直连,各得其所。
进阶 1:按目录切换身份(以及任何 Git 配置)
公司项目用公司邮箱,开源项目用个人邮箱,提错邮箱是开源世界的经典社死现场。includeIf 能把两套身份物理隔离------更关键的是,能按目录切换的不只是邮箱:SSH key、GPG 签名、甚至代理配置本身,都可以。
基础:按邮箱分身
ini
# ~/.gitconfig
[user]
name = your-name
email = you@company.com
# 注意位置:includeIf 必须写在默认值**之后**,覆盖才会生效
[includeIf "gitdir:~/workspace/github/"]
path = ~/.gitconfig-github
ini
# ~/.gitconfig-github
[user]
email = you@personal.com
只要仓库落在 ~/workspace/github/ 下,user.email 自动切换。不用再一个个仓库敲 git config user.email ...。
顺手把 SSH key 也按目录切了
一台机器同时用公司 GitHub 和个人 GitHub,SSH key 冲突是高频痛点。用 core.sshCommand 一行搞定:
ini
# ~/.gitconfig-github
[user]
email = you@personal.com
[core]
sshCommand = ssh -i ~/.ssh/id_ed25519_personal -o IdentitiesOnly=yes
IdentitiesOnly=yes 是关键------它告诉 SSH 只用这个 key,不要把 ssh-agent 里所有 key 轮着试。不加这一句,GitHub 会按"第一个能认的 key 属于哪个账号"来认人,经常触发账号错乱或限流。
一份多身份的完整骨架
ini
# ~/.gitconfig ------ 默认:公司身份
[user]
name = your-name
email = you@company.com
# (前面的域名代理配置省略)
# ~/workspace/github/ 下:个人 GitHub + 个人 SSH key
[includeIf "gitdir:~/workspace/github/"]
path = ~/.gitconfig-github
# ~/workspace/oss/ 下:开源贡献,开启 GPG 签名
[includeIf "gitdir:~/workspace/oss/"]
path = ~/.gitconfig-oss
每一种"身份"都放在独立文件里,主 ~/.gitconfig 只做分派。改动一个身份不会误伤另一个,这是塞一堆 [user] 段到单文件里做不到的。
四个真实踩过的坑
坑 1:路径必须以 / 结尾
ini
[includeIf "gitdir:~/workspace/github"] # ❌ 只匹配字面路径本身
[includeIf "gitdir:~/workspace/github/"] # ✅ 匹配该目录及其所有子孙仓库
坑 2:includeIf 要写在默认值之后
Git 按从上到下的顺序读配置,后出现的覆盖先出现的。includeIf 放在默认 [user] 段之前,下面的默认值又会反手把它盖掉------覆盖了个寂寞。
坑 3:默认大小写敏感
gitdir: 区分大小写。如果你的路径有时 Workspace 有时 workspace(macOS 上特别容易),改用 gitdir/i: 切换成大小写无关版。
坑 4:别用相对路径
ini
[includeIf "gitdir:./projects/"] # ❌ 不生效
[includeIf "gitdir:~/workspace/"] # ✅
验证究竟生效了哪份配置
进到一个预期要切换身份的仓库,跑:
bash
cd ~/workspace/github/some-repo
git config --get user.email
# 期望:you@personal.com
git config --list --show-origin | grep user.email
# 能看到 .gitconfig-github 被 include 进来,以及最终生效值来自哪里
--show-origin 是多层 include 场景下的调试神器------它告诉你每一条配置分别来自哪个文件,解 include 问题不用再靠猜。
进阶 2:SSH 协议 gitconfig 管不到
很多人卡在这里:.gitconfig 里明明配了代理,为什么 git clone git@github.com:xxx/xxx.git 还是连不上?
答案是:SSH 协议完全绕过 http.proxy 设置 ,它归 SSH 客户端管,要在 ~/.ssh/config 里配:
sshconfig
# 方案 A:HTTP 代理(Clash、V2Ray 最常见)
Host github.com
HostName ssh.github.com
User git
Port 443
ProxyCommand nc -X connect -x 127.0.0.1:7897 %h %p
# 方案 B:SOCKS5 代理
Host github.com
HostName github.com
User git
ProxyCommand nc -X 5 -x 127.0.0.1:7891 %h %p
注意方案 A 里的 HostName ssh.github.com + Port 443------这是 GitHub 官方提供的备用 SSH 入口,走 443 端口。很多公司网络会封掉 22,走这条路能救命。
验证配置真的生效了
方法一:查询某个 URL 实际匹配到的代理配置:
bash
git config --get-urlmatch http https://github.com
# 输出:http.proxy http://127.0.0.1:7897
git config --get-urlmatch http https://gitee.com
# 空输出,说明没走代理,符合预期
方法二:让 Git 把所有网络活动打印出来:
bash
GIT_CURL_VERBOSE=1 GIT_TRACE=1 git clone https://github.com/torvalds/linux
看日志里有没有 Connected to 127.0.0.1 (127.0.0.1) port 7897,有就说明代理生效。
方法三:看实际发出的请求:
bash
curl -v --proxy http://127.0.0.1:7897 https://github.com
应急技巧:一次性开/关代理
一次性给某条命令加代理:
bash
git -c http.proxy=http://127.0.0.1:7897 \
-c https.proxy=http://127.0.0.1:7897 \
clone https://github.com/xxx/xxx
一次性禁用代理(用来临时绕开老旧的全局代理配置):
bash
git -c http.proxy= -c https.proxy= clone https://gitee.com/xxx/xxx
永久取消全局代理(写给曾经踩过坑、现在想搬家的你):
bash
git config --global --unset http.proxy
git config --global --unset https.proxy
一张表总结
| 场景 | 正确做法 |
|---|---|
| 所有 Git 流量都走代理 | 别用 --global http.proxy |
| 只让 GitHub 走代理 | [http "https://github.com"] 精准匹配 |
SSH 协议(git@github.com:xxx)走代理 |
~/.ssh/config 里 ProxyCommand |
| 不同目录用不同身份 | includeIf "gitdir:..." |
| 临时开代理 | git -c http.proxy=http://... clone ... |
| 临时关代理 | git -c http.proxy= clone ... |
| 调试代理是否生效 | GIT_CURL_VERBOSE=1 git clone ... |
写在最后
Git 的配置哲学是一句很朴素的话:能精确就别粗糙。
代理这件事尤其如此------把它收束到最小必要范围,你的 git pull 会稳得多,也不用再跟同事解释"为什么我的 Git 时好时坏"。
如果你之前一直在用全局代理,花十分钟把 .gitconfig 翻修一下,这是我能想到的、ROI 最高的本地开发环境优化之一。
配置试了有用、或者遇到了新的坑,欢迎留言告诉我。