文章列表
7 分钟阅读

Git 进阶:你迟早要会的几个救急命令


系列:版本控制基础

第 2 / 8 篇

  1. Git 安装与使用教程:Linux 和 Windows 从零开始版本管理
  2. Git 进阶:你迟早要会的几个救急命令
  3. Git 分支管理指南:main、dev、feature、release 怎么用
  4. Git Commit 规范:怎么写清楚提交信息
  5. GitHub Pull Request 工作流入门
  6. GitHub Actions 入门:为博客加自动检查
  7. Git 冲突解决指南:merge、rebase、cherry-pick 冲突怎么处理
  8. GitHub Actions 自动部署实战:从检查到发布的完整流程

Git 安装与使用教程:Linux 和 Windows 从零开始版本管理 讲了 Git 的基础操作——add、commit、push、pull、branch。这些够你日常用了。

但这篇要聊的东西不一样——它们不是每天都要敲的命令,但需要用的时候不知道,会浪费你大量时间。我一共挑了十来个场景,每个都附上可复现的命令示例。

git stash:临时存一下手头的工作

你正写一半代码,突然要切分支修个线上问题,但当前的改动还没到可以 commit 的程度。这时候 stash 就是你的救星。

Terminal window
git stash # 暂存所有改动,工作区恢复干净
git stash list # 看存了多少个
git stash pop # 恢复最近一个并删除记录
git stash apply # 恢复最近一个但保留记录
git stash drop stash@{0} # 手动删掉某个 stash

给 stash 加个名字,不然堆多了根本分不清:

Terminal window
git stash push -m "WIP: 修登录页样式"

如果你只改了几个文件,想挑特定文件 stash:

Terminal window
git stash push -m "只存前端" src/components/Login.tsx

什么时候用:频繁在分支间跳来跳去,改动还没成型不想 commit。

:别让 stash 堆几个月——它不跟分支走,时间一长很容易忘了是什么。我习惯每周 git stash list 看一圈,把没用的清掉。

git bisect:二分法定位 bug 是哪次提交引入的

有个 bug 你确定三个月前没有,但不知道哪次改动引入的。bisect 用二分查找帮你自动定位到具体哪个 commit。

Terminal window
git bisect start
git bisect bad HEAD # 告诉 git:当前版本有问题
git bisect good abc1234 # 告诉 git:这个 commit 是好的

Git 会自动跳到中间某个 commit,你测试一下:

Terminal window
# 如果这个版本有问题
git bisect bad
# 如果没问题
git bisect good

如此反复五六次,Git 就能定位到引入 bug 的具体 commit。整个过程通常不超过十分钟。

Terminal window
git bisect reset # 结束 bisect,回到原来的 HEAD

自动化:如果可以写个脚本判断 bug 是否存在(比如跑个测试),那更省事:

Terminal window
git bisect run pytest tests/test_login.py

什么时候用:一个稳定的功能突然坏了,但最近一个月改了几十个 commit,不知道是哪个。

git reflog:救回你刚刚”删除”的东西

reflog 是 Git 的后悔药——它记录了你本地仓库所有 HEAD 的移动历史。误 reset、误 rebase、误删分支之后,大多数情况能靠它救回来。

Terminal window
git reflog

输出大概长这样:

abc1234 HEAD@{0}: commit: 修了一个 typo
def5678 HEAD@{1}: rebase (finish): ...
ghi9012 HEAD@{2}: reset: moving to HEAD~3
jkl3456 HEAD@{3}: checkout: moving from main to feature-x

每一条记录都有一个引用号。想跳回去:

Terminal window
git reset --hard HEAD@{2} # 回到 reset 之前的状态

误删了分支:

Terminal window
git branch -D feature-x # 手滑删了
# 从 reflog 里找 feature-x 最后指向的 commit
git reflog
# 重建分支
git branch feature-x abc1234

注意:reflog 只存在本地,记录了最近 90 天的操作。push 到远程的不在 reflog 里。

git rebase -i:整理提交历史

rebase -i(交互式 rebase)可以对 commit 做任意操作——合并、拆分、重新排序、改 message。最有用的两个场景:

场景 1:合并零碎提交

你一天之内 commit 了五六次,都是修同一个功能的小改:

Terminal window
git rebase -i HEAD~6

会弹出编辑器:

pick abc1111 开始做功能
pick abc2222 又改了一点
pick abc3333 修 bug
pick abc4444 再修
pick abc5555 删掉没用的代码
pick abc6666 还是改回来吧

改成:

pick abc1111 开始做功能
squash abc2222 又改了一点
squash abc3333 修 bug
squash abc4444 再修
squash abc5555 删掉没用的代码
squash abc6666 还是改回来吧

保存退出后,六个 commit 合并成一个。

场景 2:重新排序

想让两个 commit 互换位置,直接编辑顺就行:

pick abc3333 修 bug ← 挪到前面
pick abc1111 开始做功能

场景 3:拆一个 commit

pick 改成 edit,这个 commit 应用后 Git 会暂停,让你:

Terminal window
git reset HEAD^ # 撤销这个 commit 但保留改动
git add fileA.js
git commit -m "第一部分"
git add fileB.js
git commit -m "第二部分"
git rebase --continue # 继续

原则:rebase 完 git log --oneline 应该是干净的、每个 commit 做一件明确的事。但已经 push 给别人用的分支别 rebase——会把别人的历史搞乱。

git cherry-pick:把单个 commit 搬到另一个分支

有时候你不需要 merge 整个分支——你只想要其中一个 commit。

Terminal window
git cherry-pick abc1234

Git 会把这个 commit 的改动应用到当前分支上。如果想保留原始 commit 的 author 和时间:

Terminal window
git cherry-pick -x abc1234 # -x 会在提交信息里附带原始 commit hash

连续搬多个:

Terminal window
git cherry-pick abc1234 def5678

搬运一个范围:

Terminal window
git cherry-pick abc1234..def5678 # 左开右闭

什么时候用:修 bug 的 commit 在 feature 分支上,想把它也搬到 main。或者从实验分支里提取一个工具函数。

git worktree:同时切多个分支

正常情况下一个 repo 同时只能在一个分支上工作。想同时修改两个分支,要么 stash 切来切去,要么 clone 两份。worktree 让你在一个 repo 里打开多个工作目录。

Terminal window
git worktree add ../project-hotfix hotfix-branch

这会创建一个新目录 ../project-hotfix,里面是 hotfix-branch 的内容。你就可以同时写 feature 和修 hotfix,互不干扰。

Terminal window
git worktree list # 看所有 worktree
git worktree remove ../project-hotfix # 用完删掉

如果有未提交的改动不想丢,可以基于一个 stash 创建临时 worktree:

Terminal window
git worktree add --detach ../temp-fix
cd ../temp-fix
git stash pop
# 干完活 git add && git commit
# 然后回到原目录继续

什么时候用:正在写一个大功能,突然线上出了个 hotfix。或者在编译/跑测试的间隙想切到其他分支继续干活。

git log 和 git diff 的高级用法

git log 不止 --oneline

Terminal window
git log --oneline --graph --all # 可视化看全部分支结构
git log --since="2 weeks ago" --author="张三" # 按时间和人过滤
git log -S"函数名" # 找哪些 commit 增删了这个函数
git log --grep="JIRA-123" # 按 commit message 搜索
git log -- path/to/file # 只看某个文件的改动历史

git diff 也很能打:

Terminal window
git diff --staged # 看暂存区改了什么
git diff branchA...branchB # 只看 B 相对 A 的增量
git diff --word-diff # 按单词显示差异,不是按行
git show abc1234 # 看某个 commit 的完整改动

git blame 不是用来追责的

git blame 看每行代码是谁、哪次 commit 引入的。名字不太好听,但它不是甩锅工具——是用来理解代码上下文。

Terminal window
git blame src/app.py
git blame -L 50,100 src/app.py # 只看 50-100 行
git blame -w -C src/app.py # 忽略空白变化,检测行是否从其他文件移动过来的

看到不明白的代码,blame 可以帮你定位到原始 commit,然后 git show 看那个 commit 的完整背景。

清理和优化仓库

git gc

Git 会自动 gc(垃圾回收),但如果你刚做了大量的 rebase 或删了大文件,可以手动跑:

Terminal window
git gc --aggressive # 深度压缩,少跑

别经常跑。大型仓库 gc 一次要很久,Git 自己定期做就够了。

git clean

删掉所有未跟踪的文件(新建了还没 add 的文件):

Terminal window
git clean -n # 先看看会删啥,安全第一
git clean -fd # 删文件 + 目录

撤销某个文件的改动

还没 commit 的文件不想要了:

Terminal window
git checkout -- path/to/file # 恢复到最近一次 commit 的状态
git restore path/to/file # 新写法,更语义化

git commit —amend:改最后一个 commit

提完了发现忘记加一个文件,或者 commit message 写错了:

Terminal window
git add 忘掉的文件.py
git commit --amend -m "新的 commit message"

如果什么都不改只想改消息:

Terminal window
git commit --amend --no-edit -m "更好的描述"

绝对不要 amend 已经 push 到远程的 commit——除非只有你一个人用这个分支。否则同事 pull 的时候会报冲突。

常见踩坑场景

已经 push 的 commit 想撤回

Terminal window
git revert abc1234

revert 创建一个新的”反向 commit”,把目标 commit 的改动撤销。比 reset + --force push 安全一万倍——它不改历史,同事不会炸。

rebase 搞砸了

Terminal window
git reflog # 找到 rebase 之前的 HEAD
git reset --hard @{1} # 回到 rebase 前

rebase 过程中也可以随时 git rebase --abort

merge 完后悔了

还没 push 的话:

Terminal window
git reset --hard HEAD~1

已经 push 了的话用 revert,但 revert merge commit 比较特殊:

Terminal window
git revert -m 1 abc1234

-m 1 意思是”回到第一个 parent 的状态”,通常就是回到 main 分支在 merge 之前的状态。

不小心 commit 了敏感信息

比如把密码、API key 提交了:

  1. 如果刚 commit 还没 pushgit reset HEAD~1 重新来。
  2. 已经 push 了:改密码,然后 git rebase -i 把那个 commit 删掉,force push。
  3. 很久以前 commit 的:用 git filter-branch 或 BFG Repo-Cleaner。但本质上是改历史,全组同事要重新 clone。

核心原则:密码一 push 就泄露了,改密码比删历史更重要

总结

这些命令不是用来炫技的。把它们的场景串起来大概是:

  • 遇到 bug 不知道什么时候引入的git bisect
  • 手滑 reset/删分支/搞砸 rebasegit reflog
  • commit 记录太乱想整理git rebase -i
  • 正在写功能突然要修线上问题git stash 临时保存,或 git worktree 双开
  • 想把一个 commit 搬到另一个分支git cherry-pick
  • commit 之后发现少加了一个文件git commit --amend
  • push 完发现搞错了git revert,别 reset --hard + --force

Git 真正厉害的地方不是命令本身,而是每条改动都有迹可循,随时能回退。理解了这一点,很多操作不用死记格式,知道它能做到就行了,需要的时候 git help <command> 查具体参数。