Git 命令速查
按"我现在想做什么"组织的命令表。撤销部分建议先读,因为大多数 Git 事故都发生在分不清工作区、暂存区和提交这三个位置的时候。
一次性配置
新机器上先做这几件事。配置写在 ~/.gitconfig,对所有仓库生效。
git config --global user.name "Your Name"
git config --global user.email "you@example.com"
git config --global init.defaultBranch main
git config --global core.editor "vim"
git config --global pull.ff only # 拉取时只允许快进,避免意外的合并提交
git config --global core.autocrlf input # macOS / Linux;Windows 上用 true
pull.ff only 值得单独说一句:默认的 git pull 在历史分叉时会直接生成一个合并提交,而且不给你看合并结果。设成 only 之后,分叉时它会报错停下,逼你显式选择 git pull --rebase 还是 git merge。这个"失败"比默默合并有用。
常用别名可以省不少打字,但别把别名写进团队文档——别人机器上没有。
git config --global alias.st status
git config --global alias.co checkout
git config --global alias.lg "log --oneline --graph --decorate --all"
每天用到的那几条
git status -s # 短格式:?? 未跟踪, M 已修改,M 已暂存
git add -p # 逐块挑选,而不是整个文件一起提交
git diff # 工作区 vs 暂存区
git diff --staged # 暂存区 vs 最后一次提交
git commit -m "fix: 修正空指针判断"
git log --oneline -10
git show HEAD --stat
git add -p 是让提交历史保持干净的最省力办法:一个文件里混着两件不相关的事时,它让你只挑其中一块进本次提交。它给出的 y/n/s/q 里,s 是再拆小块,e 是直接手改要提交的行。
撤销:先判断改动在哪一层
几乎所有"怎么撤销"的问题,答案都取决于改动目前处在哪一层。按下面的表从上往下找。
| 现状 | 目的 | 命令 |
|---|---|---|
| 工作区已改,未暂存 | 丢弃这些改动 | git restore <文件> |
已 add,未提交 |
退回工作区,保留改动 | git restore --staged <文件> |
| 已提交,未推送 | 改提交信息 / 补一个漏掉的文件 | git commit --amend |
| 已提交,未推送 | 整个提交不要了,改动也丢 | git reset --hard HEAD~1 |
| 已推送 | 撤销这次提交的影响 | git revert <sha> |
三条 reset 的区别只有一句话:--soft 只移动分支指针,改动全留在暂存区;--mixed(默认)连暂存区一起清,改动留在工作区;--hard 三处一起清。想"把最近三次提交合成一次重新写",用 git reset --soft HEAD~3 然后重新提交,比交互式变基快。
git revert 是唯一适合处理已推送历史的方式。它生成一个新提交来抵消旧提交,历史保持不变,别人拉取时不会出问题。reset --hard 加 push --force 会改写别人已经拿到的历史,除非你确认这个分支只有你一个人在用,否则不要这么做。
撤销一个合并提交要指定保留哪一边,否则 Git 不知道该沿哪条线继续:
git revert -m 1 <merge-commit-sha> # 保留第一父提交(即合并前所在的分支)
分支
git switch -c feature/login # 新建并切换(Git 2.23+)
git switch main
git branch -vv # 列出分支及其上游、领先/落后数量
git branch -d feature/login # 已合并才能删;未合并用 -D 强删
git branch --merged # 哪些分支已经合进当前分支
checkout 同时负责切分支和还原文件,容易误操作,所以 2.23 之后拆成了 switch(切分支)和 restore(还原文件)。旧脚本里还能用 checkout,新写的命令建议用新的两个。
合并还是变基
merge | rebase | |
|---|---|---|
| 历史形状 | 保留分叉,多一个合并提交 | 线性,像从来没分叉过 |
| 提交哈希 | 不变 | 全部重写 |
| 适合 | 已推送的公共分支、需要保留合并点 | 本地未推送的整理、跟进上游 |
| 风险 | 历史较乱 | 冲突要逐个提交解决 |
经验规则:只对还没推送的提交做变基。已经推送的分支上做变基,等于要求所有协作者重置本地分支。
冲突处理
git status # 冲突文件列在 "Unmerged paths"
# 打开文件,处理 <<<<<<< ======= >>>>>>> 三段
git add <冲突文件>
git merge --continue # 或 git rebase --continue
git merge --abort # 放弃,回到合并前
冲突标记的三段分别是"当前分支的版本"、"分隔线"、"被合入分支的版本"。如果只是想让某一侧赢,git checkout --ours <文件> 或 --theirs 可以整文件取一边,然后再 git add。注意在变基过程中 ours 和 theirs 的含义是反的——这是新手最容易搞混的一处。
找历史
知道"这句话是哪次提交加进来的",比逐条翻 log 快得多。
git log -S'retry_limit' --oneline # 某段字符串出现或消失的提交
git log -G'def .*handler' -p -- '*.py' # 按正则匹配 diff 内容
git log --follow -p -- path/to/file # 跟随重命名
git blame -L 20,40 -- path/to/file # 指定行范围的最后修改者
git log -L 20,40:path/to/file # 这几行的演变过程
找"哪次提交弄坏了"用二分查找,不要靠猜:
git bisect start
git bisect bad # 当前版本有问题
git bisect good v1.4.0 # 这个 tag 是好的
# Git 每次检出中间版本,测完标记:
git bisect good # 或 git bisect bad
git bisect reset # 结束后回到原分支
如果有能自动判断的测试脚本,可以全自动跑完:git bisect run ./test.sh。脚本退出码 0 视为 good,1 视为 bad,125 表示跳过这个版本。
后悔药:reflog
分支被 reset --hard 掉、提交"消失"了,只要没被垃圾回收,都还能找回来。reflog 记录的是 HEAD 的每一次移动:
git reflog # 最近 90 天的 HEAD 移动记录
git reset --hard HEAD@{3} # 回到 reflog 里的第 3 条状态
git switch -c rescue HEAD@{3} # 更安全的做法:新建分支接住它
默认保留 90 天(不可达对象 30 天),gc 之后才真正删除。所以误删分支后的第一件事是别再乱敲命令,先看 reflog。
远程
git remote -v
git fetch --prune # 拉取并清掉远端已删除的分支引用
git switch -c feature origin/feature
git push -u origin HEAD # 首次推送并设置上游
git push --force-with-lease # 必须强推时用它,而不是 --force
--force-with-lease 会在远端有你没见过的提交时拒绝推送,避免把别人的工作覆盖掉。--force 什么都不检查,只在"这个分支确实只有我一个人用"时才可以考虑。
临时存放
git stash push -m "wip: 表单校验"
git stash list
git stash pop # 取出并删除这条
git stash apply stash@{1} # 取出但保留
git stash show -p stash@{0}
stash 默认不包含未跟踪文件,需要时加 -u。它也不是长期存储:stash 里放了几周的东西基本会被忘掉,超过一天的改动更适合开一个临时分支提交上去。
常见坑
1. .gitignore 对已跟踪的文件无效
文件一旦进过版本库,再加进 .gitignore 也不会被忽略。要先从索引里移除(本地文件保留):
git rm --cached config.local.json
git commit -m "chore: 停止跟踪本地配置"
2. 分离头指针(detached HEAD)
直接 git checkout <某个提交> 会进入分离头指针状态,这时的提交不属于任何分支,切走之后很容易丢。在这个状态下想保留工作,先 git switch -c <新分支名>。
3. 空目录不会被跟踪
Git 只跟踪文件。需要保留目录结构时,放一个空的 .gitkeep 进去(名字没有特殊含义,只是约定)。
4. 换行符
Windows 用 CRLF,其他平台用 LF。core.autocrlf 设错会让整个文件显示为"全部修改"。仓库里放一个 .gitattributes 更可靠:
* text=auto eol=lf
*.png binary
5. 把密钥提交上去了
即使下一次提交删掉,密钥仍然留在历史里,git log -p 能翻出来。正确顺序是:先去作废并重新生成那个凭据,再考虑清理历史(filter-repo 之类的工具会重写全部哈希,需要所有人重新克隆)。历史清理不能替代作废。
6. 大文件进了历史
仓库体积只增不减,删掉文件也不会变小,因为对象还在。要么用 git gc --prune=now 配合历史重写,要么一开始就用 .gitignore 挡住构建产物和数据集。