Git 常见问题
Git 访问 GitHub 速度过慢
现象:git clone、git push 速度极低,只有十几 KB/s,经常超时失败。
根因:不一定是 github.com 本身解析失败。常见情况是 CDN 域名(如 github.global.ssl.fastly.net)解析到海外节点、访问差,或本机 DNS 结果不稳定。硬编码进 hosts 的 IP 会过期,写入前要重新查询。
方案 1:修改本地 hosts 映射
原理:浏览器、Git 访问域名时,优先读本机 hosts 获取 IP;没有记录才向 DNS 查询。可以手动写入域名与 IP 的映射,绕过公共 DNS。
- 用 DNS 查询网站(例如 ipaddress.com)或站长工具,查询就近 IP:
github.global.ssl.fastly.net、github.com。 - 修改 hosts:Windows 为
C:\Windows\System32\drivers\etc\hosts(需管理员权限);macOS、Linux 为/etc/hosts。在文件末尾追加记录。 - 刷新本机 DNS 缓存:Windows 用
ipconfig /flushdns;macOS 用sudo dscacheutil -flushcache。
# IP 只是示例,以你查询到的为准
199.232.69.194 github.global.ssl.fastly.net
140.82.113.4 github.com
备注:改对之后,速度有机会从约 10 KiB/s 提升到 100 KiB/s 以上。IP 失效就再查、再改。
方案 2:使用 GitHub 镜像地址
# 原始地址
git clone https://github.com/xxx/xxx.git
曾经流行的 https://github.com.cnpmjs.org/xxx/xxx.git 已于 2022 年停用(维护方因钓鱼风险认定而关闭)。第三方加速域名更换频繁,也有仿冒站点,不要把过期地址写进脚本。更稳妥的是方案 1、方案 4,或改用 SSH(见下文)。
方案 3:调整 HTTP 传输缓存 http.postBuffer
适用场景:大文件推送、大仓库 clone,HTTPS 推送卡在 chunked 传输或被代理拒掉。
# 缓冲区 10MB,单位字节:10 * 1024 * 1024
git config --global http.postBuffer 10485760
原理:http.postBuffer 默认约 1 MiB。POST 超过这个大小时,Git 会改用 HTTP/1.1 的 Transfer-Encoding: chunked。部分代理、网关不支持 chunked,推送就会失败。把缓冲区调大,尽量一次发完、少走 chunked。不要设得过大,会明显增加内存占用。
方案 4:Git 配置代理
代理分全局和仅针对 GitHub;这些配置只作用于 HTTP、HTTPS,SSH 不受影响。
本地代理端口以你的软件为准,下面 1080 只是示例。连本机 HTTP 代理时,http.proxy 和 https.proxy 一般都写 http://127.0.0.1:端口(用 CONNECT 隧道),不要写成 https://127.0.0.1。SOCKS 代理用 socks5://127.0.0.1:端口。
- 全局代理(不推荐,国内 Git 仓库也会走代理,往往更慢)
git config --global http.proxy http://127.0.0.1:1080
git config --global https.proxy http://127.0.0.1:1080
- 推荐:只给 GitHub 配代理,不影响其他仓库
git config --global http.https://github.com.proxy http://127.0.0.1:1080
git config --global https.https://github.com.proxy http://127.0.0.1:1080
- 清除代理配置
git config --global --unset http.proxy
git config --global --unset https.proxy
git config --global --unset http.https://github.com.proxy
git config --global --unset https.https://github.com.proxy
Git Bash 中文乱码问题
乱码主要来自三处:Git 配置、Bash 终端编码、系统文件编码。
git status 中文文件名显示八进制转义
现象:git status 里中文文件名变成 \345\255\246 这类转义。
原因:core.quotepath 为 true 时,Git 对非 ASCII 路径做 C 风格转义。
git config --global core.quotepath false
也可以直接改 .gitconfig:
[core]
quotepath = false
git log 日志中文乱码
修改全局配置:
git config --global gui.encoding utf-8
git config --global i18n.commitencoding utf-8
git config --global i18n.logoutputencoding utf-8
Git Bash 终端是 GBK 时(见下文把 Character set 改成 GBK),把最后一项改成 gbk,并与终端编码一致。
对应配置文件片段:
[gui]
encoding = utf-8
[i18n]
commitencoding = utf-8
logoutputencoding = utf-8
[svn]
pathnameencoding = utf-8
修改 Git Bash 的 profile:%GIT_HOME%\etc\profile,末尾追加:
export LESSCHARSET=utf-8
vim、vi 查看文件中文乱码
编辑 %GIT_HOME%\share\vim\vimrc,文件末尾追加:
set fileencodings=utf-8,ucs-bom,cp936,big5
set fileencoding=utf-8
set termencoding=gbk
termencoding=gbk 面向 GBK 终端;终端是 UTF-8 时改成 utf-8。
终端输入中文乱码
编辑 %GIT_HOME%\etc\inputrc,末尾增加:
set output-meta on
set convert-meta off
ls 命令输出中文文件名乱码
不要改 etc/git-completion.bash(升级 Git 会被覆盖,也不是放别名的地方)。写到 Git Bash 的 etc/bash.bashrc 或用户 ~/.bashrc:
alias ls='ls --show-control-chars --color=auto'
ipconfig、systeminfo 等 Windows 命令输出乱码
原因:这些工具输出 GBK,Git Bash 窗口默认按 UTF-8 解,对不上。
解决:Git Bash 窗口右键 → Options → Text,Locale 设为 zh_CN,Character set 设为 GBK。
注意:改成 GBK 后,UTF-8 文件名又会乱。只能按当前任务切换编码,没有一劳永逸的方案。
GitHub SSH 密钥配置
SSH(Secure Shell)是加密远程协议。配好密钥后,用 SSH 地址访问 GitHub 不必反复输入账号密码。只对 git@github.com:... 这类地址生效,和 HTTPS 互不通用。
检查本机是否已有 SSH 密钥
# 查看 ~/.ssh 下的密钥。常见公钥:id_ed25519.pub、id_rsa.pub
ls -al ~/.ssh
生成 SSH 密钥对
GitHub 推荐 Ed25519;RSA 仍可用,长度用 4096。
# 推荐
ssh-keygen -t ed25519 -C "your_email@example.com"
# RSA
ssh-keygen -t rsa -b 4096 -C "your_email@example.com"
参数说明:
-t:算法,ed25519或rsa-b:位数,RSA 用 4096;Ed25519 不用这个参数-C:注释,一般填注册邮箱-f:自定义保存路径
提示保存路径时直接回车用默认。然后可以设密钥密码(可选;设了之后每次解锁密钥要输入密码,更安全)。
将私钥交给 ssh-agent 管理
ssh-agent 保管已解锁的私钥,避免每次操作都输入密钥密码。
# Windows Git Bash 启动 agent
eval $(ssh-agent -s)
# 把私钥加进 agent,文件名换成你的(Ed25519 默认是 id_ed25519)
ssh-add ~/.ssh/id_ed25519
ssh-add ~/.ssh/id_rsa
公钥配置到 GitHub 账号
- 复制公钥(只复制
.pub,不要把没有.pub的私钥发出去)
# macOS
pbcopy < ~/.ssh/id_ed25519.pub
# Windows Git Bash
clip < ~/.ssh/id_ed25519.pub
也可以直接打开 ~/.ssh/id_ed25519.pub 复制。RSA 则把文件名换成 id_rsa.pub。
- GitHub 网页:头像 →
Settings→SSH and GPG keys→New SSH key - Title 填备注,把完整公钥粘贴进去,保存。
测试 SSH 连通 GitHub
ssh -T git@github.com
首次连接会提示核对主机指纹,类似:
The authenticity of host 'github.com (IP ADDRESS)' can't be established.
ED25519 key fingerprint is SHA256:+DiY3wvvV6TuJJhbpZisF/zLDA0zPMSvHdkr4UvCOqU.
Are you sure you want to continue connecting (yes/no/[fingerprint])?
核对须与 GitHub 官方公布的指纹 一致,再输入 yes。GitHub 在 2023 年更换过 RSA 主机密钥,网上旧教程里的 nThbg6kX... 已经作废。当前官方指纹:
- RSA:
SHA256:uNiVztksCsDhcc0u9e8BujQXVUpKZIDTMczCvj3tD2s - ECDSA:
SHA256:p2QAMXNIC1TJYWeIOttrVc98/R1BUFWu3/LiyKgUfQM - Ed25519:
SHA256:+DiY3wvvV6TuJJhbpZisF/zLDA0zPMSvHdkr4UvCOqU
连接成功会看到:
Hi xxx! You've successfully authenticated, but GitHub does not provide shell access.
修改已有私钥的密码
不必重新生成密钥对:
ssh-keygen -p
按提示输入私钥路径、旧密码、新密码。
给 SSH 密钥设密码:本机密钥泄露后,没有密码也用不了。ssh-agent 会缓存已解锁的密钥,日常开发不用反复输入。
merge 与 rebase 的区别
二者都是把一个分支的改动并到另一个分支,但实现、历史形态和风险不同。
git merge
语法:当前在 A 分支,执行 git merge B。
- 做三方合并:A 的 HEAD、B 的 HEAD、二者共同祖先,对比两边相对祖先的差异。
- 同一位置两边都改了,就要手解冲突。
- 不能快进时,会生成新的合并提交,两边原有提交都还在,历史呈网状。若 B 已经是 A 的祖先,默认快进,只移动指针,不产生合并提交。
优点:
- 原始提交都在,好追溯、好回滚。
- 不改已有 commit 的哈希。
缺点:频繁合并会让历史分叉多,日志里出现大量合并提交。
git rebase(变基)
不要在已经推送、多人共用的分支上 rebase。 只用于本地还没推、或只有你在用的分支(若已推过,需要 --force-with-lease 且所有人都知情)。
语法:当前在 A 分支,执行 git rebase B。
- 把 A 上独有的提交摘下来,接到 B 的最新提交后面,逐个重放。
- 冲突按每一个提交解,不是 merge 那样一次解完。
- 完成后这些提交会有新的哈希,历史变成一条线。
优点:历史干净、线性,适合整理自己的本地分支。
缺点:
- 重写历史、改变哈希;公共分支 rebase 再强推,别人基于旧哈希的工作会对不上。
- 冲突要逐次处理,量大时更烦。
- 原来的分叉交汇痕迹没了。
小结
- merge:保留历史,允许分叉,适合公共分支协作;
- rebase:重写历史,抹平分叉,适合本地私有分支整理。
git pull、git fetch、git remote update
git pull:取回远程更新并合并进当前分支。默认等价git fetch+git merge FETCH_HEAD;若配置了pull.rebase,则是 fetch + rebase。会改工作区,容易产生冲突。git fetch:下载远程分支的新提交,不合并。只更新远程跟踪引用(如origin/main),工作区不动。适合先看再合并。git remote update:更新已配置的全部远程的跟踪分支,不合并。多个 remote 时好用。
# 取 origin 上各分支的最新提交,不合并
git fetch
# 指定远程
git fetch origin
# 只取指定分支
git fetch origin main
# 把远程 main 写到本地新建(或更新)temp 分支,不切换
git fetch origin main:temp
git reset 与 git revert
两者都能撤销提交,但对历史的影响不同。
git reset
直接移动 HEAD(当前分支指针),指定位置之后的提交从 git log 里消失,git reflog 里暂时还能找到。
reset 改历史,只适合尚未推到远程的提交。已经在公共分支上的提交不要 reset 再 git push --force,会覆盖别人基于旧历史的工作。
git revert
不删旧提交,再生成一条反向提交,抵消目标提交的改动。整条历史还在。
适合已经推到公共分支的回滚,普通 git push 即可,不必强制推送。
例如提交链 ... → A → B,要去掉 B 的改动:
git reset A:历史变成...→A,B 丢掉;git revert B:历史变成...→A→B→C,C 的内容效果回到 A。
注意:revert 掉一次合并提交之后,再 merge 同一条源分支,那些被撤销的改动不会自动回来。
解决:把这条 revert 再 revert 一次,或在源分支上制造新提交(cherry-pick、squash 后再提交)。
Git 换行符问题
- Linux、macOS:
LF(\n) - Windows:
CRLF(\r\n)
跨平台协作时让 Git 做换行转换,避免满仓库「整文件都改了」其实只是换行符。
# Windows:检出成 CRLF,提交时转回 LF
git config --global core.autocrlf true
# Linux、macOS:提交时把 CRLF 收成 LF,检出不主动改成 CRLF
git config --global core.autocrlf input
参数:
true:检出 CRLF,提交 LF;Windows 常用input:只在提交时标准化为 LF;Linux、macOS 常用false:不转换,文件里是什么就是什么;跨平台仓库容易把换行差异提交上去
常见报错处理
报错 1:fatal: Could not read from remote repository.
fatal: Could not read from remote repository.
Please make sure you have the correct access rights
and the repository exists.
现象:SSH 访问 GitHub 失败,测试连接提示 22 端口超时。
ssh: connect to host github.com port 22: Connection timed out
根因:本机网络或防火墙拦了 SSH 默认 22 端口。
解决:编辑 ~/.ssh/config(没有就新建),让 GitHub 的 SSH 走 443:
Host github.com
HostName ssh.github.com
User git
Port 443
原理:GitHub 在 ssh.github.com:443 上提供 SSH,多数网络放行 443。
报错 2:packet_write_wait: Connection to xxx port 22: Broken pipe
remote: Enumerating objects: 283, done.
remote: Counting objects: 100% (283/283), done.
remote: Compressing objects: 100% (18/18), done.
packet_write_wait: Connection to 13.229.188.59 port 22: Broken pipe
fatal: the remote end hung up unexpectedly
fatal: early EOF
fatal: index-pack failed
现象:git push、git pull 大仓库时,SSH 中途断开。
根因:会话长时间看起来空闲,被防火墙或 NAT 掐掉。
解决:改本机 ~/.ssh/config,让客户端发保活包。ClientAliveCountMax 是 sshd 服务端配置,写在自己电脑的 ssh 客户端配置里无效。
Host *
# 每 60 秒发一次保活
ServerAliveInterval 60
# 连续 3 次无响应再断开
ServerAliveCountMax 3