“git add .”
“git commit -m ‘fix’”
“git push”
“完事儿。”
—— 90% 程序员的 Git 日常

前言:你真的会用 Git 吗?

让我猜猜你的 Git 使用方式:

git add .
git commit -m "update"
git push

# 遇到冲突?
git pull
# 手动解决冲突
git add .
git commit -m "merge"
git push

# 搞砸了?
# 删掉本地仓库,重新 clone

如果你中枪了,别担心,你不是一个人。

根据 Stack Overflow 的调查,87% 的开发者每天都在用 Git,但 65% 的初级开发者对高级命令一无所知。

今天,我要带你解锁 Git 的隐藏技能。

学完这篇文章,你再也不用"删库重来"了。


第一章:救命三连——当你搞砸的时候

1.1 git reflog —— 后悔药,真的存在

场景: 你不小心 git reset --hard 了,代码没了,心态崩了。

别慌!Git 其实什么都记得。

# 查看所有操作历史(包括已经"删除"的提交)
git reflog

# 输出类似这样:
# a1b2c3d HEAD@{0}: reset: moving to HEAD~3
# e4f5g6h HEAD@{1}: commit: 重要的功能
# i7j8k9l HEAD@{2}: commit: 另一个功能
# ...

# 找到你想恢复的提交,然后:
git reset --hard e4f5g6h

# 代码回来了!

原理: git reflog 记录了 HEAD 的所有移动历史,即使是被 reset 掉的提交,也能找回来。

这就是为什么说 Git 几乎不会真正丢失数据——只要你知道 reflog。

┌─────────────────────────────────────────────────────────────┐
│                    git reflog 的魔力                         │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│   你以为的 Git:                                             │
│   ┌───┐    ┌───┐    ┌───┐                                  │
│   │ A │ ── │ B │ ── │ C │  ← HEAD                          │
│   └───┘    └───┘    └───┘                                  │
│                                                             │
│   git reset --hard A 之后:                                 │
│   ┌───┐                                                    │
│   │ A │  ← HEAD                                            │
│   └───┘                                                    │
│   B 和 C 去哪了???                                        │
│                                                             │
│   实际的 Git:                                               │
│   ┌───┐    ┌───┐    ┌───┐                                  │
│   │ A │ ── │ B │ ── │ C │  (还在!只是看不见了)            │
│   └───┘    └───┘    └───┘                                  │
│     ↑                                                       │
│    HEAD                                                     │
│                                                             │
│   reflog 知道 B 和 C 的位置,随时可以恢复!                   │
│                                                             │
└─────────────────────────────────────────────────────────────┘

1.2 git stash —— 临时存档,随时切换

场景: 你正在开发一个功能,写到一半,老板说"线上有 bug,赶紧修"。

你的代码还没写完,不想提交,但又需要切换分支。

怎么办?

# 把当前修改"藏"起来
git stash

# 现在工作区干净了,可以切换分支
git checkout hotfix-branch

# 修完 bug,切回来
git checkout feature-branch

# 把之前的修改"拿"出来
git stash pop

# 继续开发!

进阶用法:

# 给 stash 加个描述(推荐!)
git stash save "正在开发用户登录功能"

# 查看所有 stash
git stash list
# stash@{0}: On feature: 正在开发用户登录功能
# stash@{1}: On main: 临时修改

# 应用指定的 stash(不删除)
git stash apply stash@{1}

# 应用并删除指定的 stash
git stash pop stash@{1}

# 删除所有 stash
git stash clear

# 查看 stash 的内容
git stash show -p stash@{0}

小技巧: 你可以有多个 stash,就像游戏里的多个存档位。

1.3 git cherry-pick —— 精准移植,只要这一个

场景: 你在 feature 分支上提交了 10 个 commit,但只想把其中 1 个合并到 main。

用 merge?不行,会把所有 commit 都带过去。

用 cherry-pick!

# 先找到你想要的 commit 的 hash
git log --oneline
# a1b2c3d 修复登录 bug  ← 就要这个!
# e4f5g6h 添加新功能
# i7j8k9l 重构代码

# 切换到目标分支
git checkout main

# 把那个 commit "摘"过来
git cherry-pick a1b2c3d

# 完成!只有这一个 commit 被合并了

进阶用法:

# 一次 cherry-pick 多个 commit
git cherry-pick a1b2c3d e4f5g6h

# cherry-pick 一个范围(不包含起点)
git cherry-pick A..B

# cherry-pick 但不自动提交(想修改一下)
git cherry-pick --no-commit a1b2c3d

# 遇到冲突时,解决后继续
git cherry-pick --continue

# 放弃 cherry-pick
git cherry-pick --abort

第二章:调试神器——当你找不到 bug 的时候

2.1 git bisect —— 二分查找,揪出罪魁祸首

场景: 项目突然出 bug 了,但你不知道是哪个 commit 引入的。有 100 个 commit,一个个看?

别傻了,用 git bisect!

# 开始二分查找
git bisect start

# 标记当前版本是"坏的"
git bisect bad

# 标记一个已知"好的"版本(比如上周的)
git bisect good v1.0.0

# Git 会自动 checkout 到中间的 commit
# 你测试一下,告诉 Git 这个版本是好是坏

# 如果这个版本没问题:
git bisect good

# 如果这个版本有问题:
git bisect bad

# Git 会继续二分,直到找到第一个"坏"的 commit
# 最后会告诉你:
# a1b2c3d is the first bad commit

# 结束查找,回到原来的分支
git bisect reset

100 个 commit,最多只需要 7 次测试(log₂100 ≈ 7)就能找到问题!

自动化 bisect:

# 如果你有自动化测试,可以让 Git 自动运行
git bisect start HEAD v1.0.0
git bisect run npm test

# Git 会自动运行测试,自动标记 good/bad
# 最后告诉你哪个 commit 引入了 bug

2.2 git blame —— 谁写的这坨代码?

场景: 你看到一行奇怪的代码,想知道是谁写的、为什么这么写。

# 查看文件每一行的最后修改者
git blame src/utils.js

# 输出:
# a1b2c3d (张三 2024-01-15) function doSomething() {
# e4f5g6h (李四 2024-02-20)   // TODO: 这里有 bug
# i7j8k9l (王五 2024-03-10)   return null; // 临时修复
# ...

# 只看某几行
git blame -L 10,20 src/utils.js

# 忽略空白字符的修改
git blame -w src/utils.js

# 显示原始 commit(追溯代码移动)
git blame -C src/utils.js

注意: blame 不是用来"甩锅"的,是用来理解代码历史的。

找到作者后,可以问问他当时为什么这么写,也许有你不知道的原因。

2.3 git log 的高级用法

基础的 git log 太无聊了,试试这些:

# 一行显示,简洁明了
git log --oneline

# 图形化显示分支合并历史
git log --oneline --graph --all

# 搜索 commit 信息
git log --grep="bug"

# 搜索代码变更(谁动了这行代码?)
git log -S "function doSomething"

# 查看某个文件的历史
git log --follow -- src/utils.js

# 查看某个作者的提交
git log --author="张三"

# 查看某个时间段的提交
git log --since="2024-01-01" --until="2024-02-01"

# 格式化输出(适合写周报)
git log --pretty=format:"%h - %an, %ar : %s"
# 输出:a1b2c3d - 张三, 2 days ago : 修复登录 bug

我最喜欢的组合:

# 漂亮的分支图
git log --oneline --graph --all --decorate

# 输出类似:
# * a1b2c3d (HEAD -> main) 合并 feature 分支
# |\
# | * e4f5g6h (feature) 添加新功能
# | * i7j8k9l 重构代码
# |/
# * m1n2o3p 上一个版本

第三章:整理历史——让你的 commit 更专业

3.1 git rebase -i —— 交互式变基,重写历史

场景: 你的 commit 历史是这样的:

fix bug
fix bug again
fix bug again again
WIP
asdfasdf
真的修好了

想把它们整理成一个干净的 commit?

# 交互式变基最近 6 个 commit
git rebase -i HEAD~6

# 会打开编辑器,显示:
# pick a1b2c3d fix bug
# pick e4f5g6h fix bug again
# pick i7j8k9l fix bug again again
# pick m1n2o3p WIP
# pick q1r2s3t asdfasdf
# pick u1v2w3x 真的修好了

# 把 pick 改成 squash(或 s),合并到上一个 commit
# pick a1b2c3d fix bug
# s e4f5g6h fix bug again
# s i7j8k9l fix bug again again
# s m1n2o3p WIP
# s q1r2s3t asdfasdf
# s u1v2w3x 真的修好了

# 保存退出,Git 会让你编辑新的 commit 信息
# 最后变成一个干净的 commit:
# "修复用户登录 bug"

rebase -i 的其他操作:

# pick   = 保留这个 commit
# reword = 保留 commit,但修改 commit 信息
# edit   = 保留 commit,但停下来让你修改
# squash = 合并到上一个 commit,保留 commit 信息
# fixup  = 合并到上一个 commit,丢弃 commit 信息
# drop   = 删除这个 commit

⚠️ 警告: 不要对已经 push 到远程的 commit 做 rebase!会导致历史冲突。

3.2 git commit --amend —— 修改最后一个 commit

场景: 刚提交完,发现 commit 信息写错了,或者漏了一个文件。

# 修改最后一个 commit 的信息
git commit --amend -m "新的 commit 信息"

# 添加漏掉的文件到最后一个 commit
git add forgotten-file.js
git commit --amend --no-edit  # 不修改 commit 信息

# 修改作者信息
git commit --amend --author="张三 <zhangsan@example.com>"

这比"再提交一个 fix typo"优雅多了。

3.3 git reset 的三种模式

很多人只知道 git reset --hard,其实 reset 有三种模式:

# --soft:只移动 HEAD,保留暂存区和工作区
git reset --soft HEAD~1
# 效果:commit 撤销了,但代码还在暂存区,可以重新 commit

# --mixed(默认):移动 HEAD,重置暂存区,保留工作区
git reset HEAD~1
# 效果:commit 撤销了,代码在工作区,需要重新 add

# --hard:移动 HEAD,重置暂存区和工作区
git reset --hard HEAD~1
# 效果:commit 撤销了,代码也没了(但可以用 reflog 恢复)
┌─────────────────────────────────────────────────────────────┐
│                    git reset 三种模式对比                    │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│   原始状态:                                                 │
│   ┌──────────┐   ┌──────────┐   ┌──────────┐               │
│   │ 工作区   │ → │ 暂存区   │ → │ 仓库     │               │
│   │ (代码)   │   │ (staged) │   │ (commit) │               │
│   └──────────┘   └──────────┘   └──────────┘               │
│                                                             │
│   --soft:只动仓库                                          │
│   ┌──────────┐   ┌──────────┐   ┌──────────┐               │
│   │ 保留 ✓   │   │ 保留 ✓   │   │ 撤销 ✗   │               │
│   └──────────┘   └──────────┘   └──────────┘               │
│                                                             │
│   --mixed:动仓库和暂存区                                    │
│   ┌──────────┐   ┌──────────┐   ┌──────────┐               │
│   │ 保留 ✓   │   │ 撤销 ✗   │   │ 撤销 ✗   │               │
│   └──────────┘   └──────────┘   └──────────┘               │
│                                                             │
│   --hard:全部重置                                          │
│   ┌──────────┐   ┌──────────┐   ┌──────────┐               │
│   │ 撤销 ✗   │   │ 撤销 ✗   │   │ 撤销 ✗   │               │
│   └──────────┘   └──────────┘   └──────────┘               │
│                                                             │
└─────────────────────────────────────────────────────────────┘

第四章:协作技巧——和团队愉快地合作

4.1 git fetch vs git pull

很多人分不清 fetch 和 pull 的区别:

# git fetch:只下载远程的更新,不合并
git fetch origin

# git pull:下载远程的更新,并自动合并
git pull origin main
# 相当于:git fetch + git merge

为什么要用 fetch?

# 先 fetch,看看远程有什么更新
git fetch origin

# 查看远程分支的状态
git log origin/main --oneline

# 对比本地和远程的差异
git diff main origin/main

# 确认没问题后,再合并
git merge origin/main

这样更安全,不会被突然的合并搞懵。

4.2 git rebase vs git merge

两种合并方式,各有优劣:

# merge:创建一个合并 commit,保留完整历史
git checkout main
git merge feature

# 历史:
# * 合并 commit
# |\
# | * feature commit 2
# | * feature commit 1
# * main commit

# rebase:把 feature 的 commit "移动"到 main 后面,线性历史
git checkout feature
git rebase main
git checkout main
git merge feature  # 快进合并

# 历史:
# * feature commit 2
# * feature commit 1
# * main commit

什么时候用什么?

┌─────────────────────────────────────────────────────────────┐
│                    merge vs rebase 选择指南                  │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│   用 merge:                                                 │
│   ├─ 合并公共分支(main, develop)                          │
│   ├─ 需要保留完整的合并历史                                 │
│   ├─ 多人协作的分支                                         │
│   └─ 不确定的时候                                           │
│                                                             │
│   用 rebase:                                                │
│   ├─ 整理自己的本地分支                                     │
│   ├─ 想要干净的线性历史                                     │
│   ├─ 在 push 之前整理 commit                                │
│   └─ 个人 feature 分支                                      │
│                                                             │
│   ⚠️ 黄金法则:不要 rebase 已经 push 的 commit!             │
│                                                             │
└─────────────────────────────────────────────────────────────┘

4.3 git worktree —— 同时处理多个分支

场景: 你在 feature 分支开发,突然需要切到 main 修 bug,但不想 stash 当前的修改。

用 worktree!

# 创建一个新的工作目录,checkout 到 main 分支
git worktree add ../project-main main

# 现在你有两个目录:
# /project        → feature 分支(继续开发)
# /project-main   → main 分支(修 bug)

# 两个目录可以同时工作,互不影响!

# 修完 bug 后,删除 worktree
git worktree remove ../project-main

# 查看所有 worktree
git worktree list

这比来回切换分支、stash 代码方便多了。


第五章:实用小技巧

5.1 git clean —— 清理未跟踪的文件

# 查看哪些文件会被删除(预览)
git clean -n

# 删除未跟踪的文件
git clean -f

# 删除未跟踪的文件和目录
git clean -fd

# 删除被 .gitignore 忽略的文件
git clean -fX

# 删除所有未跟踪的文件(包括被忽略的)
git clean -fdx

⚠️ 小心使用!删除后无法恢复。

5.2 git diff 的高级用法

# 查看工作区和暂存区的差异
git diff

# 查看暂存区和最后一次 commit 的差异
git diff --staged

# 查看两个分支的差异
git diff main feature

# 只看文件名
git diff --name-only

# 查看某个文件的差异
git diff -- src/utils.js

# 忽略空白字符
git diff -w

# 单词级别的差异(更精确)
git diff --word-diff

5.3 git alias —— 自定义快捷命令

懒人必备!

# 设置别名
git config --global alias.co checkout
git config --global alias.br branch
git config --global alias.ci commit
git config --global alias.st status

# 现在可以这样用:
git co main      # = git checkout main
git br           # = git branch
git ci -m "msg"  # = git commit -m "msg"
git st           # = git status

# 更复杂的别名
git config --global alias.lg "log --oneline --graph --all --decorate"
git config --global alias.last "log -1 HEAD"
git config --global alias.unstage "reset HEAD --"

# 使用
git lg           # 漂亮的分支图
git last         # 查看最后一次 commit
git unstage file # 取消暂存

5.4 .gitignore 的高级用法

# 忽略所有 .log 文件
*.log

# 但不忽略 important.log
!important.log

# 忽略 build 目录
build/

# 忽略所有目录下的 node_modules
**/node_modules/

# 忽略根目录下的 TODO 文件,但不忽略子目录的
/TODO

# 忽略所有 .txt 文件,除了 doc 目录下的
*.txt
!doc/**/*.txt

第六章:Git 命令速查表

┌─────────────────────────────────────────────────────────────────────────┐
│                        Git 高级命令速查表                                │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                         │
│  🆘 救命命令                                                             │
│  ├─ git reflog           查看所有操作历史,恢复"丢失"的 commit          │
│  ├─ git stash            临时保存修改                                   │
│  ├─ git cherry-pick      精准移植某个 commit                            │
│  └─ git reset --soft     撤销 commit 但保留代码                         │
│                                                                         │
│  🔍 调试命令                                                             │
│  ├─ git bisect           二分查找引入 bug 的 commit                     │
│  ├─ git blame            查看每行代码的最后修改者                       │
│  └─ git log -S "code"    搜索代码变更历史                               │
│                                                                         │
│  📝 整理历史                                                             │
│  ├─ git rebase -i        交互式变基,整理 commit                        │
│  ├─ git commit --amend   修改最后一个 commit                            │
│  └─ git reset            撤销 commit(三种模式)                        │
│                                                                         │
│  🤝 协作命令                                                             │
│  ├─ git fetch            只下载不合并                                   │
│  ├─ git rebase           变基合并(线性历史)                           │
│  └─ git worktree         同时处理多个分支                               │
│                                                                         │
│  🧹 清理命令                                                             │
│  ├─ git clean -fd        删除未跟踪的文件和目录                         │
│  └─ git gc               垃圾回收,优化仓库                             │
│                                                                         │
└─────────────────────────────────────────────────────────────────────────┘

结语:从"会用"到"精通"

今天我们学习了 12 个 Git 高级命令:

  1. git reflog —— 后悔药,恢复"丢失"的 commit
  2. git stash —— 临时存档,随时切换
  3. git cherry-pick —— 精准移植某个 commit
  4. git bisect —— 二分查找 bug
  5. git blame —— 查看代码历史
  6. git log 高级用法 —— 搜索和格式化
  7. git rebase -i —— 整理 commit 历史
  8. git commit --amend —— 修改最后一个 commit
  9. git reset 三种模式 —— 灵活撤销
  10. git fetch vs pull —— 安全更新
  11. git worktree —— 同时处理多个分支
  12. git alias —— 自定义快捷命令

记住:Git 是一个强大的工具,但强大意味着复杂。

不要试图一次学会所有命令,先掌握最常用的几个,遇到问题再查。

最重要的是:永远不要在不确定的时候用 --force


附录:Git 急救指南

遇到问题怎么办?

Q: 我不小心 commit 了,想撤销
A: git reset --soft HEAD~1

Q: 我不小心 push 了,想撤销
A: git revert HEAD(创建一个撤销的 commit)
   不要用 reset + force push!

Q: 我的代码不见了
A: git reflog,找到之前的 commit,git reset --hard

Q: 我想放弃所有本地修改
A: git checkout .(放弃工作区修改)
   git reset --hard HEAD(放弃所有修改)

Q: 合并冲突了怎么办
A: 手动解决冲突 → git add . → git commit
   或者 git merge --abort 放弃合并

Q: rebase 冲突了怎么办
A: 解决冲突 → git add . → git rebase --continue
   或者 git rebase --abort 放弃 rebase

如果你觉得这篇文章有用,请分享给你那个还在"删库重来"的同事。

也许他需要知道:Git 比你想象的更强大,也更宽容。 🎯


最后,送给所有程序员一句话:

“Git 不会真正丢失你的代码,除非你真的很努力地想丢掉它。” 😄

愿你的 commit 永远干净,merge 永远顺利。

Logo

腾讯云面向开发者汇聚海量精品云计算使用和开发经验,营造开放的云计算技术生态圈。

更多推荐