Git 从入门到实战:一篇搞定所有常用场景
Git 从入门到实战:一篇搞定所有常用场景
不管你是刚入行的开发者,还是已经工作几年的老鸟,Git 都是绕不开的技能。本文从最基础的概念讲起,覆盖日常开发、团队协作、紧急修复、事故抢救等各种真实场景,力求让你读完就能上手,遇到问题能查能解决。
一、Git 是什么?为什么必须学?
1.1 一句话说清楚
Git 是一个分布式版本控制系统,它记录你代码的每一次变更,让你随时可以回到过去任何一个版本。
类比:如果写代码像写小说,Git 就是"无限撤销 + 分支剧情 + 多人协作"的神器。
1.2 集中式 vs 分布式
集中式(SVN): 分布式(Git):
┌─────────┐ ┌─────────┐
│ 中央服务器 │◄──── 所有代码都在这 │ 远程仓库 │
└────┬────┘ └────┬────┘
│ │
┌──┴──┐ ┌────┼────┐
│开发者│ ← 必须联网才能干活 │开发者A│ │开发者B│ │开发者C│
└─────┘ │(完整)│ │(完整)│ │(完整)│
└─────┘ └─────┘ └─────┘
每人都有完整的代码历史,离线也能工作!
1.3 安装与初次配置
# 安装(Windows 下载 Git-SCM,Mac 用 brew install git)
# 必做的全局配置(告诉 Git 你是谁)
git config --global user.name "你的名字"
git config --global user.email "你的邮箱@example.com"
# 推荐配置
git config --global init.defaultBranch main # 默认分支名用 main
git config --global core.autocrlf input # Windows 换行符自动转换
git config --global pull.rebase true # pull 默认用 rebase(保持历史整洁)
# 查看当前配置
git config --list
# 设置别名(效率神器)
git config --global alias.st status
git config --global alias.co checkout
git config --global alias.br branch
git config --global alias.ci commit
git config --global alias.lg "log --oneline --graph --all"
二、核心概念:四个区域和文件状态
2.1 Git 的四个区域
┌─────────────────────────────────────────────────────────────┐
│ Git 四个区域全景图 │
├───────────────┬─────────────────────────────────────────────┤
│ 区域 │ 说明 │
├───────────────┼─────────────────────────────────────────────┤
│ 工作区(Working)│ 你正在编辑的文件(磁盘上的实际文件) │
│ 暂存区(Staging)│ git add 后的文件(即将提交的快照) │
│ 本地仓库(Local)│ git commit 后的历史记录 │
│ 远程仓库(Remote)│ GitHub/GitLab 等远程服务器上的仓库 │
└───────────────┴─────────────────────────────────────────────┘
你的代码在这四个区域之间流转:
工作区 ──git add──► 暂存区 ──git commit──► 本地仓库 ──git push──► 远程仓库
▲ │
│◄──────────── git pull / git clone ◄────────────────────────┘
2.2 文件的四种状态
┌──────────────┐
┌────────►│ Untracked │(未跟踪:Git 还不知道这个文件的存在)
│ └──────┬───────┘
│ │ git add
│ ▼
│ ┌──────────────┐
git rm │ │ Staged │(已暂存:准备放进下一次提交)
--cached│ └──────┬───────┘
│ │ git commit
│ ▼
│ ┌──────────────┐ 修改文件 ┌──────────────┐
└─────────│ Committed │───────────────►│ Modified │
│ (已提交) │◄─── git add ───│ (已修改) │
└──────────────┘ └──────────────┘
2.3 最常用的状态查看命令
git status # 查看当前状态(哪些文件被修改、暂存了)
git status -s # 简短输出(?? 未跟踪, A 新增, M 修改, D 删除)
git diff # 查看工作区和暂存区的差异
git diff --staged # 查看暂存区和最近一次提交的差异
git diff HEAD # 查看工作区和最近一次提交的差异
git log --oneline -10 # 查看最近10条提交记录(一行一条)
git log --oneline --graph --all # 图形化查看所有分支历史
三、基础操作:从零开始管理一个项目
3.1 创建仓库
# 方式一:把已有项目纳入 Git 管理
cd my-project/
git init
git add .
git commit -m "Initial commit"
# 方式二:从远程克隆已有项目
git clone https://github.com/user/repo.git
# 或克隆指定分支
git clone -b develop https://github.com/user/repo.git
# 或浅克隆(只要最近的历史,大项目提速)
git clone --depth=1 https://github.com/user/repo.git
3.2 日常工作流
# 1. 编写/修改代码...
vim app.py
# 2. 查看改了什么
git status
git diff
# 3. 把修改加入暂存区
git add app.py # 添加单个文件
git add src/ # 添加整个目录
git add . # 添加所有变更(慎用,建议先 git status 确认)
# 4. 提交到本地仓库
git commit -m "feat: 添加用户登录功能"
# 5. 推送到远程
git push origin main
3.3 .gitignore 文件(哪些文件不该提交)
# .gitignore 示例(放在仓库根目录)
# 编译产物
*.class
*.pyc
*.o
*.so
/build/
/dist/
# 依赖目录(不要提交第三方库!)
/node_modules/
/venv/
__pycache__/
# IDE 配置
.idea/
.vscode/
*.swp
# 系统文件
.DS_Store
Thumbs.db
# 环境变量和密钥(⚠️绝对不能提交!)
.env
*.key
*.pem
config/secrets.yml
# 日志
*.log
logs/
💡 血的教训:把 .env 或密钥文件提交到 Git 是最常见的安全事故之一。一旦推送到远程,即使后续删除,历史记录里仍然存在!一定要在 .gitignore 里排除。
四、Commit 提交规范
4.1 好的提交信息长什么样
# ✅ 好的提交(一眼就知道干了什么)
git commit -m "feat: 添加用户注册接口"
git commit -m "fix: 修复登录页面在 Safari 上的样式问题"
git commit -m "docs: 更新 API 文档中的错误码说明"
# ❌ 烂的提交(看不懂干了什么)
git commit -m "update"
git commit -m "fix bug"
git commit -m "111"
git commit -m "先提交一下回家再说"
4.2 Conventional Commits 规范
┌────────────────────────────────────────────────────┐
│ Commit 类型(type)速查表 │
├──────────┬──────────────────────────────────────────┤
│ 类型 │ 说明 │
├──────────┼──────────────────────────────────────────┤
│ feat │ 新功能 │
│ fix │ 修复 Bug │
│ docs │ 文档变更 │
│ style │ 代码格式(不影响功能,如缩进、空格) │
│ refactor │ 重构(不是新功能也不是修 Bug) │
│ perf │ 性能优化 │
│ test │ 添加或修改测试 │
│ chore │ 构建过程或辅助工具的变动 │
│ ci │ CI/CD 配置变更 │
│ revert │ 回滚之前的提交 │
└──────────┴──────────────────────────────────────────┘
# 完整格式
git commit -m "type(scope): 简短描述
可选的详细说明(正文)
可分多行写
Breaking Changes: 不兼容变更说明
Closes #123"
4.3 修改最后一次提交
# 发现提交信息写错了
git commit --amend -m "feat: 添加用户注册接口(正确的描述)"
# 发现漏了一个文件没提交
git add forgotten-file.py
git commit --amend --no-edit # 追加到最后一次提交,不改提交信息
# ⚠️ 注意:amend 会修改 commit hash,已 push 的提交慎用!
五、分支管理:团队协作的核心
5.1 什么是分支?为什么用分支?
主线(main):代表生产环境的稳定代码
│
├──► develop:日常开发主线
│ │
│ ├──► feature/login:开发登录功能
│ ├──► feature/cart:开发购物车功能
│ └──► feature/search:开发搜索功能
│
├──► hotfix/fix-crash:紧急修复线上崩溃
│
└──► release/v2.0:准备发布 v2.0
类比:主线是正传,分支是平行宇宙。你在平行宇宙里折腾,不影响主线。
搞定后合并回来就行。
5.2 分支常用操作
# 查看分支
git branch # 查看本地分支
git branch -a # 查看所有分支(包括远程)
git branch -v # 查看分支及其最后一次提交
# 创建分支
git branch feature/login # 创建新分支
git checkout -b feature/login # 创建并切换到新分支(最常用)
git switch -c feature/login # 同上(Git 2.23+ 新命令,推荐)
# 切换分支
git checkout main # 切换到 main 分支
git switch main # 同上(推荐)
# 删除分支
git branch -d feature/login # 删除已合并的分支
git branch -D feature/login # 强制删除(即使没合并)
# 重命名分支
git branch -m old-name new-name # 重命名当前分支
git branch -m feature/login feature/user-login
5.3 合并分支(merge vs rebase)
# ========= merge:保留完整历史 =========
git checkout main
git merge feature/login
# 会产生一个合并提交(merge commit),历史有分叉但完整
# ========= rebase:让历史变线性 =========
git checkout feature/login
git rebase main
# 把 feature 分支的提交"搬到" main 最新提交之后,历史是直线
# ⚠️ 黄金法则:不要对已经推送到远程的公共分支做 rebase!
# rebase 会改写历史,别人基于旧历史的工作会乱套
merge vs rebase 对比:
merge 的历史: rebase 的历史:
│ │
│ C3 (main) │ C3 (main)
│ │ C4' (feature)
╱│ C4 (feature) │ C5' (feature)
╱ │ C5 (feature) │ C6 (main)
╱ │ │
╱ │ C6 (merge commit) │ (线性,漂亮!)
│
(保留分叉,真实但复杂)
5.4 解决合并冲突
# 合并时出现冲突:
# CONFLICT (content): Merge conflict in app.py
# Automatic merge failed; fix conflicts and then commit the result.
# 第一步:查看哪些文件冲突了
git status
# 会显示:both modified: app.py
# 第二步:打开冲突文件,手动编辑
# 冲突标记长这样:
# <<<<<<< HEAD
# 这是当前分支的代码(main)
# =======
# 这是要合并进来的代码(feature)
# >>>>>>> feature/login
# 你需要决定保留哪部分,删除冲突标记(<<< === >>>),保存
# 第三步:标记冲突已解决
git add app.py
# 第四步:完成合并
git commit -m "merge: 合并 feature/login 到 main"
# 💡 小技巧:如果想放弃合并,回到合并前的状态
git merge --abort
六、远程仓库操作
6.1 远程仓库管理
# 查看远程仓库
git remote -v
# origin https://github.com/user/repo.git (fetch)
# origin https://github.com/user/repo.git (push)
# 添加远程仓库
git remote add origin https://github.com/user/repo.git
# 修改远程仓库地址
git remote set-url origin https://github.com/user/new-repo.git
# 删除远程仓库
git remote remove origin
6.2 拉取和推送
# 拉取远程更新(两种方式)
git pull origin main # pull = fetch + merge
git pull --rebase origin main # pull = fetch + rebase(推荐,避免产生 merge commit)
# 推送到远程
git push origin main # 推送当前分支到远程 main
git push -u origin feature/login # -u 设置上游分支(首次推送新分支时用)
git push # 设置了上游后,直接 push 即可
# 查看远程分支和本地分支的追踪关系
git branch -vv
# 删除远程分支
git push origin --delete feature/login
6.3 fetch vs pull
┌──────────────────────────────────────────────────┐
│ fetch vs pull 的区别 │
├──────────┬───────────────────────────────────────┤
│ git fetch │ 只下载远程更新,不修改工作区(安全) │
│ git pull │ 下载远程更新 + 自动合并(可能冲突) │
└──────────┴───────────────────────────────────────┘
# 推荐工作流:先 fetch 看看有什么更新,再决定是否合并
git fetch origin # 下载远程更新
git log HEAD..origin/main --oneline # 看看远程有哪些新提交
git merge origin/main # 确认没问题再合并
七、🎯 场景一:团队协作——Git Flow 工作流
这是企业中最常用的团队协作流程。
Git Flow 全景图:
main(生产)────●────────────●──────────●──────────
│ │ │
release/v2.0────┤ │ │
│ │ │
develop─────────●──●──●──●──●──────────●──────────
│ │
feature/login──────● │
feature/cart───────────●──┘
hotfix/urgent──────────────────●───────┘(从 main 拉,修完回 main + develop)
# === 完整的 Git Flow 流程 ===
# 1. 克隆项目
git clone https://github.com/team/project.git
cd project
# 2. 创建 develop 分支(如果还没有)
git checkout -b develop main
# 3. 开始新功能开发
git checkout -b feature/user-profile develop
# 4. 日常开发(频繁提交)
# ... 编写代码 ...
git add .
git commit -m "feat: 添加用户头像上传功能"
# ... 继续编写 ...
git add .
git commit -m "feat: 添加个人简介编辑功能"
# 5. 功能开发完成,合并回 develop
git checkout develop
git pull origin develop # 先拉取最新
git merge --no-ff feature/user-profile # --no-ff 保留分支历史
git push origin develop
# 6. 准备发布
git checkout -b release/v2.0 develop
# ... 只做 Bug 修复和版本号更新 ...
git commit -m "chore: 更新版本号到 v2.0"
# 7. 发布:合并到 main 和 develop
git checkout main
git merge --no-ff release/v2.0
git tag -a v2.0 -m "Release v2.0"
git push origin main --tags
git checkout develop
git merge release/v2.0
git push origin develop
# 8. 清理分支
git branch -d feature/user-profile
git branch -d release/v2.0
八、🎯 场景二:紧急修复线上 Bug(Hotfix)
线上突然出 Bug 了!客户在催!怎么快速修复?
# 第一步:立即从 main 拉一个 hotfix 分支
git checkout main
git pull origin main
git checkout -b hotfix/fix-payment-error
# 第二步:快速修复
vim src/payment.py # 修复代码
git add src/payment.py
git commit -m "fix: 修复支付金额计算错误"
# 第三步:测试通过后,合并回 main(发布)
git checkout main
git merge --no-ff hotfix/fix-payment-error
git tag -a v1.0.1 -m "Hotfix: 修复支付金额计算错误"
git push origin main --tags
# 第四步:同步修复到 develop(防止下次发布又出现这个 Bug)
git checkout develop
git merge hotfix/fix-payment-error
git push origin develop
# 第五步:清理
git branch -d hotfix/fix-payment-error
# 💡 整个过程:拉分支 → 修 Bug → 合并 → 发布,5 分钟搞定!
九、🎯 场景三:写了一半的代码,突然要切分支
你正在 feature 分支写新功能,写到一半,突然被告知线上有个紧急 Bug 要修。代码还没写完,不想提交,怎么办?
# 方案一:git stash(推荐,最常用)
# 1. 把当前工作暂存起来
git stash save "用户注册功能写了一半"
# 或者直接 git stash
# 2. 查看暂存列表
git stash list
# stash@{0}: On feature/register: 用户注册功能写了一半
# 3. 切去修 Bug
git checkout hotfix/fix-bug
# ... 修 Bug,提交,合并 ...
# 4. 回来继续开发
git checkout feature/register
git stash pop # 恢复最近的暂存,并从列表删除
# 或
git stash apply stash@{0} # 恢复指定暂存,不删除
# 方案二:直接提交(草稿提交)
git add .
git commit -m "WIP: 用户注册功能(未完成)"
# 切去修 Bug...
# 回来后撤销这个提交,代码还在
git reset HEAD~1 # 撤销最后一次提交,保留修改在工作区
十、🎯 场景四:撤回操作(各种"后悔药")
这是大家最常遇到的情况:提交错了、push 错了、想回退版本。
10.1 撤回场景速查表
┌──────────────────────────────────────────────────────────────────┐
│ 撤回操作速查表(按场景选命令) │
├───────────────────────────┬──────────────────────────────────────┤
│ 场景 │ 命令 │
├───────────────────────────┼──────────────────────────────────────┤
│ git add 添加错了 │ git restore --staged 文件名 │
│ │ 或 git reset HEAD 文件名 │
├───────────────────────────┼──────────────────────────────────────┤
│ 工作区改乱了,想放弃修改 │ git restore 文件名 │
│ │ 或 git checkout -- 文件名 │
├───────────────────────────┼──────────────────────────────────────┤
│ commit 提交错了(未 push) │ git reset HEAD~1 ← 保留修改 │
│ │ git reset --hard HEAD~1 ← 丢弃修改 │
├───────────────────────────┼──────────────────────────────────────┤
│ commit 提交错了(已 push) │ git revert HEAD ← 安全回退 │
│ │ git push origin main │
├───────────────────────────┼──────────────────────────────────────┤
│ 想回到某个历史版本 │ git checkout 提交哈希 │
│ │ 或 git reset --hard 提交哈希 │
├───────────────────────────┼──────────────────────────────────────┤
│ 删错了分支 │ git reflog → 找到哈希 → git checkout │
└───────────────────────────┴──────────────────────────────────────┘
10.2 reset vs revert:一分钟搞懂核心区别
这是 Git 面试必考题,也是实际开发中最容易搞混的命令。先用一个比喻帮你记住:
📝 比喻记忆法:
reset = 橡皮擦 🔴 → 直接擦掉历史,就像什么都没发生过
revert = 撤销条 🟢 → 在后面贴一张"声明",说"前面的操作作废"
举例:
提交历史:A → B → C(C 有 bug)
用 reset:A → B ← C 直接消失了!历史被改写
用 revert:A → B → C → C' ← C 还在,但 C' 是一个"反向操作",效果等于删除了 C 的改动
核心区别一图看懂:
┌─────────────────────────────────────────────────────────────────────┐
│ reset vs revert 全面对比 │
├──────────────────┬──────────────────────┬────────────────────────────┤
│ 维度 │ git reset │ git revert │
├──────────────────┼──────────────────────┼────────────────────────────┤
│ 本质 │ 改写历史(移动指针) │ 新增一个"反向提交" │
├──────────────────┼──────────────────────┼────────────────────────────┤
│ 历史记录 │ 提交会消失 ⚠️ │ 提交保留,新增撤销提交 ✅ │
├──────────────────┼──────────────────────┼────────────────────────────┤
│ 是否安全 │ 有丢代码风险 │ 安全,不会丢代码 │
├──────────────────┼──────────────────────┼────────────────────────────┤
│ 对远程的影响 │ 会改写远程历史 💥 │ 不改写历史,正常push │
├──────────────────┼──────────────────────┼────────────────────────────┤
│ 团队协作 │ 危险!其他人会受影响 │ 安全!不影响其他人 │
├──────────────────┼──────────────────────┼────────────────────────────┤
│ 适用场景 │ 本地未push的提交 │ 已push的提交 │
├──────────────────┼──────────────────────┼────────────────────────────┤
│ 撤销方式 │ 把 HEAD 指针往回拨 │ 创建新提交来抵消旧提交 │
├──────────────────┼──────────────────────┼────────────────────────────┤
│ 可逆性 │ 用 reflog 可恢复 │ 天然可逆(再 revert 回来) │
├──────────────────┼──────────────────────┼────────────────────────────┤
│ 冲突可能性 │ 无冲突(直接移动指针) │ 可能产生冲突(需手动解决) │
└──────────────────┴──────────────────────┴────────────────────────────┘
如何选择?看这个决策树:
你的提交已经 push 了吗?
│
├─ 没有 push(只在本地)
│ │
│ ├─ 还要保留代码修改?
│ │ ├─ 要 → git reset --soft HEAD~1 (修改在暂存区)
│ │ └─ 不要 → git reset --hard HEAD~1 (修改全丢)
│ │
│ └─ 想让修改回到工作区(未暂存)?
│ └─ git reset HEAD~1 (等同于 --mixed)
│
└─ 已经 push 了
│
├─ 是你一个人在用的分支?
│ ├─ 是 → 可以用 git reset,然后 git push --force
│ └─ 否(团队共用) → 必须用 git revert ⚠️
│
└─ revert 后 push 即可,不会影响其他人
10.3 reset 的三种模式:逐层深入理解
reset 有三种模式,区别在于"回退到哪一步"。理解的关键是知道 Git 的三个区域:
Git 的三个区域:
工作区(Working Directory) → 你能看到、能编辑的文件
暂存区(Staging Area/Index) → git add 之后,准备提交的文件
仓库区(Repository) → git commit 之后,正式的提交记录
完整流程:工作区 → git add → 暂存区 → git commit → 仓库区
reset 的三种模式,就是决定"回退几步":
# 假设当前状态:你做了第三次提交 C
# 提交历史:A → B → C(HEAD 指向 C)
# C 里面包含了对 file.py 的修改
# ═══════════════════════════════════════════
# 模式一:--soft(最温柔,代码全保留)
# ═══════════════════════════════════════════
git reset --soft HEAD~1
# 效果图解:
# 仓库区: HEAD 指针从 C 退回到 B(C 提交消失)
# 暂存区: C 的修改还在这里 ✅(已经 add 的状态)
# 工作区: 不受影响
#
# 相当于:撤销了 commit,但 add 还在
# 适用场景:
# - commit 信息写错了,想重新写
# - 想把几次提交合并成一次
# - 修改还在,可以直接 git commit -m "新信息" 重新提交
# 实战例子:
git log --oneline
# abc1234 (HEAD) feat: 添加登录功能
# def5678 feat: 添加注册功能
git reset --soft HEAD~1
# HEAD 退回 def5678,但登录功能的代码还在暂存区
git status
# Changes to be committed:
# modified: src/login.py ← 注意!还在暂存区,可以直接重新提交
git commit -m "feat: 添加登录功能(含表单验证)" # 重新提交,这次信息写对了
# ═══════════════════════════════════════════
# 模式二:--mixed(默认模式,最常用)
# ═══════════════════════════════════════════
git reset HEAD~1
# 等同于 git reset --mixed HEAD~1
# 效果图解:
# 仓库区: HEAD 指针从 C 退回到 B(C 提交消失)
# 暂存区: C 的修改被移出(变回未暂存状态)
# 工作区: C 的修改还在这里 ✅(文件内容不变)
#
# 相当于:撤销了 commit + add,但文件修改还在
# 适用场景:
# - 提交错了,想把某些文件从暂存区拿出来
# - 想重新选择要提交的文件
# 实战例子:
git log --oneline
# abc1234 (HEAD) feat: 添加登录功能和注册功能(一次提交了两个功能)
# 你发现应该分开提交
git reset HEAD~1
# 两个功能的修改都回到了工作区(未暂存)
git status
# Changes not staged for commit:
# modified: src/login.py
# modified: src/register.py
# 现在可以分开提交了
git add src/login.py
git commit -m "feat: 添加登录功能"
git add src/register.py
git commit -m "feat: 添加注册功能"
# ═══════════════════════════════════════════
# 模式三:--hard(最危险,代码全丢!)
# ═══════════════════════════════════════════
git reset --hard HEAD~1
# 效果图解:
# 仓库区: HEAD 指针从 C 退回到 B(C 提交消失)
# 暂存区: C 的修改被清空
# 工作区: C 的修改被清空 ❌(文件也回到 B 的状态)
#
# 相当于:一切回到 B 提交时的样子,C 的修改彻底消失
# ⚠️ 警告:代码真的会丢!除非用 reflog 找回
# 适用场景:
# - 确定不要这些修改了(比如试错后发现方向不对)
# - 想完全回到之前的某个版本
# 实战例子:
git log --oneline
# abc1234 (HEAD) try: 尝试新方案(失败了)
# def5678 feat: 添加主功能
git reset --hard HEAD~1
# HEAD 回到 def5678,abc1234 的所有修改全部消失
# 文件恢复到 def5678 时的状态
git status
# nothing to commit, working tree clean ← 干干净净,好像什么都没发生过
# 💡 如果后悔了怎么办?用 reflog 救命!
git reflog
# def5678 HEAD@{0}: reset: moving to HEAD~1
# abc1234 HEAD@{1}: commit: try: 尝试新方案
git reset --hard abc1234 # 还能找回来!
# ═══════════════════════════════════════════
# 三种模式对比总结
# ═══════════════════════════════════════════
#
# 仓库(commit) 暂存区(add) 工作区(文件)
# --soft 撤销 ✅ 保留 ✅ 保留 ✅
# --mixed 撤销 ✅ 撤销 ✅ 保留 ✅
# --hard 撤销 ✅ 撤销 ✅ 撤销 ✅
#
# 记忆口诀:
# soft = 只动 commit,最安全
# mixed = 动 commit + add,默认值
# hard = 全都动,最危险
10.4 revert:安全的"反向操作"(已 push 的代码必须用这个)
revert 不是"删掉历史",而是"加一条新记录来抵消之前的操作"。就像会计做账:错了不能涂掉,要在下面写一笔冲正。
# ═══════════════════════════════════════════
# 为什么已 push 的代码必须用 revert?
# ═══════════════════════════════════════════
#
# 假设提交历史:A → B → C(C 已 push)
# 你的同事张三已经 pull 了这份代码
#
# 如果你用 reset:
# 你的历史:A → B (C 消失了)
# 张三的历史:A → B → C (C 还在!)
# → 你 push 的时候需要 --force
# → 张三 pull 的时候会冲突、丢失代码 💥
#
# 如果你用 revert:
# 你的历史:A → B → C → C'(C' 抵消了 C)
# 张三 pull 后:A → B → C → C'(安全同步 ✅)
# ═══════════════════════════════════════════
# revert 基本用法
# ═══════════════════════════════════════════
# 撤销最后一次提交
git revert HEAD
# 会弹出编辑器让你写撤销原因,保存后会生成一个新提交
# 撤销指定提交
git revert abc123f
# 找到 abc123f 这个提交的改动,生成一个"反向改动"的新提交
# 撤销多个提交(连续的)
git revert HEAD~3..HEAD
# 撤销最近 3 个提交(注意:会按从新到旧的顺序逐个撤销)
# 撤销但不自动提交(先看看效果)
git revert --no-commit HEAD
# 撤销的改动放在暂存区,你可以检查后再手动 commit
git commit -m "revert: 撤销登录功能(有 bug)"
# ═══════════════════════════════════════════
# revert 遇到冲突怎么办?
# ═══════════════════════════════════════════
# 如果后来的提交修改了和被撤销提交相同的文件,revert 会产生冲突
git revert abc123f
# CONFLICT (content): Merge conflict in src/login.py
# error: could not revert abc123f
# 解决步骤:
# 1. 打开冲突文件,手动选择要保留的内容
# 2. 解决后:
git add src/login.py
git revert --continue
# 如果放弃撤销:
git revert --abort
# ═══════════════════════════════════════════
# push 失败了怎么办?——force push 详解
# ═══════════════════════════════════════════
# 🤔 为什么 push 会失败?
# ──────────────────────────────
# 当你用 reset 回退了本地历史后,本地和远程的提交历史就不一致了:
#
# 远程:A → B → C → D
# 本地:A → B (你 reset 掉了 C 和 D)
#
# 此时 git push 会报错:
# ! [rejected] main -> main (non-fast-forward)
# error: failed to push some refs to 'origin/main'
#
# Git 拒绝推送,因为它发现你本地"少"了提交,担心你误操作丢代码!
# 📋 三种解决方案对比
# ──────────────────────────────
# ┌──────────────────────────────────────────────────────┐
# │ 方案 │ 命令 │ 危险等级 │
# ├──────────────────────────────────────────────────────┤
# │ ① revert + push │ git revert → git push │ 🟢 安全 │
# │ ② force push │ git push --force │ 🟡 危险 │
# │ ③ force push 安全版│ git push --force-with-lease │ 🟡 较安全│
# └──────────────────────────────────────────────────────┘
# 方案一:revert + push(推荐!最安全)
# ──────────────────────────────
# 不改写历史,用 revert 生成反向提交,然后正常 push
git revert C D # 逐个撤销 C 和 D 的改动
git push origin main # ✅ 正常 push 成功!
# 历史变成:A → B → C → D → C' → D'
# 代码效果回到了 B 的状态,但历史完整保留
# 方案二:force push(强制推送,⚠️ 慎用!)
# ──────────────────────────────
# 场景:你确定要覆盖远程历史,而且其他人没有基于 C/D 做新提交
git push --force origin main
# 或者简写:
git push -f origin main
# ⚠️ 危险在哪?
# 如果同事小明已经基于 D 拉取了代码并做了新提交 E:
# 小明:A → B → C → D → E
# 你 force push 后远程变成:A → B
# 小明下次 pull 时会冲突爆炸💥,甚至可能丢失 E!
# 方案三:force-with-lease(安全版的 force push 🌟推荐)
# ──────────────────────────────
# 比 --force 多了一层保护:会检查远程分支是否被别人更新过
git push --force-with-lease origin main
# 工作原理:
# 如果远程分支还是你上次看到的状态(没人动过)→ ✅ 推送成功
# 如果远程分支已经被别人更新了 → ❌ 推送失败,保护了别人的代码!
# 对比:
# git push --force → 管他三七二十一,直接覆盖!
# git push --force-with-lease → 先确认没人动过,再覆盖(有保镖的 force push)
# ═══════════════════════════════════════════
# 实战场景 1:本地 reset 后需要推送
# ═══════════════════════════════════════════
# 你在本地 reset 了最近 2 个提交:
git reset --hard HEAD~2
# 本地:A → B(C 和 D 被删了)
# 远程:A → B → C → D
# 方案 A:用 revert(安全,推荐团队协作)
git revert HEAD@{1} HEAD@{2} # 撤销 C 和 D
git push origin main # 正常 push
# 方案 B:用 force-with-lease(个人分支,确认没别人用)
git push --force-with-lease origin main
# 远程变成:A → B ✅
# 方案 C:用 force(只有你一个人在用的分支)
git push --force origin main
# 远程变成:A → B ✅ 但没有保镖保护
# ═══════════════════════════════════════════
# 实战场景 2:rebase 后需要推送
# ═══════════════════════════════════════════
# rebase 也会改写历史,push 同样会失败
git rebase -i HEAD~3 # 整理了最近 3 个提交
git push origin main # ❌ rejected!
# 解决:
git push --force-with-lease origin main # ✅ 推送成功
# ═══════════════════════════════════════════
# 什么时候该用哪个?决策指南
# ═══════════════════════════════════════════
#
# 问自己两个问题:
#
# 1️⃣ 这个分支有没有其他人也在用?
# ├── 有 → 用 revert + push(方案一)🟢
# └── 没有(个人分支)→ 继续问 ↓
#
# 2️⃣ 你确定要覆盖远程历史吗?
# ├── 确定 → 用 --force-with-lease(方案三)🟡
# └── 不确定 → 用 revert + push(方案一)🟢
#
# 🔴 永远不要在 main/master 等公共分支上用 --force!
# 🔴 团队协作的分支,优先用 revert!
# 🟡 个人分支可以用 --force-with-lease
# 🟢 不确定时,revert + push 永远是最安全的选择
# ═══════════════════════════════════════════
# 实战场景:完整的 revert 流程
# ═══════════════════════════════════════════
# 场景:你昨天提交了一个支付功能,今天测试发现有严重 bug
# 提交记录:
# efg7890 (HEAD) docs: 更新 API 文档
# cde4567 feat: 添加用户管理
# abc1234 feat: 添加支付功能 ← 这个有 bug,要撤销!
# 第一步:撤销支付功能的提交
git revert abc1234
# Git 会生成一个新提交,内容和 abc1234 完全相反
# 第二步:查看历史
git log --oneline
# hij0123 (HEAD) Revert "feat: 添加支付功能"
# efg7890 docs: 更新 API 文档
# cde4567 feat: 添加用户管理
# abc1234 feat: 添加支付功能 ← 还在!但效果被上面的 Revert 抵消了
# 第三步:推送到远程
git push origin main
# 完美!同事们 pull 后,支付功能就被安全地移除了
# 等你修好 bug 后,可以重新提交,也可以 revert 那个 revert:
git revert hij0123 # 恢复支付功能!
# ═══════════════════════════════════════════
# 一句话总结
# ═══════════════════════════════════════════
#
# 🔴 reset = 时光倒流(改写历史)→ 只在本地用!
# 🟢 revert = 发布补丁(保留历史)→ 已推送的代码用这个!
#
# 记住:已经 push 的代码,永远用 revert!
# 没有 push 的代码,reset 更方便!
十一、🎯 场景五:代码写岔了,怎么搬移提交?
11.1 cherry-pick:把某个提交搬到另一个分支
# 场景:你在 develop 分支做了一个修复,但这个修复也需要同步到 main
git checkout main
git cherry-pick abc123f # 把 abc123f 这个提交"复制"到当前分支
# cherry-pick 多个提交
git cherry-pick abc123f def456g
# cherry-pick 一个范围
git cherry-pick feature/login~3..feature/login
# 如果 cherry-pick 遇到冲突
# 解决冲突后:
git add .
git cherry-pick --continue
# 放弃 cherry-pick
git cherry-pick --abort
11.2 交互式 rebase:修改历史提交
# 场景:提交历史太乱,想整理一下(合并提交、修改提交信息、删除提交、调整顺序)
git rebase -i HEAD~5 # 整理最近 5 个提交
# 会打开编辑器,显示:
# pick abc1234 feat: 添加登录页面
# pick def5678 fix: 修复登录按钮样式
# pick ghi9012 feat: 添加注册页面
# pick jkl3456 feat: 添加忘记密码功能
# pick mno7890 docs: 更新 README
# 你可以修改每行开头的命令:
# pick = 保留这个提交
# squash = 把这个提交合并到前一个提交
# reword = 保留提交,但修改提交信息
# edit = 保留提交,但停下来让你修改
# drop = 删除这个提交
# 把 def5678 那行改成 squash(合并到 abc1234),保存退出即可
# ⚠️ 同样的规则:不要 rebase 已经推送的公共分支!
十二、🎯 场景六:误操作的终极救星——reflog
如果你不小心 reset --hard 了、删了分支、rebase 搞乱了——别慌!reflog 记录了你的所有操作历史。
# 查看操作历史(本地所有 HEAD 的移动记录)
git reflog
# abc1234 HEAD@{0}: reset: moving to HEAD~1
# def5678 HEAD@{1}: commit: feat: 添加了很重要的功能
# ghi9012 HEAD@{2}: checkout: moving from main to feature/login
# ...
# 找到你想回到的那个点(比如 HEAD@{1})
git reset --hard def5678 # 回到那个状态
# 误删分支也能找回
git reflog # 找到分支最后一次提交的哈希
git checkout -b recovered-branch abc1234 # 用那个哈希重建分支
# 💡 reflog 默认保留 90 天,这期间几乎能恢复一切!
十三、🎯 场景七:大型项目中的高级技巧
13.1 子模块(Submodule)
# 场景:项目依赖另一个 Git 仓库(如公共组件库)
# 添加子模块
git submodule add https://github.com/team/shared-lib.git libs/shared-lib
git commit -m "chore: 添加 shared-lib 子模块"
# 克隆包含子模块的项目
git clone --recurse-submodules https://github.com/team/project.git
# 已克隆的项目拉取子模块
git submodule init
git submodule update
# 更新子模块到最新
git submodule update --remote
13.2 子树(Subtree)—— 比 Submodule 更简单的替代方案
# 添加子树
git subtree add --prefix=libs/shared-lib https://github.com/team/shared-lib.git main --squash
# 拉取子树更新
git subtree pull --prefix=libs/shared-lib https://github.com/team/shared-lib.git main --squash
13.3 大文件处理(Git LFS)
# Git 不擅长管理大文件(视频、模型、数据集等)
# Git LFS(Large File Storage)解决此问题
# 安装(一次性)
git lfs install
# 追踪大文件类型
git lfs track "*.psd"
git lfs track "*.mp4"
git lfs track "datasets/**"
# 正常 add/commit 即可,LFS 自动处理
git add .gitattributes
git add large-file.psd
git commit -m "chore: 配置 Git LFS 追踪大文件"
13.4 二分查找(bisect)—— 找出哪个提交引入了 Bug
# 场景:某个 Bug 不知道是哪次提交引入的
git bisect start
git bisect bad # 当前版本有 Bug
git bisect good v1.0.0 # v1.0.0 版本没 Bug
# Git 会自动二分查找,每次让你测试一个版本
# 测试后告诉 Git:
git bisect good # 这个版本没问题
git bisect bad # 这个版本有 Bug
# 重复几次,Git 会定位到具体哪个提交引入了 Bug
# 找到后:
git bisect reset # 结束二分查找
十四、🎯 场景八:处理敏感信息泄露
# ⚠️ 最可怕的场景:不小心把密码/API Key 推到了远程仓库!
# 光删文件没用,Git 历史里还有!
# 方案一:BFG Repo-Cleaner(推荐,简单快速)
# 安装 BFG(Java 工具)
# 替换敏感信息
bfg --replace-text passwords.txt my-repo/
git reflog expire --expire=now --all
git gc --prune=now --aggressive
git push --force
# 方案二:git filter-branch(内置,慢但通用)
git filter-branch --force --index-filter \\
'git rm --cached --ignore-unmatch path/to/sensitive-file' \\
--prune-empty --tag-name-filter cat -- --all
# 方案三:git filter-repo(推荐替代 filter-branch)
pip install git-filter-repo
git filter-repo --path path/to/sensitive-file --invert-paths
# 💡 最好的方案是预防:
# 1. .gitignore 里排除 .env 和密钥文件
# 2. 使用 pre-commit 钩子检测敏感信息
# 3. 使用 git-secrets 等工具
十五、🎯 场景九:多仓库/多账号配置
公司用 GitLab,个人用 GitHub,两套账号怎么管理?
# 在 ~/.ssh/ 下生成两套密钥
ssh-keygen -t ed25519 -C "work@company.com" -f ~/.ssh/id_ed25519_work
ssh-keygen -t ed25519 -C "personal@gmail.com" -f ~/.ssh/id_ed25519_personal
# 配置 SSH config(~/.ssh/config)
# 公司 GitLab
Host gitlab.company.com
HostName gitlab.company.com
User git
IdentityFile ~/.ssh/id_ed25519_work
# 个人 GitHub
Host github.com
HostName github.com
User git
IdentityFile ~/.ssh/id_ed25519_personal
# 不同仓库使用不同的 Git 配置
# 全局配置(默认用个人的)
git config --global user.name "个人名字"
git config --global user.email "personal@gmail.com"
# 在公司项目里覆盖
cd /path/to/work-project
git config user.name "工作名字"
git config user.email "work@company.com"
十六、🎯 场景十:代码审查(Code Review)
16.1 Pull Request / Merge Request 流程
PR/MR 工作流:
1. 开发者 Fork 或从 develop 拉分支
│
2. 在自己的分支上开发,提交代码
│
3. 推送到远程,创建 Pull Request
│
4. 团队成员 Code Review
├── 提出修改意见 → 开发者修改 → 再提交
├── Approve(通过)
└── Request Changes(要求修改)
│
5. CI/CD 自动测试通过
│
6. 合并到主分支
│
7. 自动部署
16.2 PR 提交者的最佳实践
# 1. PR 粒度要小(一个 PR 做一件事)
# ✅ 好的 PR:只添加用户登录功能
# ❌ 烂的 PR:登录 + 注册 + 购物车 + 重构 + 改 UI
# 2. 提交前自检
git diff main...feature/login # 看看和主分支的差异
git log main..feature/login --oneline # 看看有哪些新提交
# 3. 如果提交太多太碎,交互式 rebase 合并一下
git rebase -i HEAD~5 # squash 合并碎片提交
# 4. 确保通过了所有测试
npm test # 或 pytest、go test 等
# 5. 写好 PR 描述
# 标题:feat: 添加用户登录功能
# 描述:
# - 实现了邮箱/密码登录
# - 添加了 JWT Token 认证
# - 补充了单元测试
# - 截图/录屏演示
16.3 Code Review 者的最佳实践
# 快速审查命令
git fetch origin
git checkout feature/login
git log --oneline main..HEAD # 看提交历史
git diff main...HEAD # 看代码差异
git diff main...HEAD --stat # 看改了哪些文件
# 用 GitLab/GitHub 的行内评论功能
# 关注点:
# 1. 逻辑是否正确
# 2. 是否有安全漏洞
# 3. 代码风格是否统一
# 4. 是否有更好的实现方式
# 5. 测试覆盖是否充分
十七、Git 钩子(Hooks)自动化
# Git 钩子放在 .git/hooks/ 目录下
# 常用钩子:
# pre-commit → 提交前执行(代码检查、格式化)
# commit-msg → 验证提交信息格式
# pre-push → 推送前执行(运行测试)
# post-merge → 合并后执行(自动安装依赖)
# ===== 示例:pre-commit 钩子(代码检查)=====
# .git/hooks/pre-commit(去掉 .sample 后缀即可生效)
# 检查是否有 console.log 残留
if git diff --cached --name-only | grep '\.js$' | xargs grep -l 'console.log'; then
echo "发现 console.log,请移除后再提交!"
exit 1
fi
# 检查是否有调试代码
if git diff --cached | grep -E 'debugger|breakpoint'; then
echo "发现调试代码,请移除后再提交!"
exit 1
fi
echo "代码检查通过"
17.1 用 Husky 管理钩子(推荐)
# 原生 Git Hooks 的问题:
# 1. .git/hooks 不会被 Git 跟踪,团队共享困难
# 2. 手动管理麻烦,容易忘
# Husky:让钩子配置写在 package.json 里,团队共享!
npm install husky --save-dev
npx husky init # 初始化
# 添加 pre-commit 钩子
echo "npm run lint" > .husky/pre-commit
# 添加 commit-msg 钩子(配合 commitlint 使用)
echo "npx --no -- commitlint --edit \$1" > .husky/commit-msg
17.2 实战:commitlint 规范提交信息
# 安装 commitlint
npm install @commitlint/cli @commitlint/config-conventional --save-dev
# 合法的提交信息示例:
# git commit -m "feat: 添加用户注册功能"
# git commit -m "fix: 修复登录页面空白问题"
# git commit -m "docs: 更新 API 文档"
# 非法的提交信息(会被拒绝):
# git commit -m "更新了东西" → 缺少类型前缀
# git commit -m "abc: xxx" → 不合法的类型
十八、Git 标签(Tag)管理
# Tag 就像书签,给某个提交打个标记,通常用于版本发布
# 创建轻量标签
git tag v1.0.0
# 创建附注标签(推荐,包含更多信息)
git tag -a v1.0.0 -m "正式版 1.0.0 发布"
# 给历史提交打标签
git tag -a v0.9.0 abc1234 -m "0.9.0 版本"
# 查看所有标签
git tag
git tag -l "v1.*" # 按模式过滤
# 推送标签到远程
git push origin v1.0.0 # 推送单个标签
git push origin --tags # 推送所有标签
# 删除标签
git tag -d v1.0.0 # 删除本地标签
git push origin :refs/tags/v1.0.0 # 删除远程标签
18.1 语义化版本号(SemVer)
# 版本号格式:MAJOR.MINOR.PATCH
# 例如:2.1.3
# 2 → MAJOR(大版本,可能有不兼容的变更)
# 1 → MINOR(小版本,新增功能,向后兼容)
# 3 → PATCH(补丁,修复 bug,向后兼容)
# 什么时候改什么版本号?
# 修复了一个 bug → PATCH+1(2.1.3 → 2.1.4)
# 新增了一个功能 → MINOR+1(2.1.3 → 2.2.0)
# 改了接口,旧代码不能用了 → MAJOR+1(2.1.3 → 3.0.0)
十九、常见问题与故障排查
19.1 提交错了怎么办?
# 场景1:刚 commit,发现少加了一个文件
git add forgotten_file.js
git commit --amend --no-edit # 追加到上一次提交
# 场景2:刚 commit,发现提交信息写错了
git commit --amend -m "正确的提交信息"
# 场景3:刚 push 了,需要撤回(慎用!)
# 如果只有你一个人在用这个分支:
git reset --hard HEAD~1
git push --force
# 如果有其他人在用这个分支:用 revert 更安全
git revert <commit-hash>
git push
19.2 合并冲突怎么解决?
# 冲突长什么样:
# <<<<<<< HEAD
# 这是你的代码
# =======
# 这是别人的代码
# >>>>>>> feature-branch
# 解决步骤:
# 1. 打开冲突文件
# 2. 手动选择保留哪部分代码
# 3. 删除所有标记符号
# 4. 保存文件
git add .
git commit -m "解决合并冲突"
19.3 误删分支怎么恢复?
# 查看被删除分支的最后一次提交
git reflog
# 恢复分支
git checkout -b feature-recovered abc1234
# reflog 是 Git 的后悔药,记录了所有 HEAD 的移动
# 默认保留 90 天
19.4 大文件提交了怎么彻底删除?
# 场景:不小心把大文件提交了
# 即使删了再提交,Git 历史里还保留着
# 用 BFG Repo-Cleaner(推荐,快)
bfg --delete-files large-file.zip
git reflog expire --expire=now --all
git gc --prune=now --aggressive
# 用 git-filter-repo(推荐,现代化)
pip install git-filter-repo
git filter-repo --path large-file.zip --invert-paths
19.5 .gitignore 不生效怎么办?
# 原因:文件已经被 Git 跟踪了
# 解决:先取消跟踪,再添加到 .gitignore
git rm --cached path/to/file
git rm -r --cached node_modules/
echo "node_modules/" >> .gitignore
git add .gitignore
git commit -m "停止跟踪 node_modules"
19.6 push 被拒绝怎么办?
# 错误:! [rejected] main -> main (fetch first)
# 原因:远程有你本地没有的新提交
# 解决:先 pull 再 push
git pull --rebase origin main
git push origin main
# 或者用更安全的 force
git push --force-with-lease origin main
二十、Git 进阶技巧与可视化工具
20.1 实用技巧
# 暂存部分文件(交互式)
git add -p # 逐块选择要暂存的代码
# 查看某个文件的修改历史
git log --follow -p path/to/file.js
# 查看某行代码是谁写的
git blame path/to/file.js
git blame -L 10,20 path/to/file.js # 只看第10-20行
# 找到引入 bug 的提交
git bisect start
git bisect bad # 当前版本有 bug
git bisect good v1.0.0 # 这个版本没 bug
git bisect run npm test # 自动运行测试来定位
# 清理本地已合并的分支
git branch --merged | grep -v "\*\|main\|develop" | xargs -n 1 git branch -d
# 垃圾回收
git gc --aggressive --prune=now
20.2 可视化工具推荐
工具 类型 特点
─────────────────────────────────────────────────
VS Code GitLens VS Code 插件 最强 Git 可视化插件
SourceTree 独立 GUI 免费且功能全面
GitKraken 独立 GUI 跨平台,界面美观
GitHub Desktop 独立 GUI 简单易用,官方出品
lazygit 终端 UI 键盘操作,效率极高
推荐:
初学者 → GitHub Desktop 或 SourceTree
进阶 → VS Code + GitLens
高手 → lazygit
20.3 换行符问题(Windows/Mac/Linux)
# Windows 用 CRLF,Mac/Linux 用 LF
# Windows 用户
git config --global core.autocrlf true
# Mac/Linux 用户
git config --global core.autocrlf input
# 项目统一配置(.gitattributes 文件)
* text=auto
*.sh text eol=lf
*.bat text eol=crlf
总结:Git 学习路线图
入门阶段(1-2周)
├── git init / clone / add / commit / push / pull
├── 理解工作区 → 暂存区 → 仓库 的概念
├── 学会看 git status / git log
└── 练习:创建仓库,提交代码,推送到 GitHub
进阶阶段(2-4周)
├── 分支管理(branch / merge / rebase)
├── 解决合并冲突
├── .gitignore 配置
├── git stash 暂存
└── 练习:多人协作开发一个项目
高级阶段(1-2月)
├── 交互式 rebase(整理提交历史)
├── cherry-pick(精准摘取提交)
├── Git Hooks 自动化
├── bisect 定位 bug
├── 大文件处理 / 历史重写
└── 练习:参与开源项目贡献
专家阶段(持续)
├── 自定义 Git 工作流
├── Git 内部原理(对象模型、引用)
├── 性能优化(大仓库管理)
├── 编写自定义钩子 / CI/CD 集成
└── 指导团队 Git 最佳实践
Git 的学习不需要一次性掌握所有命令。从最基础的 add/commit/push 开始,在日常开发中逐步积累经验。遇到问题别慌,git reflog 是你的后悔药,git stash 是你的暂存箱。
记住一句话:"先学会走,再学会跑,最后再飞。"祝你在 Git 的世界里畅行无阻!
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐
所有评论(0)