外观
Git 系统学习文档
资料核对日期:2026-06-30
适合人群:前端、Node.js、全栈、前端架构师候选人
学习目标:从下载安装到项目协作,从基础命令到冲突处理、历史修复、团队规范和真实故障排查,形成可直接用于实际项目的 Git 能力。
1. Git 是什么
Git 是一个分布式版本控制系统,用来记录代码历史、多人协作、分支开发、回滚错误、追踪变更来源。
它解决的核心问题:
- 代码每天都在变,必须知道“谁在什么时候改了什么,为什么改”。
- 多人同时开发,必须把不同人的修改合并到同一个项目里。
- 线上出问题时,必须能定位引入问题的提交,并安全回退。
- 需求、Bug、实验功能需要并行推进,必须用分支隔离风险。
Git 和平台不是一回事:
| 名称 | 作用 | 常见使用 |
|---|---|---|
| Git | 本地版本控制工具 | git add、git commit、git merge |
| GitHub | 代码托管与协作平台 | Pull Request、Actions、Issues |
| GitLab | 企业常用代码平台 | Merge Request、CI/CD、权限管理 |
| Gitee | 国内常用代码托管平台 | 国内团队协作、镜像仓库 |
你可以没有 GitHub,但只要做现代开发,几乎离不开 Git。
2. 学习路线
建议按下面顺序学习:
- 安装 Git,配置身份、编辑器、默认分支、换行符。
- 理解工作区、暂存区、本地仓库、远程仓库。
- 熟练掌握
status、add、commit、log、diff。 - 学会分支:
switch、branch、merge、rebase。 - 学会远程协作:
clone、remote、fetch、pull、push。 - 掌握冲突解决、回滚、恢复误删、撤销提交。
- 学会团队规范:提交规范、分支策略、PR/MR、代码审查。
- 进阶掌握
stash、tag、cherry-pick、reflog、bisect、worktree。 - 能排查真实项目中的 Git 报错。
3. 下载与安装
3.1 Windows
官方下载入口:
- Git 官方下载页:https://git-scm.com/downloads
- Git for Windows:https://git-scm.com/download/win
推荐方式一:官网下载 Git for Windows 安装包。
安装时重点选项:
- Editor:如果你用 VS Code,可以选择 VS Code。
- Default branch name:建议选择
main。 - PATH:建议选择让 Git 可以在命令行中使用。
- HTTPS transport backend:一般选择 OpenSSL。
- Line endings:Windows 常见选择是 checkout 为 CRLF、commit 为 LF,也可以通过后续配置控制。
- Terminal emulator:一般默认即可。
推荐方式二:使用 winget。
bash
winget install --id Git.Git -e安装后检查:
bash
git --version3.2 macOS
方式一:触发 Xcode Command Line Tools。
bash
git --version如果系统未安装,会提示安装命令行工具。
方式二:使用 Homebrew。
bash
brew install git
git --version3.3 Linux
Ubuntu / Debian:
bash
sudo apt update
sudo apt install git
git --versionCentOS / RHEL / Fedora:
bash
sudo dnf install git
git --version4. 首次配置
安装 Git 后,先配置提交身份。这个身份会写入 commit 历史。
bash
git config --global user.name "Your Name"
git config --global user.email "you@example.com"查看配置:
bash
git config --global --list设置默认分支名:
bash
git config --global init.defaultBranch main设置编辑器,以 VS Code 为例:
bash
git config --global core.editor "code --wait"设置凭据缓存,减少反复输入账号密码:
bash
git config --global credential.helper manager配置换行符。
Windows 常用:
bash
git config --global core.autocrlf truemacOS / Linux 常用:
bash
git config --global core.autocrlf input说明:
- CRLF 是 Windows 常见换行。
- LF 是 Linux、macOS、服务端、CI 环境常见换行。
- 如果团队里有人用 Windows,有人用 macOS,建议通过
.gitattributes明确管理换行。
5. SSH、HTTPS 和访问令牌
5.1 HTTPS
HTTPS 地址类似:
bash
https://github.com/org/repo.git优点:
- 配置简单。
- 公司网络环境里通常更容易访问。
注意:
- GitHub 等平台通常不再支持用账号密码直接推送代码。
- 需要使用 Personal Access Token,也就是个人访问令牌。
适合场景:
- 新手快速 clone。
- CI/CD 中使用令牌访问私有仓库。
- 公司网络限制 SSH 端口时。
5.2 SSH
SSH 地址类似:
bash
git@github.com:org/repo.git生成密钥:
bash
ssh-keygen -t ed25519 -C "you@example.com"启动 ssh-agent 并添加私钥:
bash
ssh-add ~/.ssh/id_ed25519查看公钥:
bash
cat ~/.ssh/id_ed25519.pub把公钥添加到 GitHub / GitLab / Gitee 的 SSH keys 页面。
测试 GitHub SSH:
bash
ssh -T git@github.com常见错误:
text
Permission denied (publickey).排查顺序:
- 确认本机是否有密钥:
ls ~/.ssh。 - 确认公钥是否添加到代码平台。
- 确认 remote 地址是不是 SSH 地址:
git remote -v。 - 确认私钥是否加入 ssh-agent:
ssh-add -l。 - 如果有多个平台或多个账号,配置
~/.ssh/config。
多账号示例:
sshconfig
Host github-work
HostName github.com
User git
IdentityFile ~/.ssh/id_ed25519_work
Host github-personal
HostName github.com
User git
IdentityFile ~/.ssh/id_ed25519_personal对应 remote:
bash
git remote set-url origin git@github-work:company/project.git6. Git 的四个区域
Git 学不好,通常是因为没理解区域。
| 区域 | 英文 | 含义 |
|---|---|---|
| 工作区 | Working Tree | 你正在编辑的真实文件 |
| 暂存区 | Index / Staging Area | 准备进入下一次提交的变更 |
| 本地仓库 | Local Repository | .git 中保存的 commit 历史 |
| 远程仓库 | Remote Repository | GitHub、GitLab、Gitee 上的仓库 |
典型流转:
text
编辑文件 -> git add -> 暂存区 -> git commit -> 本地仓库 -> git push -> 远程仓库撤销也要看区域:
| 想做什么 | 命令 |
|---|---|
| 撤销工作区文件修改 | git restore <file> |
| 取消暂存,但保留文件修改 | git restore --staged <file> |
| 撤销上一次本地提交,保留变更到暂存区 | git reset --soft HEAD~1 |
| 撤销上一次本地提交,保留变更到工作区 | git reset --mixed HEAD~1 |
| 用一个新提交反向撤销历史提交 | git revert <commit> |
6.1 .git 目录里到底存了什么
Git 学不深,往往是因为没理解对象模型。.git 不是“一堆备份文件”,而是一个对象数据库(Object Database):
| 对象类型 | 存什么 | 类比 |
|---|---|---|
blob | 文件内容,不含文件名 | 一张快照里的某个文件 |
tree | 一个目录的快照:文件名、权限、指向的 blob | 文件夹 |
commit | 指向顶层 tree、作者、提交者、时间、信息、父提交 | 一次快照 + 元数据 |
tag(annotated) | 指向某个 commit,带标签名、说明、打标签者 | 版本书签 |
可以用命令直接验证:
bash
git cat-file -t HEAD # 查看 HEAD 指向的对象类型:commit
git cat-file -p HEAD # 打印 commit 对象内容,能看到 tree 和 parent
git cat-file -p HEAD^{tree} # 打印顶层 tree,能看到每个文件对应的 blob关键结论:
- 每个对象都按内容哈希命名(SHA-1,现在也支持 SHA-256 仓库)。内容相同只存一份,这就是 Git 体积小的原因之一。
- commit 不存“差异”,而是存指向 tree 快照的指针;差异是展示时计算出来的。
- 分支只是指向某个 commit 的可移动指针,所以创建分支几乎零成本。
HEAD是指向当前分支的指针,detached HEAD就是 HEAD 直接指向 commit 而不是分支。
⚠️ 理解了“分支 = 指针”“commit = 快照”,
reset、rebase、cherry-pick、reflog就都不再是魔法:它们只是在移动指针或复制快照。
6.2 三个核心引用:HEAD、index 与工作区
“四个区域”是使用者视角,底层其实是三个引用在互相比较:
text
HEAD(最近一次提交的快照) <-> index(暂存区快照) <-> 工作区(磁盘文件)| 命令 | 实际在比较/操作什么 |
|---|---|
git diff | 工作区 vs index |
git diff --staged | index vs HEAD |
git diff HEAD | 工作区 vs HEAD(两者合并的差异) |
git add | 把工作区文件内容写入 index |
git commit | 把 index 写成一个新的 commit 对象,并移动分支指针 |
git checkout/restore | 用 index 或 HEAD 的内容覆盖工作区 |
这也解释了常见现象:git add 之后再改文件,git commit 提交的是暂存那一刻的内容,而不是提交那一刻的文件。
7. 创建仓库
7.1 从零创建
bash
mkdir demo
cd demo
git init添加文件并提交:
bash
echo "# demo" > README.md
git add README.md
git commit -m "docs: add readme"绑定远程仓库:
bash
git remote add origin git@github.com:your-name/demo.git
git push -u origin main-u 的作用是建立本地分支和远程分支的 upstream 关系。之后可以直接:
bash
git push
git pull7.2 克隆已有项目
bash
git clone git@github.com:org/project.git
cd project
git remote -v克隆指定分支:
bash
git clone -b develop git@github.com:org/project.git浅克隆,适合 CI 或只需要最新历史:
bash
git clone --depth 1 git@github.com:org/project.git注意:浅克隆历史不完整,某些依赖完整历史的命令会受限,例如复杂的 bisect、版本生成、历史比较。
8. 基础命令
8.1 查看状态
bash
git status
git status -sbgit status -sb 输出更简洁,适合日常查看。
8.2 查看差异
查看工作区和暂存区之间的差异:
bash
git diff查看暂存区和上次提交之间的差异:
bash
git diff --staged查看某个提交:
bash
git show <commit>8.3 暂存与提交
暂存全部变更:
bash
git add .暂存指定文件:
bash
git add src/main.ts交互式暂存,适合一个文件里有多处修改但只想提交其中一部分:
bash
git add -p提交:
bash
git commit -m "feat: add login page"修改最近一次提交信息:
bash
git commit --amend注意:如果最近一次提交已经推送到共享分支,修改历史会影响别人。需要推送时用 --force-with-lease,并确认团队允许。
8.4 查看历史
bash
git log
git log --oneline
git log --oneline --graph --decorate --all查看某个文件历史:
bash
git log -- src/main.ts查看每一行最后是谁改的:
bash
git blame src/main.tsgit blame 不应该用于甩锅,而应该用于理解上下文:为什么这里要这样写,应该找谁确认。
8.5 跟踪重命名:git log --follow 和 -M
Git 不在元数据里记录“重命名”,而是在比较时通过内容相似度检测出来。所以:
bash
# 查看某个文件的历史,跨越它被重命名之前
git log --follow -- src/utils/request.ts
# 查看某次提交或两个提交间的差异时检测重命名/复制
git log -M --summary
git diff -M v1.0.0 v2.0.0注意:
--follow只对单文件历史有效,且依赖相似度检测,重命名同时大改内容时可能断开。git blame也有类似的-C选项,可以追踪从其他文件复制来的代码行。
8.6 git log 的高级过滤
排查问题时,git log 的过滤能力比翻历史高效得多:
bash
# 谁改过这个函数(按代码内容搜索,需要 -p 展开 diff)
git log -S "useToken" --oneline -- src/auth
# 谁修改过匹配正则的内容(区别于 -S 的“出现次数变化”)
git log -G "VITE_CDN" --oneline
# 只看某个作者、某段时间、某个路径
git log --author="Ada" --since="2 weeks ago" -- src/router
# 看合并提交 / 排除合并提交
git log --merges --oneline
git log --no-merges --oneline
# 查看两个分支的差异提交:在 feature 上但不在 main 上
git log main..feature/login --oneline
# 反向:在 main 上但不在 feature 上
git log feature/login..main --oneline-S 和 -G 的差别:
-S找“某个字符串出现次数发生变化”的提交,适合定位“这个 API 是什么时候引入/删除的”。-G找“diff 内容匹配正则”的提交,适合定位“哪次提交动过这行代码”。
9. 分支
分支是 Git 协作的核心。
查看分支:
bash
git branch
git branch -a创建并切换分支:
bash
git switch -c feature/login切换已有分支:
bash
git switch main删除本地分支:
bash
git branch -d feature/login强制删除本地分支:
bash
git branch -D feature/login删除远程分支:
bash
git push origin --delete feature/login9.1 merge
merge 会把一个分支的变更合并进当前分支。
bash
git switch main
git merge feature/login适合:
- 保留真实分支合并历史。
- 团队希望看到每个功能分支的合并节点。
- PR/MR 合并时使用 merge commit。
缺点:
- 历史可能出现较多合并提交。
9.2 rebase
rebase 会把当前分支的提交“挪到”另一个分支的最新提交之后。
bash
git switch feature/login
git fetch origin
git rebase origin/main适合:
- 让功能分支历史更线性。
- 提 PR/MR 前同步主分支。
- 整理本地提交历史。
重要原则:
- 可以 rebase 自己还没共享出去的分支。
- 不要随便 rebase 多人共同开发、已经被别人基于其继续开发的公共分支。
9.3 merge 和 rebase 怎么选
| 场景 | 推荐 |
|---|---|
| 本地功能分支同步主分支 | rebase origin/main |
| 合并 PR/MR 到主分支 | 按团队策略选择 merge、squash、rebase merge |
| 多人共用长期分支 | 谨慎 rebase,优先 merge |
| 想保留完整分支合并历史 | merge |
| 想让提交历史更直线 | rebase |
9.4 merge 的内部机制:三种方式
git merge 并不总是同一种行为。理解它有助于判断冲突来源:
bash
git merge feature/login| 场景 | 行为 | 说明 |
|---|---|---|
| Fast-forward | 当前分支没有新提交,直接把指针移到 feature/login | 不产生 merge commit,历史看起来像直线 |
| 三方合并(3-way) | 两边都有新提交,找到共同祖先(merge base),做三方比较 | 产生一个有两个父提交的 merge commit |
| Octopus | 同时合并多个分支 | 较少用,要求无冲突 |
bash
# 不允许 fast-forward,强制保留合并节点(团队常用,保留“这里发生过合并”)
git merge --no-ff feature/login
# 只合并,如果无法 fast-forward 就失败(适合脚本和 CI 检查)
git merge --ff-only feature/login
# 合并但不自动提交,先看一眼结果
git merge --no-commit feature/login查找两个分支的共同祖先(判断冲突范围时很有用):
bash
git merge-base main feature/login⚠️ 三方合并的冲突标记里,
HEAD一侧是当前分支内容,另一侧是被合并分支内容;git merge还可以配合git checkout --ours/--theirs快速选择一边,但理解两边意图后再手动合并通常更安全。
9.5 交互式 rebase:整理提交历史
rebase 不只是“同步主分支”,--interactive 是整理本地提交的核心工具:
bash
# 整理最近 5 个提交
git rebase -i HEAD~5打开的编辑器会列出每个提交及其动作:
text
pick a1b2c3d feat: add login form
pick d4e5f6a fix: typo
pick 7g8h9i0 feat: add login validation常用动作:
| 动作 | 含义 |
|---|---|
pick | 保留该提交 |
reword | 保留提交但修改提交信息 |
squash | 合并到上一个提交,保留信息并编辑 |
fixup | 合并到上一个提交,丢弃本条信息 |
drop | 删除该提交 |
edit | 停在该提交,允许修改内容后 --continue |
典型用法:把多个零碎的 wip、fix typo 提交 squash/fixup 成一个干净的提交,再推送。
注意:
- 交互式 rebase 会改写历史,只对自己还没共享(或团队允许改写)的分支使用。
- 执行前先建备份分支:
git branch backup。 - 中途出错随时可以
git rebase --abort回到起点。
10. 远程协作
查看远程仓库:
bash
git remote -v添加远程:
bash
git remote add origin git@github.com:org/project.git修改远程地址:
bash
git remote set-url origin git@github.com:org/new-project.git拉取远程信息,但不自动合并:
bash
git fetch origin拉取并合并当前分支对应的远程分支:
bash
git pullgit pull 本质上通常等于:
bash
git fetch
git merge如果配置了 rebase,则可能等于:
bash
git fetch
git rebase建议团队明确 pull 策略:
bash
git config --global pull.rebase false或:
bash
git config --global pull.rebase true只允许 fast-forward:
bash
git config --global pull.ff only推送当前分支并建立 upstream:
bash
git push -u origin feature/login10.1 upstream 跟踪关系与分支清理
-u(即 --set-upstream)建立的是本地分支与远程分支的跟踪关系。有了它:
git push、git pull不用再写参数。git status -sb会显示领先/落后远程多少个提交(例如[ahead 2, behind 1])。git branch -vv可以查看所有本地分支的跟踪情况:
bash
git branch -vv
# feature/login a1b2c3d [origin/feature/login: ahead 1] feat: add login维护远程分支列表的几个习惯:
bash
# fetch 时顺便删除远程已删除分支对应的本地跟踪引用
git fetch --prune
# 删除已经合并进当前分支的本地分支(清理用)
git branch --merged main
# 推送所有本地分支(谨慎使用,通常逐个推送更可控)
git push --all origin⚠️
git fetch只更新origin/*这类远程跟踪分支(存在.git/refs/remotes/下),不会碰你的本地分支;本地分支要更新,必须再走merge、rebase或pull。这是很多人“fetch 了为什么代码没变”的原因。
11. 常见团队工作流
11.1 Feature Branch
最常见,适合前端、Node、业务系统。
流程:
text
main / develop -> 创建 feature 分支 -> 开发 -> push -> PR/MR -> Code Review -> 合并命令示例:
bash
git switch main
git pull
git switch -c feature/user-permission
# 开发
git add .
git commit -m "feat(permission): add route guard"
git push -u origin feature/user-permission优点:
- 每个需求隔离。
- 方便 Code Review。
- 方便 CI 针对 PR/MR 跑测试。
11.2 Git Flow
常见分支:
| 分支 | 作用 |
|---|---|
main | 线上稳定版本 |
develop | 集成开发版本 |
feature/* | 功能开发 |
release/* | 发布准备 |
hotfix/* | 线上紧急修复 |
适合:
- 版本发布节奏较清晰的产品。
- 客户端、小程序、需要发版审批的项目。
缺点:
- 分支多,流程较重。
- 对快速迭代 Web 项目可能显得复杂。
11.3 Trunk-Based Development
主干开发强调短分支、小步提交、频繁集成。
适合:
- CI/CD 完备。
- 自动化测试较可靠。
- 团队有 feature flag 能力。
典型要求:
- 分支生命周期很短。
- 每次变更都比较小。
- 主分支必须随时可发布。
11.4 前端项目建议流程
中小型前端团队:
text
main -> feature/* -> PR -> CI -> review -> squash merge -> main多环境企业项目:
text
main 线上
release 预发
develop 测试
feature/* 需求
hotfix/* 紧急修复Monorepo 项目:
- 分支策略要更严格,因为一个仓库里包含多个应用或包。
- PR 描述必须说明影响范围。
- CI 最好基于 changed files 只跑受影响项目。
- 锁文件冲突要谨慎处理。
11.5 PR 的三种合并方式
GitHub / GitLab 平台合并 PR 时通常有三种策略,本质对应不同的 Git 操作:
| 方式 | 对应 Git 操作 | 结果 | 适合 |
|---|---|---|---|
| Merge commit | git merge --no-ff | 保留全部提交 + 一个合并节点 | 需要完整保留开发过程 |
| Squash merge | 把 PR 所有提交压成一个再提交 | 主分支每个 PR 只有一个干净提交 | 小团队、频繁提交、主分支要求干净 |
| Rebase merge | 把 PR 提交逐个变基到主分支再 fast-forward | 线性历史,无合并节点 | 追求线性且接受改写 |
Squash merge 的内部机制:
text
feature 分支:A - B - C
squash 后: 一个包含 A+B+C 全部变更的新提交,只有一个父提交注意:
- Squash 之后的新提交和原来的 A、B、C 不是同一个 commit(哈希不同),所以功能分支不能直接再
git merge进主分支,否则会出现重复变更。 - 用 squash 策略时,功能分支合并后应该删除,后续修改开新分支。
- 如果团队在 squash 后又想在功能分支上继续开发,建议先在本地把历史整理干净,或使用 merge commit 策略。
12. 提交信息规范
推荐使用 Conventional Commits 风格。
格式:
text
<type>(scope): <subject>常见 type:
| type | 含义 |
|---|---|
feat | 新功能 |
fix | 修复 Bug |
docs | 文档 |
style | 格式,不影响逻辑 |
refactor | 重构 |
perf | 性能优化 |
test | 测试 |
build | 构建系统 |
ci | CI 配置 |
chore | 杂项 |
revert | 回滚 |
示例:
bash
git commit -m "feat(auth): add login token refresh"
git commit -m "fix(upload): resume chunk upload after network retry"
git commit -m "build(vite): enable legacy plugin"团队收益:
- PR/MR 更容易看。
- 可以自动生成 changelog。
- 可以根据提交类型控制版本号。
git log --oneline更清晰。
13. .gitignore 和 .gitattributes
13.1 .gitignore
.gitignore 用来忽略不应该进入仓库的文件。
前端项目常见配置:
gitignore
node_modules/
dist/
build/
.turbo/
.vite/
.next/
.nuxt/
coverage/
*.log
.env
.env.local
.DS_StoreNode / Nest 项目常见配置:
gitignore
node_modules/
dist/
coverage/
.env
.env.*
!.env.example
npm-debug.log*
pnpm-debug.log*
yarn-debug.log*注意:
.env通常不应该提交。- 可以提交
.env.example,说明需要哪些环境变量。 - 如果文件已经被 Git 追踪,后面加入
.gitignore不会自动取消追踪。
取消已追踪文件:
bash
git rm --cached .env
git commit -m "chore: stop tracking env file"13.2 .gitattributes
.gitattributes 用于统一换行、文件属性、合并策略。
常见配置:
gitattributes
* text=auto
*.js text eol=lf
*.ts text eol=lf
*.tsx text eol=lf
*.vue text eol=lf
*.json text eol=lf
*.md text eol=lf
*.png binary
*.jpg binary
*.pdf binary锁文件可以保持文本合并:
gitattributes
package-lock.json text eol=lf
pnpm-lock.yaml text eol=lf
yarn.lock text eol=lf14. 标签与版本发布
标签常用于发布版本。
创建轻量标签:
bash
git tag v1.0.0创建带说明的标签:
bash
git tag -a v1.0.0 -m "release v1.0.0"推送标签:
bash
git push origin v1.0.0推送全部标签:
bash
git push origin --tags删除本地标签:
bash
git tag -d v1.0.0删除远程标签:
bash
git push origin --delete v1.0.0项目使用场景:
- Web 项目发布到生产环境后打 tag。
- 小程序每次审核版本打 tag。
- npm 包发布版本和 Git tag 对齐。
- 后端服务镜像 tag 和 Git tag 对齐。
15. 撤销、回滚与恢复
15.1 restore
撤销工作区文件修改:
bash
git restore src/main.ts取消暂存:
bash
git restore --staged src/main.ts15.2 reset
撤销最近一次提交,保留变更在暂存区:
bash
git reset --soft HEAD~1撤销最近一次提交,保留变更在工作区:
bash
git reset --mixed HEAD~1彻底回到某个提交:
bash
git reset --hard <commit>危险点:
--hard会丢弃工作区和暂存区变更。- 对已经推送到共享分支的历史使用
reset要非常谨慎。
15.3 revert
用新提交撤销旧提交:
bash
git revert <commit>适合:
- 线上回滚。
- 已经推送到共享分支的提交。
- 需要保留历史,不想改写历史。
15.4 reflog
reflog 记录本地 HEAD 和分支指针移动历史。误删分支、误 reset 后,常用它救命。
bash
git reflog找回某个提交:
bash
git reset --hard <reflog中的commit>找回误删分支:
bash
git reflog
git switch -c recovered-branch <commit>注意:
reflog主要是本地记录,不等同于远程永久备份。- 时间久了可能被清理。
15.5 revert 合并提交与选择决策表
普通 git revert <commit> 处理单父提交很直接,但 revert 一个 merge commit 时要指定保留哪一侧:
bash
# -m 1 表示保留第一个父提交(通常是主干)那一侧,撤销被合并分支带来的变更
git revert -m 1 <merge-commit>理解要点:
- merge commit 有两个父提交,
-m 1是“以第一个父提交为基准计算反向差异”。 - revert 一个 merge 后,如果以后想把该功能分支重新合并,需要先“revert 那个 revert”,否则 Git 认为那些变更已经合并过,不会再次带入。
三种撤销手段的选择决策表:
| 场景 | 推荐 | 原因 |
|---|---|---|
改动只在工作区,还没 git add | git restore <file> | 不触碰历史 |
| 已暂存,想取消暂存 | git restore --staged <file> | 保留修改 |
| 本地未推送的提交想重做 | git reset --soft/--mixed HEAD~1 | 历史未共享,安全 |
| 已推送到共享分支的提交要撤销 | git revert <commit> | 不改写历史,对协作者无影响 |
| 刚合并的功能整体有问题 | git revert -m 1 <merge-commit> | 保留历史且可追溯 |
| 误操作导致分支/提交“消失” | git reflog 找回 | 对象还在,只是没被引用 |
⚠️
reset --hard和git clean -fd是少数会真正“丢数据”的操作。执行前如果不确定,先git stash push -u或建备份分支,成本极低。
16. stash 临时保存
当你正在开发,但需要临时切换分支处理 Bug,可以用 stash。
保存当前修改:
bash
git stash push -m "wip: login page"查看 stash:
bash
git stash list恢复最近一次 stash,并删除该 stash:
bash
git stash pop恢复但保留 stash:
bash
git stash apply stash@{0}删除 stash:
bash
git stash drop stash@{0}保存未追踪文件:
bash
git stash push -u -m "wip: include new files"适合场景:
- 切分支前临时保存当前工作。
- 拉取远程代码前避免本地修改被覆盖。
- 需要把未完成工作转移到另一个分支。
17. cherry-pick、bisect、worktree
17.1 cherry-pick
把某个提交复制到当前分支。
bash
git cherry-pick <commit>适合:
- hotfix 已经提交到
main,需要同步到release。 - 某个功能分支里只有一两个提交需要提前合入。
遇到冲突:
bash
git status
# 解决冲突
git add .
git cherry-pick --continue放弃:
bash
git cherry-pick --abort17.2 bisect
二分查找哪个提交引入了 Bug。
bash
git bisect start
git bisect bad
git bisect good <known-good-commit>Git 会切到中间提交,你运行测试或手动验证。
如果当前提交有问题:
bash
git bisect bad如果当前提交没问题:
bash
git bisect good结束:
bash
git bisect reset适合:
- 项目很大,无法肉眼定位问题。
- 线上突然出现回归,需要找出引入问题的 commit。
17.3 worktree
同一个仓库同时检出多个工作目录。
bash
git worktree add ../project-hotfix hotfix/login-error适合:
- 当前分支有大量未完成工作,但要立刻修复线上问题。
- 同时对比两个分支的构建结果。
- Monorepo 中需要并行处理多个任务。
查看:
bash
git worktree list删除:
bash
git worktree remove ../project-hotfix18. 冲突处理
冲突并不代表出错,它只代表 Git 无法自动判断哪一边应该保留。
常见冲突来源:
- 两个人修改了同一个文件同一段代码。
- 一个人修改文件,另一个人删除文件。
- 一个分支重命名文件,另一个分支修改旧文件。
package-lock.json、pnpm-lock.yaml、yarn.lock同时变化。- 大型重构和业务需求并行开发。
18.1 merge 冲突解决流程
合并:
bash
git switch main
git merge feature/login如果出现冲突:
bash
git status文件里会出现冲突标记:
text
<<<<<<< HEAD
当前分支内容
=======
被合并分支内容
>>>>>>> feature/login处理步骤:
- 打开冲突文件。
- 理解两边代码的业务意图。
- 删除
<<<<<<<、=======、>>>>>>>标记。 - 保留正确代码,必要时手动合并两边逻辑。
- 运行测试、构建、lint。
- 标记已解决。
bash
git add src/auth.ts
git commit放弃本次 merge:
bash
git merge --abort18.2 rebase 冲突解决流程
bash
git switch feature/login
git fetch origin
git rebase origin/main出现冲突后:
bash
git status
# 修改冲突文件
git add .
git rebase --continue跳过当前提交:
bash
git rebase --skip放弃 rebase:
bash
git rebase --abort注意:
- rebase 可能对多个提交逐个处理冲突。
- 每次解决后都要
git rebase --continue。 - 如果你不确定,先
git branch backup-before-rebase建备份分支。
18.3 锁文件冲突
前端项目高频冲突文件:
package-lock.jsonpnpm-lock.yamlyarn.lock
不要简单地随便选一边,因为锁文件决定依赖版本。
推荐处理:
bash
# 先解决 package.json
# 再重新安装依赖,重新生成锁文件
pnpm install或:
bash
npm install然后:
bash
git add package.json pnpm-lock.yaml
git commit检查:
bash
pnpm test
pnpm build18.4 二进制文件冲突
图片、设计稿、PDF、压缩包等二进制文件不能像代码一样合并。
处理方式:
- 和协作者确认保留哪个版本。
- 使用
git checkout --ours <file>保留当前分支版本。 - 使用
git checkout --theirs <file>保留被合并分支版本。
示例:
bash
git checkout --ours public/logo.png
git add public/logo.png或:
bash
git checkout --theirs public/logo.png
git add public/logo.png19. 真实开发高频问题与解决方案
这一节按实际开发中常见报错和事故整理。
19.1 non-fast-forward
常见报错:
text
! [rejected] main -> main (non-fast-forward)原因:
- 远程分支有你本地没有的提交。
- 直接 push 会覆盖远程历史,所以 Git 拒绝。
解决:
bash
git fetch origin
git rebase origin/main
git push如果团队不用 rebase:
bash
git pull
git push不要第一反应就 git push -f。强推会改写远程历史,可能覆盖同事代码。
19.2 divergent branches
常见报错:
text
You have divergent branches and need to specify how to reconcile them.原因:
- 本地和远程都有新提交。
- Git 需要你明确 pull 时使用 merge、rebase,还是只允许 fast-forward。
配置方式:
bash
git config --global pull.rebase false或:
bash
git config --global pull.rebase true或:
bash
git config --global pull.ff only临时指定:
bash
git pull --rebase
git pull --no-rebase
git pull --ff-only19.3 local changes would be overwritten
常见报错:
text
Your local changes to the following files would be overwritten by checkout原因:
- 你本地有未提交修改。
- 切分支、pull、merge 会覆盖这些修改。
解决方式一:提交。
bash
git add .
git commit -m "wip: save current work"解决方式二:stash。
bash
git stash push -m "wip before switching branch"
git switch target-branch
git stash pop解决方式三:确认不要这些修改后丢弃。
bash
git restore .19.4 untracked files would be overwritten
常见报错:
text
The following untracked working tree files would be overwritten by checkout原因:
- 你本地有未被 Git 追踪的新文件。
- 目标分支里也有同路径文件。
解决:
bash
git status如果要保留:
bash
git add .
git commit -m "wip: keep untracked files"或:
bash
git stash push -u -m "save untracked files"如果确认不要:
bash
git clean -fd注意:git clean -fd 会删除未追踪文件和目录,执行前必须确认。
19.5 detached HEAD
常见状态:
text
HEAD detached at <commit>原因:
- 你检出了某个 commit 或 tag,而不是分支。
如果只是查看历史,直接切回分支:
bash
git switch main如果已经在 detached HEAD 上做了提交,要保留它:
bash
git switch -c fix/from-detached-head19.6 提交到了错误分支
场景:
- 本来应该在
feature/a开发,却提交到了main。
如果还没推送:
bash
git branch feature/a
git reset --soft HEAD~1
git switch feature/a
git add .
git commit -m "feat: correct branch commit"如果已经推送到共享分支:
- 不要自行强推。
- 和团队确认后用
git revert或开 PR 修正。
19.7 提交信息写错
最近一次提交未推送:
bash
git commit --amend -m "fix(auth): correct token refresh"已推送到个人分支:
bash
git commit --amend -m "fix(auth): correct token refresh"
git push --force-with-lease已推送到公共分支:
- 通常不改历史。
- 可以追加一个修正提交。
19.8 错误提交了敏感信息
例如:
.env- 数据库密码
- API key
- 私钥
立即处理:
- 先废弃泄露的密钥,在平台上重新生成。
- 从当前版本中删除文件。
bash
git rm --cached .env
echo ".env" >> .gitignore
git add .gitignore
git commit -m "chore: remove env from tracking"如果敏感信息已经进入历史,需要重写历史。可以使用 git filter-repo 或平台推荐工具。完成后还要强制推送,并通知所有协作者重新同步。
重点:删除当前文件不等于删除历史里的秘密,所以密钥必须轮换。
19.9 fatal: not a git repository
常见报错:
text
fatal: not a git repository原因:
- 当前目录不是 Git 仓库。
.git目录被删除。- 你在错误目录运行命令。
排查:
bash
pwd
ls -a
git rev-parse --show-toplevel解决:
- 切换到正确项目目录。
- 如果是新项目,执行
git init。 - 如果
.git被误删,通常需要重新 clone,或从备份恢复。
19.10 repository not found
常见原因:
- 仓库地址写错。
- 没有权限。
- 私有仓库没有授权。
- SSH key 或 PAT 配置错误。
排查:
bash
git remote -v
ssh -T git@github.comHTTPS 场景:
- 检查 Personal Access Token 是否过期。
- 检查 token 权限是否包含仓库访问范围。
19.11 Permission denied publickey
原因:
- 没有 SSH key。
- 公钥没加到平台。
- ssh-agent 没加载私钥。
- 多账号时用了错误私钥。
排查:
bash
ssh -T git@github.com
ssh-add -l如果没有加载:
bash
ssh-add ~/.ssh/id_ed2551919.12 refusing to merge unrelated histories
常见报错:
text
fatal: refusing to merge unrelated histories原因:
- 两个仓库没有共同提交历史。
- 常见于本地
git init后,又强行关联一个远程已有仓库。
如果确认要合并:
bash
git pull origin main --allow-unrelated-histories更推荐:
- 新项目先 clone 远程仓库,再把代码复制进去。
- 避免本地和远程分别初始化。
19.13 pathspec did not match
常见报错:
text
error: pathspec 'xxx' did not match any file(s) known to git原因:
- 分支名、tag 名、文件路径写错。
- 本地还没有 fetch 到远程分支。
解决:
bash
git branch -a
git fetch origin
git switch -c feature/foo origin/feature/foo19.14 误删分支
如果只是删了本地分支:
bash
git reflog
git switch -c recovered <commit>如果远程分支还在:
bash
git fetch origin
git switch -c feature/foo origin/feature/foo如果远程分支也删了:
- 先找本地 reflog。
- 再查同事本地仓库是否还有该分支。
- 查 GitHub / GitLab 的 PR/MR 页面是否能恢复。
19.15 锁文件频繁冲突
原因:
- 多个需求同时安装或升级依赖。
- npm、pnpm、yarn 混用。
- Node 版本不一致导致锁文件格式变化。
解决策略:
- 团队统一包管理器,例如只用 pnpm。
- 提交
packageManager字段。 - 启用 Corepack。
- 统一 Node 版本,例如
.nvmrc或.node-version。 - 冲突时先解决
package.json,再重新 install。
示例 package.json:
json
{
"packageManager": "pnpm@9.0.0",
"engines": {
"node": ">=20"
}
}19.16 大文件 push 被拒绝
常见原因:
- 提交了视频、PSD、压缩包、模型文件。
- 平台限制单文件大小。
解决:
- 普通构建产物不要提交,加入
.gitignore。 - 必须版本化的大文件使用 Git LFS。
bash
git lfs install
git lfs track "*.psd"
git add .gitattributes
git add design.psd
git commit -m "chore: track psd with lfs"如果大文件已经进入历史,需要从历史中清理,再重新推送。
19.17 子模块没有初始化
常见现象:
- clone 后子目录为空。
- 构建时报依赖源码不存在。
解决:
bash
git submodule update --init --recursive克隆时同时拉子模块:
bash
git clone --recurse-submodules git@github.com:org/project.git更新子模块:
bash
git submodule update --remote --merge19.18 代理、证书、网络问题
常见现象:
- clone 超时。
- SSL certificate problem。
- 公司网络不能访问 SSH 端口。
排查:
bash
git remote -v
git config --global --get http.proxy
git config --global --get https.proxy设置代理示例:
bash
git config --global http.proxy http://127.0.0.1:7890
git config --global https.proxy http://127.0.0.1:7890取消代理:
bash
git config --global --unset http.proxy
git config --global --unset https.proxy企业内网证书问题应优先安装公司根证书,不建议长期关闭 SSL 校验。
20. 项目中的 Git 使用示例
20.1 Vue 2 / Vue 3 项目
典型文件:
text
src/
public/
package.json
pnpm-lock.yaml
vite.config.ts 或 vue.config.js
.env.development
.env.production建议:
.env.local不提交。.env.example提交。dist/不提交。pnpm-lock.yaml或package-lock.json必须提交,保证依赖一致。
开发流程:
bash
git switch main
git pull --rebase
git switch -c feature/vue-user-list
pnpm install
pnpm dev
git add src package.json pnpm-lock.yaml
git commit -m "feat(user): add user list page"
git push -u origin feature/vue-user-list提交前检查:
bash
pnpm lint
pnpm test
pnpm build20.2 React 项目
React 项目常见冲突点:
- 组件文件同名修改。
- 路由表同时修改。
- 状态管理 store 同时修改。
- 锁文件冲突。
建议:
- 功能分支尽量小。
- 公共组件改动单独 PR。
- 大范围格式化不要和业务需求混在一个提交里。
示例:
bash
git switch -c feature/react-dashboard-filter
git add src/pages src/components
git commit -m "feat(dashboard): add filter panel"20.3 Nuxt 项目
Nuxt 常见生成目录:
gitignore
.nuxt/
.output/
dist/
node_modules/
.env建议:
- 不提交
.nuxt、.output。 - 环境变量通过
.env.example描述。 - SSR 项目发布时用 tag 对齐部署版本。
发布:
bash
git tag -a v1.3.0 -m "release nuxt app v1.3.0"
git push origin v1.3.020.4 小程序项目
小程序项目常见特点:
- 版本需要提交审核。
project.config.json可能包含成员本地配置。- 构建产物、上传密钥不应该随意提交。
建议:
- 保留必要的项目配置。
- 本地私有配置拆成 local 文件并忽略。
- 每次提审前打 tag。
示例:
bash
git switch -c release/mp-v2.4.0
git commit -m "release(mp): prepare v2.4.0"
git tag -a mp-v2.4.0 -m "wechat mini program v2.4.0"
git push origin release/mp-v2.4.0
git push origin mp-v2.4.020.5 Node / Express / Nest 项目
常见忽略:
gitignore
node_modules/
dist/
coverage/
.env
logs/
*.log建议:
- 数据库迁移文件要提交。
.env不提交,.env.example提交。dist/通常不提交,由 CI 构建。- 生产发布用 tag 或 commit SHA 追踪镜像。
Nest 项目示例:
bash
git switch -c feature/nest-auth-guard
git add src/modules/auth src/common/guards
git commit -m "feat(auth): add jwt guard"
git push -u origin feature/nest-auth-guard回滚线上问题:
bash
git revert <bad-commit>
git push然后让 CI/CD 重新部署。
20.6 Monorepo 项目
Monorepo 目录可能类似:
text
apps/admin
apps/web
packages/ui
packages/utils
package.json
pnpm-lock.yaml
pnpm-workspace.yaml建议:
- PR 描述影响范围,例如
apps/admin、packages/ui。 - 修改公共包时说明影响的应用。
- CI 按变更范围运行任务。
- 使用 changeset 管理包版本。
示例:
bash
git switch -c feat/ui-button-loading
git add packages/ui apps/admin
git commit -m "feat(ui): add button loading state"21. Git Hooks 与团队规范
Git Hooks 是 Git 在特定时机执行脚本的机制。
常见 hooks:
| Hook | 时机 | 用途 |
|---|---|---|
pre-commit | 提交前 | lint、format、单测 |
commit-msg | 提交信息生成后 | 校验提交信息规范 |
pre-push | push 前 | 测试、类型检查 |
前端项目常见组合:
- Husky:管理 Git hooks。
- lint-staged:只检查本次暂存的文件。
- commitlint:检查 commit message。
示例:
bash
npm install -D husky lint-staged @commitlint/cli @commitlint/config-conventional
npx husky initpackage.json 示例:
json
{
"lint-staged": {
"*.{js,ts,tsx,vue}": [
"eslint --fix",
"prettier --write"
],
"*.{json,md,css,scss}": [
"prettier --write"
]
}
}pre-commit 示例:
bash
npx lint-stagedcommit-msg 示例:
bash
npx commitlint --edit "$1"注意:
- Hooks 是本地兜底,不应该替代 CI。
- 团队强约束应放在 CI、分支保护、代码审查里。
- 大型项目 pre-commit 不要跑太慢,否则开发者会绕过它。
22. 分支保护与 Code Review
成熟团队通常会保护主分支。
常见规则:
- 禁止直接 push 到
main。 - 必须通过 PR/MR。
- 至少一名或两名 reviewer approve。
- CI 通过后才能合并。
- 必须解决所有 conversation。
- 禁止 force push 到主分支。
PR/MR 描述建议:
markdown
## 背景
为什么做这次修改。
## 变更
- 增加用户权限路由守卫
- 调整菜单渲染逻辑
## 影响范围
- apps/admin
- packages/auth
## 验证
- pnpm lint
- pnpm test
- pnpm build
## 风险
- 老用户权限数据可能需要兼容Code Review 看什么:
- 业务逻辑是否正确。
- 是否影响现有功能。
- 是否有测试。
- 是否有安全风险。
- 是否有性能问题。
- 是否有隐藏的破坏性变更。
23. CI/CD 中的 Git
CI/CD 通常基于 Git 事件触发:
- push 到分支。
- 提交 PR/MR。
- 创建 tag。
- 合并到主分支。
常见流水线:
text
push feature -> lint/test/build -> PR review
merge main -> build image -> deploy staging
tag v1.0.0 -> deploy production前端项目:
- PR 阶段跑 lint、typecheck、unit test、build。
- 合并主分支后部署测试环境。
- tag 后部署生产。
Node / Nest 项目:
- PR 阶段跑 lint、test、e2e。
- 合并主分支构建 Docker 镜像。
- 镜像 tag 使用 commit SHA 或 Git tag。
24. 安全与最佳实践
24.1 不要提交敏感信息
不要提交:
.env- 私钥
- token
- 数据库密码
- 生产配置
- 云服务凭证
应该提交:
.env.example- 配置字段说明
- 本地开发启动文档
24.2 小提交、清晰提交
好的提交:
- 一个提交只解决一个问题。
- 提交信息说明目的。
- 可以单独 revert。
不好的提交:
updatefix bug- 一个提交里同时格式化全项目、改业务、升级依赖。
24.3 不要在公共分支乱改历史
高危命令:
bash
git reset --hard
git push --force
git rebase不是不能用,而是要知道影响范围。
如果必须强推个人分支:
bash
git push --force-with-lease--force-with-lease 会在远程分支被别人更新时拒绝覆盖,比 --force 安全。
24.4 合并前先自测
前端常见:
bash
pnpm lint
pnpm test
pnpm buildNode / Nest 常见:
bash
pnpm lint
pnpm test
pnpm test:e2e
pnpm build25. 常用命令速查
bash
# 初始化与克隆
git init
git clone <url>
# 状态与差异
git status
git diff
git diff --staged
# 暂存与提交
git add .
git add -p
git commit -m "feat: message"
git commit --amend
# 历史
git log --oneline --graph --decorate --all
git show <commit>
git blame <file>
# 分支
git branch
git switch <branch>
git switch -c <branch>
git branch -d <branch>
# 远程
git remote -v
git fetch origin
git pull
git pull --rebase
git push -u origin <branch>
# 合并与变基
git merge <branch>
git rebase <branch>
git merge --abort
git rebase --continue
git rebase --abort
# 撤销与恢复
git restore <file>
git restore --staged <file>
git reset --soft HEAD~1
git reset --mixed HEAD~1
git revert <commit>
git reflog
# 临时保存
git stash push -m "message"
git stash list
git stash pop
# 标签
git tag
git tag -a v1.0.0 -m "release v1.0.0"
git push origin v1.0.026. 练习路线
阶段一:个人基础
练习目标:
- 创建仓库。
- 提交三次。
- 查看历史。
- 撤销工作区修改。
- 修改最近一次提交信息。
练习命令:
bash
git init
git status
git add .
git commit -m "docs: init"
git log --oneline
git restore <file>
git commit --amend阶段二:分支协作
练习目标:
- 创建两个 feature 分支。
- 模拟同一文件修改。
- 合并并解决冲突。
练习命令:
bash
git switch -c feature/a
git switch main
git switch -c feature/b
git merge feature/a阶段三:远程协作
练习目标:
- 创建远程仓库。
- push 分支。
- 创建 PR/MR。
- 模拟 non-fast-forward。
- 解决后重新 push。
阶段四:故障恢复
练习目标:
- 误删分支后用
reflog恢复。 - 错误提交后用
reset和revert分别处理。 - 用
stash保存未完成工作。 - 用
bisect定位 Bug。
27. 面试与架构师视角
前端架构师不只会用 Git 命令,还要能设计团队协作方式。
需要掌握的问题:
- 为什么不能直接 push 到主分支?
- merge 和 rebase 的区别是什么?
- squash merge 适合什么场景?
- 线上 Bug 应该用 reset 还是 revert?
- 如何处理锁文件冲突?
- 如何避免提交敏感信息?
- 如何设计 Monorepo 的分支和发布策略?
- 如何通过 Git tag、commit SHA 对齐发布版本?
- 如何用 hooks 和 CI 保证提交质量?
- 如何处理误删分支、误 reset、误强推?
架构师级答案应该包含:
- 命令本身。
- 为什么这样做。
- 对团队协作和历史记录的影响。
- 是否会改写历史。
- 是否影响 CI/CD。
- 是否有回滚方案。
28. 官方资料
建议优先阅读官方资料:
- Git 官方下载:https://git-scm.com/downloads
- Git 官方文档入口:https://git-scm.com/docs
- Pro Git 官方书籍:https://git-scm.com/book/en/v2
- git config:https://git-scm.com/docs/git-config
- git merge:https://git-scm.com/docs/git-merge
- git rebase:https://git-scm.com/docs/git-rebase
- git reset:https://git-scm.com/docs/git-reset
- git restore:https://git-scm.com/docs/git-restore
- git reflog:https://git-scm.com/docs/git-reflog
- git stash:https://git-scm.com/docs/git-stash
- git bisect:https://git-scm.com/docs/git-bisect
- gitignore:https://git-scm.com/docs/gitignore
- GitHub SSH 文档:https://docs.github.com/en/authentication/connecting-to-github-with-ssh
- GitHub Personal Access Token 文档:https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens
- GitHub 命令行解决合并冲突:https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/addressing-merge-conflicts/resolving-a-merge-conflict-using-the-command-line
- GitHub rebase 后解决冲突:https://docs.github.com/en/get-started/using-git/resolving-merge-conflicts-after-a-git-rebase
- GitHub non-fast-forward 问题:https://docs.github.com/en/get-started/using-git/dealing-with-non-fast-forward-errors
- GitHub 行尾配置:https://docs.github.com/en/get-started/git-basics/configuring-git-to-handle-line-endings
- GitHub Git LFS 文档:https://docs.github.com/en/repositories/working-with-files/managing-large-files/about-git-large-file-storage