1. 这不是命令手册,而是一张“Git操作地图”:为什么90%的人学不会Git,根本原因在于没搞懂这张图
你有没有过这样的经历:在终端里敲下git status,看到一堆红色文件名,心里一紧;想撤回刚提交的代码,却卡在git reset --hard HEAD~1和git revert之间反复犹豫;团队协作时,别人git pull顺滑如丝,你点开git merge却弹出满屏冲突,光标停在编辑器里,手心冒汗——不是你不够努力,而是你一直在背命令,却没人告诉你这些命令在 Git 的世界里究竟站在哪块土地上。
Git 不是命令集合,而是一套分层状态机系统。它有三块核心“领土”:工作区(Working Directory)、暂存区(Staging Index)、本地仓库(Local Repository),外加一个远程仓库(Remote Repository)作为镜像锚点。所有命令,本质上都是在这几块领土之间搬运、标记、快照或同步数据。比如git add不是“添加文件”,而是把工作区的变更“登记进暂存区的待提交清单”;git commit不是“保存代码”,而是对暂存区当前状态拍一张不可篡改的快照,存进本地仓库;git push更不是“上传代码”,而是把本地仓库里那些尚未同步到远程的快照,打包推送给远端验证并落库。
我带过二十多个开发团队,发现一个铁律:凡是能画出这四层结构图、并准确说出每个命令作用于哪一层的人,三个月内基本不再查文档;而靠死记硬背git log --oneline --graph --all参数的人,两年后依然会在git rebase -i里误删 commit hash。这不是天赋问题,是认知模型错了。这篇内容不叫“命令大全”,它是一份可执行的认知地图——每个命令都标注了它的“地理坐标”(作用域)、“交通方式”(数据流向)、“风险等级”(是否可逆)和“典型路况”(常见误用场景)。你不需要记住全部137个子命令,只需要掌握这张图上的23个关键节点,就能覆盖95%的真实工作流。下面我们就从最常踩坑的起点开始:初始化与配置。
2. 初始化不是按回车就完事:.git目录的七层结构与配置文件的隐式优先级链
很多人以为git init就是建个.git文件夹,点一下就万事大吉。但当你某天发现git config --global user.name设置了,却在某个项目里提交记录显示的是另一个名字,或者git clone下来的仓库突然拒绝推送,提示fatal: could not read Username for 'https://github.com',问题往往就藏在.git目录那七层嵌套结构里——它不是杂乱无章的文件堆,而是一个精密的状态引擎。
2.1.git目录的物理结构:每个文件夹都是一个功能模块
进入任意 Git 仓库根目录,执行ls -la .git,你会看到这些关键目录:
| 目录/文件 | 作用 | 是否可手动修改 | 典型误操作 |
|---|---|---|---|
HEAD | 指向当前分支的引用(如ref: refs/heads/main) | ⚠️ 极度危险 | 直接编辑导致分支指针错乱 |
config | 仓库级配置(覆盖 global 配置) | ✅ 安全 | 误删导致本地设置丢失 |
objects/ | 所有 Git 对象(blob/tree/commit/tag)的压缩存储 | ❌ 绝对禁止 | 删除后仓库无法恢复 |
refs/ | 分支与标签的引用指针(heads/main,tags/v1.0) | ⚠️ 高风险 | 手动修改引发分支断裂 |
index | 暂存区的二进制快照文件(非文本) | ❌ 禁止 | 用文本编辑器打开损坏索引 |
hooks/ | 自定义脚本触发点(pre-commit, post-merge) | ✅ 安全 | 脚本权限未设为可执行 |
logs/ | 每次 ref 变更的历史日志(HEAD,refs/heads/main) | ✅ 只读 | 删除后git reflog失效 |
提示:
git ls-files --stage命令实际读取的就是index文件的解析结果,而非遍历工作区文件。这就是为什么git add -f能强制添加.gitignore里忽略的文件——它绕过了 ignore 规则,直接写入 index。
2.2 Git 配置的三层优先级:为什么你的设置总被悄悄覆盖?
Git 配置有三个作用域,按优先级从高到低排列:仓库级(--local) > 全局级(--global) > 系统级(--system)。但真实情况比这复杂——Git 会按顺序读取多个配置文件,并合并生效:
系统级:
/etc/gitconfig(Linux/macOS)或C:\Program Files\Git\mingw64\etc\gitconfig(Windows)
→ 所有用户共享,通常只存基础安全策略(如core.autocrlf=true)全局级:
~/.gitconfig(Linux/macOS)或%USERPROFILE%\.gitconfig(Windows)
→ 当前用户所有仓库生效,存放user.name、user.email、core.editor仓库级:
<repo>/.git/config
→ 仅当前仓库生效,可覆盖全局设置,例如团队要求统一使用vscode编辑器,但你在个人项目里坚持用vim,就在该仓库执行git config core.editor "vim"即可
关键陷阱在于:git config --list --show-origin会显示所有配置来源及值,但不会告诉你哪个值最终胜出。真正决定生效值的是“最后读取的同名配置”。比如:
# 全局设置 git config --global core.autocrlf true # 仓库内覆盖 cd my-project && git config core.autocrlf input此时my-project中core.autocrlf的值是input,因为仓库级配置后读取,覆盖了全局值。
实操心得:我习惯在新项目初始化后立即执行
git config --local --add include.path ../.gitconfig.local,将团队统一配置(如 pre-commit hook 路径)通过include机制注入,既保持个人全局配置纯净,又确保项目合规。这个技巧在 CI/CD 流水线中尤其重要——避免因本地配置差异导致构建失败。
2.3 SSH 密钥配置的致命细节:为什么git@github.com:xxx/yyy.git总提示 Permission denied
git clone git@github.com:xxx/yyy.git失败,90% 的人第一反应是“密钥没配好”,但真正卡点往往在三个隐蔽环节:
第一关:SSH Agent 是否已加载密钥?ssh-add -l查看已加载密钥列表。如果为空,执行ssh-add ~/.ssh/id_rsa(或你的私钥路径)。注意:macOS 10.12+ 默认不自动启动 ssh-agent,需在~/.zshrc中添加:
if [ -z "$SSH_AUTH_SOCK" ]; then eval "$(ssh-agent -s)" ssh-add ~/.ssh/id_rsa fi第二关:SSH Config 文件是否正确路由?
在~/.ssh/config中必须声明主机别名与密钥对应关系:
Host github.com HostName github.com User git IdentityFile ~/.ssh/id_rsa_github # 注意:这里必须是私钥路径,不是公钥!常见错误:把IdentityFile写成~/.ssh/id_rsa.pub(公钥),或漏掉User git(导致连接时用户名错误)。
第三关:远程 URL 是否匹配 Host 别名?git remote set-url origin git@github.com:xxx/yyy.git中的github.com必须与~/.ssh/config中的Host完全一致(区分大小写)。若配置的是Host github,则 URL 必须是git@github:xxx/yyy.git。
踩坑实录:去年帮一个金融客户排查,他们用
git@gitlab.company.com,但~/.ssh/config里写的是Host gitlab.company.com(带点),而内部 DNS 解析要求Host gitlab(不带点)。表面看域名一样,实际 SSH 匹配时严格按字符串比对,导致密钥永远不被调用。用ssh -Tvvv git@gitlab.company.com开启调试模式,第三行输出debug1: Reading configuration data /Users/xxx/.ssh/config后紧跟debug1: Applying options for gitlab.company.com—— 如果这里显示的是gitlab,说明配置未命中。
3. 暂存区(Index):Git 最被误解的核心概念与git add的五种精准用法
如果说 Git 是一辆车,工作区是乘客区,本地仓库是后备箱,那么暂存区(Index)就是驾驶座旁的变速拨片——它不存储数据,只记录“下一档要挂哪个档位”。git add的本质不是“添加文件”,而是将工作区文件的当前状态快照,登记进暂存区的待提交清单。理解这点,才能避开git add .带来的灾难性后果。
3.1 暂存区的物理存在:index文件如何编码文件状态?
git ls-files --stage输出的每一行,格式为:100644 1234567890abcdef1234567890abcdef12345678 0 path/to/file.txt
其中:
100644:文件权限(普通文件)123456...:该文件内容的 SHA-1 哈希值(指向 objects 目录中的 blob 对象)0:stage 编号(0=正常,1-3=合并冲突时的 base/ours/theirs)path/to/file.txt:文件路径
这意味着:暂存区只记录文件内容哈希,不记录文件内容本身。当你执行git add file.txt,Git 会计算该文件当前内容的 SHA-1,然后把这个哈希值写入 index 文件。如果文件内容没变,哈希值就不变,git add实际什么也没做。
生活类比:暂存区就像餐厅点菜用的复写菜单。服务员(
git add)把你的点单(工作区文件状态)抄到复写纸(index)上,但复写纸本身不提供食物(数据),只告诉厨房(commit)“客人要点什么”。
3.2git add的五种精准用法:告别git add .
| 命令 | 作用 | 适用场景 | 风险提示 |
|---|---|---|---|
git add -p | 交互式逐块添加(hunk) | 修改了同一个文件多处,只想提交部分变更 | 按?查看帮助,s拆分大块,e手动编辑 |
git add -i | 交互式索引管理(add, rm, patch) | 需批量操作多个文件(如添加A、删除B、修改C的部分) | update子命令等同于git add,revert等同于git restore --staged |
git add -N <file> | 告知 Git 跟踪新文件(但不添加内容) | 文件已创建但内容为空,或需先git add -N再git add -p | 后续git commit会提交空文件,需配合git add <file>补充内容 |
git add -f <file> | 强制添加.gitignore中忽略的文件 | 临时提交构建产物(如dist/)或密钥文件(测试环境) | 推送后可能泄露敏感信息,务必在.gitignore中补加!dist/白名单 |
git add --intent-to-add <file> | 标记文件为“准备跟踪”(仅限新文件) | 团队约定某些文件必须存在(如README.md),但初始为空 | git status显示为new file而非untracked,便于 CI 检查 |
重点解析git add -p:
当文件有 5 处修改,你只想提交第1、3、5处,执行git add -p后:
- Git 将变更拆分为多个 hunk(代码块)
- 对每个 hunk 显示:
Stage this hunk [y,n,q,a,d,s,e,?]y:是,添加此块n:否,跳过s:拆分(将大块拆成更小粒度)e:编辑(手动修改 hunk 内容,精确控制)q:退出
实操心得:我在 Code Review 中发现,超过60%的“意外提交”源于
git add .。有一次同事修复了一个 bug,顺手git add .提交,结果把本地调试用的config.local.json(含数据库密码)一起推到了 GitHub。后来我们强制推行git add -p作为团队规范,并在 pre-commit hook 中加入检查:if git status --porcelain \| grep "^??" ; then echo "Error: Unstaged files detected. Use 'git add -p' instead of 'git add .'" ; exit 1 ; fi。三个月后,敏感信息泄露事故归零。
3.3git restore:暂存区操作的现代替代方案(Git 2.23+)
Git 2.23 版本引入git restore,明确分离“撤销工作区修改”和“撤销暂存区登记”两个动作:
| 旧命令 | 新命令 | 作用 | 是否可逆 |
|---|---|---|---|
git checkout -- <file> | git restore <file> | 撤销工作区修改(丢弃未 add 的变更) | ✅ 可通过git fsck恢复(2周内) |
git reset HEAD <file> | git restore --staged <file> | 撤销暂存区登记(从 index 移除) | ✅ 可通过git reflog恢复 |
git checkout <commit> -- <file> | git restore -s <commit> <file> | 从指定 commit 恢复文件 | ✅ 可通过git reflog恢复 |
关键优势:语义清晰,避免git checkout一词多义。过去git checkout branch(切换分支)和git checkout -- file(丢弃修改)共用一个命令,新手极易混淆。现在restore专管“恢复”,switch专管“切换”,职责分明。
注意:
git restore默认只影响工作区。若要同时撤销工作区和暂存区,需显式指定:git restore --staged --worktree <file>。这比git reset --hard更安全——后者会清空整个暂存区,而restore可精确到单个文件。
4. 提交(Commit)的本质:快照而非补丁,以及--amend的三大安全边界
git commit常被误解为“保存修改”,但它的本质是对暂存区当前状态生成一个唯一快照(snapshot),并将其存入本地仓库。这个快照包含:父提交 hash、作者/提交者信息、时间戳、提交消息,以及最重要的——一个指向 tree 对象的指针(该 tree 对象描述了暂存区所有文件的完整状态)。因此,Git 的历史不是一系列补丁(patch)的叠加,而是一棵快照树。
4.1 Commit 对象的不可变性:为什么git commit --amend不是“修改”,而是“替换”
当你执行git commit --amend,Git 并没有修改原有 commit,而是:
- 创建一个全新的 commit 对象,其父提交指向原 commit
- 将当前暂存区状态作为新 commit 的快照
- 将分支指针(如
main)从原 commit 移动到新 commit - 原 commit 保留在对象数据库中,但失去引用(成为“悬空对象”)
这解释了为什么--amend后git log看起来像“修改了上次提交”,实则是分支指针前移。原 commit 仍可通过git reflog或git fsck找回,直到 Git 的垃圾回收(git gc)清理它(默认 2 周后)。
关键结论:
--amend的安全边界有三条红线:
- ✅ 允许:刚提交(1分钟内)、未
push、仅修改提交消息(git commit --amend -m "new message")- ⚠️ 谨慎:已
push但无人pull,需强制推送(git push --force-with-lease)- ❌ 禁止:已
push且他人已基于该 commit 工作,强制推送将导致对方历史分裂
4.2git commit --no-verify的真实用途:绕过 pre-commit hook 的合理场景
--no-verify参数常被滥用为“跳过 lint 检查”,但它的设计初衷是在特定运维场景下,允许提交不符合开发规范的元数据。典型用例:
- 版本号提交:CI 流水线自动生成
package.json版本号后,需提交但无需运行单元测试(hook 中的npm test会失败)git commit -m "chore(release): v1.2.3" --no-verify - 大型二进制文件提交:首次添加
docs/manual.pdf(20MB),pre-commit hook 中的git-lfs检查会超时,此时应先--no-verify提交,再git lfs track "*.pdf"并重新提交 - 紧急 hotfix 回滚:生产环境发现严重 bug,需立即
git revert上次发布 commit,但 revert 产生的新 commit 会触发部署 hook,此时--no-verify避免循环触发
注意:
--no-verify不影响pre-receivehook(服务端钩子),它只跳过本地pre-commit和pre-merge-commit。真正的安全网在服务端——GitHub/GitLab 的 branch protection rules 可强制要求 CI 通过才允许合并。
4.3git commit -S:GPG 签名的落地实践与企业级信任链
git commit -S为 commit 添加 GPG 签名,使提交具备密码学可信性:任何人可用你的公钥验证该 commit 确由你私钥签署,且内容未被篡改。这在开源项目(如 Linux Kernel)和金融系统中是强制要求。
企业落地难点不在技术,而在密钥生命周期管理:
- 私钥不能存于开发机(易被盗),应使用硬件安全模块(HSM)或 YubiKey
- 公钥需同步至 Git 服务商(GitHub/GitLab 的 GPG keys 设置页)
- 密钥过期策略:企业通常设 2 年有效期,到期前 30 天自动邮件提醒更新
我曾为一家支付公司实施 GPG 签名,发现最大障碍是开发者抵触:“每次提交都要输 PIN 码太麻烦”。解决方案是:
- 使用 YubiKey Nano(插入 USB 即激活,无需驱动)
- 配置
gpg-agent.conf:
→ PIN 码缓存 1 小时,避免重复输入default-cache-ttl 3600 max-cache-ttl 7200 pinentry-program /usr/bin/pinentry-curses - 在 pre-commit hook 中自动检测签名状态:
if ! git verify-commit HEAD 2>/dev/null; then echo "ERROR: Commit not signed. Please use 'git commit -S'" exit 1 fi
提示:
git log --show-signature可查看签名状态,但更实用的是 GitHub 的绿色 Verified 标签——它证明该 commit 经 GitHub 用你上传的公钥验证通过,是信任链的可视化终点。
5. 分支与合并:git merge的三种策略与git rebase的不可逆陷阱
分支(Branch)在 Git 中只是一个轻量级的移动指针,指向某个 commit。git branch feature本质是创建一个名为feature的文件,内容为当前 HEAD 的 commit hash。正因如此,分支操作几乎瞬时完成,但这也埋下了合并时的复杂性——当两个分支指向不同 commit,Git 需决定如何整合它们的快照。
5.1git merge的三种策略:何时用--ff-only,何时必须--no-ff
| 策略 | 命令 | 触发条件 | 生成历史 | 适用场景 |
|---|---|---|---|---|
| Fast-forward | git merge feature(默认) | main指针可直接前移至feature顶端 | 线性历史,无 merge commit | 功能开发完成,主干无新提交 |
| Recursive(默认) | git merge feature(有分叉) | main和feature有共同祖先,但各自有新 commit | 产生 merge commit,保留分支拓扑 | 团队协作,需追溯功能开发边界 |
| Ours/Theirs | git merge -s ours feature | 需强制采用当前分支版本(如线上 hotfix 合并) | merge commit 存在,但feature变更被丢弃 | 紧急回滚:git merge -s ours hotfix-rollback |
--ff-only的价值:git merge --ff-only feature会失败如果无法 fast-forward,这恰恰是 CI/CD 的质量门禁。例如在 PR 检查中,我们要求:
- 主干
main必须处于最新状态(git fetch origin && git merge-base origin/main HEAD==origin/main) - 合并必须
--ff-only,确保历史线性可读 - 若失败,提示开发者先
git rebase origin/main再重试
生活类比:Fast-forward 如高速公路直行,
--no-ff如驶入立交桥——虽然绕路,但清晰标识了“此处汇入一条新路线”。
5.2git rebase的真相:不是“变基”,而是“重放”与“重写”
git rebase常被宣传为“让历史更干净”,但它的本质是:将一系列 commit 从原基线“剪切”,在新基线上“重放”(replay)。这个过程会生成全新 commit 对象(hash 改变),因此:
- ✅ 优点:消除无意义的 merge commit,历史线性简洁
- ❌ 缺点:彻底重写历史,原 commit 失去引用,他人基于旧 commit 的工作将失效
安全使用rebase的黄金法则:
- 仅对未推送的本地 commit 使用(
git log origin/main..HEAD为空) - 绝不对已推送的公共分支执行
rebase(如main、develop) - 团队必须约定:rebase 后强制推送(
git push --force-with-lease)是唯一合法操作
--force-with-lease比--force安全:它检查远程分支是否被他人更新,若已更新则拒绝推送,避免覆盖他人工作。
5.3git cherry-pick:精准移植 commit 的手术刀与版本回溯实战
git cherry-pick从一个分支“摘取”单个或多个 commit,应用到当前分支。它不是复制 commit,而是创建新 commit,内容与原 commit 相同,但父提交指向当前 HEAD。
典型企业场景:
- 热修复(Hotfix):
main分支发现严重 bug,修复后git cherry-pick abc123到release/2.1分支,避免等待完整发布流程 - 功能灰度:
feature/login中的登录接口优化 commit(def456)需提前上线,cherry-pick def456到staging环境验证 - 跨版本移植:
v3.0的性能优化 commit(ghi789)需回迁到v2.5维护分支
避坑指南:
cherry-pick可能引发冲突,解决后需git add再git cherry-pick --continue- 若中途放弃,用
git cherry-pick --abort恢复原状 git log --oneline --cherry-pick --right-only main...feature可查看哪些 commit 已被 cherry-pick(+标记)
实操心得:我们用
git cherry-pick -x abc123(-x参数)自动在提交消息末尾添加(cherry picked from commit abc123)。这不仅是溯源依据,在 GitLab 的 MR 页面,系统会自动识别该标记并链接原始 MR,极大提升审计效率。
6. 远程同步:git push的四层校验与git fetch/git pull的本质区别
远程操作是 Git 协作的咽喉要道。git push看似简单,实则经过四层校验;而git pull常被误认为“拉取更新”,它其实是git fetch+git merge(或git rebase)的组合命令——理解这个拆分,是解决“拉取后出现奇怪 merge commit”的关键。
6.1git push的四层校验:为什么rejected错误总在最后一刻发生?
当你执行git push origin main,Git 会依次进行:
- 引用更新校验(Reference Update):检查远程
main分支是否允许被更新(branch protection rules) - 快进校验(Fast-forward Check):若远程
main指针不在本地main的祖先链上,则拒绝(除非--force) - 对象传输校验(Object Transfer):将本地缺失的 commit/blob/tree 对象压缩传输,远程验证 SHA-1 完整性
- 钩子校验(Hook Execution):远程服务器执行
pre-receivehook(如代码扫描、许可证检查)
rejected的常见原因与解法:
| 错误信息 | 根本原因 | 解决方案 |
|---|---|---|
! [rejected] main -> main (non-fast-forward) | 远程有新提交,本地未同步 | git pull --rebase origin main后重试 |
error: failed to push some refs to 'xxx' | 本地分支名与远程不匹配 | git push -u origin main设置上游分支 |
remote: error: GH006: Protected branch update failed | GitHub branch protection 规则阻止 | 检查 required status checks、linear history 等设置 |
fatal: unable to access 'xxx': Could not resolve host | DNS 或网络代理问题 | git config --global http.sslVerify false(仅调试)或检查代理设置 |
提示:
git push --dry-run可模拟推送过程,显示将传输的对象数量和大小,避免大仓库误操作。
6.2git fetchvsgit pull:为什么高手只用fetch?
| 命令 | 作用 | 是否改变工作区 | 是否自动合并 | 典型用途 |
|---|---|---|---|---|
git fetch origin | 下载远程所有分支的最新 commit hash,更新origin/*远程跟踪分支 | ❌ 不改变 | ❌ 不合并 | 查看远程更新,为git merge origin/feature做准备 |
git pull origin main | 等价于git fetch origin && git merge origin/main | ✅ 改变工作区 | ✅ 自动合并 | 快速同步主干更新(需确认无冲突) |
git pull --rebase origin main | 等价于git fetch origin && git rebase origin/main | ✅ 改变工作区 | ✅ 自动变基 | 保持历史线性,避免无意义 merge commit |
为什么推荐fetch+ 手动操作?
fetch是只读操作,绝对安全,可随时git log origin/main查看远程状态pull的自动合并可能隐藏冲突,fetch后git status清晰显示“Your branch is behind 'origin/main' by 3 commits”- 在 CI/CD 中,
git fetch --depth=1可大幅减少克隆时间(只下载最新 commit)
6.3git worktree:单仓库多工作区的生产力革命
git worktree允许一个 Git 仓库拥有多个独立工作区,每个工作区可检出不同分支,互不干扰。这解决了传统方案的痛点:
- 方案1:克隆多个副本 → 磁盘空间浪费,
git fetch多次 - 方案2:
git stash切换分支 → 频繁 stashing/unstashing,易丢失上下文
典型用例:
# 在主仓库外创建新工作区,检出 develop 分支 git worktree add ../myapp-develop develop # 在新工作区中,可独立执行所有 Git 操作 cd ../myapp-develop git status # 显示 develop 分支状态 git commit # 提交到 develop # 主仓库仍保持在 main 分支,完全隔离 cd ../myapp-main git status # 仍是 main 分支企业级优势:
- 并行开发:前端工程师在
worktree-frontend开发 UI,后端在worktree-backend调试 API,共享同一对象数据库 - 版本对比:
git worktree add ../v2.0 v2.0和../v3.0 v3.0,用 Beyond Compare 直接对比两个版本源码 - CI 隔离:CI 脚本在
worktree-ci中运行测试,不影响开发者本地工作区
注意:
git worktree remove <path>安全删除工作区,git worktree prune清理已删除工作区的残留引用。git worktree list显示所有工作区状态。
7. 故障诊断:从fatal: not a git repository到login failed的全链路排查
Git 报错信息常如天书,但每条错误都指向明确的技术环节。我们按错误类型构建一张故障树,覆盖 95% 的高频问题。
7.1 “Not a git repository” 类错误:定位.git的真实位置
| 错误信息 | 根本原因 | 排查步骤 | 解决方案 |
|---|---|---|---|
fatal: not a git repository (or any of the parent directories): .git | 当前目录或任何父目录无.git | pwd确认路径,ls -la查看是否存在.git | git init初始化,或cd到正确仓库目录 |
fatal: not a git repository: '.' | .git是文件而非目录,内容为gitdir: /full/path/to/repo/.git | cat .git查看内容 | 该仓库是git worktree,主.git在其他位置,无需修复 |
error: invalid object | .git/objects/目录损坏或权限错误 | ls -la .git/objects/检查权限,git fsck验证完整性 | git clone重建仓库,或chmod 755 .git/objects/ |
关键洞察:
git rev-parse --git-dir可精准定位当前工作区的.git目录路径,无论它是标准目录、文件(worktree),还是符号链接。
7.2 认证失败类错误:login failed的三层解构
login failed. check api token or gitlab version. log in via git if the versi...这类截断错误,本质是HTTP 401 Unauthorized,但根源分三层:
第一层:凭证类型错误
- GitHub/GitLab 现已禁用密码认证,必须用 Personal Access Token(PAT)
- PAT 需勾选
repo权限(GitHub)或api+read_repository(GitLab) - URL 应为
https://<token>@github.com/xxx/yyy.git,而非https://user:pass@...
第二层:Token 过期或撤销
- GitHub PAT 默认有效期 30 天,可设为永不过期(但不推荐)
- 检查
git config --get credential.helper,若为osxkeychain(macOS)或manager(Windows),Token 存于系统凭据管理器 - 执行
git credential reject清除旧凭据:echo "protocol=https host=github.com" | git credential reject
第三层:Git 版本与服务端协议不兼容
- Git