1. 为什么“背命令”这条路从一开始就走不通刚入行那会儿我也干过把git常用命令抄在便签纸上贴显示器边框的事。git add、git commit、git push三件套背得滚瓜烂熟觉得自己已经掌握了版本控制。直到第一次遇到合并冲突满屏的 HEAD让我盯着终端愣了十分钟最后靠删掉整个文件夹重新克隆才把活干完。那次之后我才明白git指令大全这种东西如果只是罗列命令和参数那跟背字典没什么区别——你记住了rebase这个单词但根本不知道什么时候该用它、用了之后会发生什么、出了问题怎么退回来。这篇内容想聊的不是再给你一份冷冰冰的命令清单。网上随便一搜git命令列表能拉出几百行参数说明比官方文档还长。但真正干活的时候你会发现卡住你的从来不是“不知道有这个命令”而是“不知道现在这个局面该用哪个命令、用了之后仓库会变成什么样、搞砸了怎么救”。所以我打算换个思路按实际工作场景来拆——你遇到什么问题该用什么工具背后的逻辑是什么以及我踩过的那些坑。适合谁看如果你已经会基本的add、commit、push但遇到分支合并、历史修改、误操作恢复这些事就心里发虚那这篇内容就是写给你的。如果你完全没接触过git建议先找个沙盒仓库把基础流程跑一遍再回来不然有些场景你很难有体感。全文会涉及大量命令但重点永远在“为什么”和“什么时候”上而不是“这个参数有几个取值”。提示下面所有操作建议都在一个专门用来折腾的测试仓库里先跑一遍。git的大部分操作在本地是可逆的但涉及push --force和reset --hard的时候手滑的代价可能是几小时的活白干。2. 仓库初始化与日常提交那些没人告诉你但天天在用的细节2.1git init之后第一件事不是写代码很多人git init完就急着git add .然后git commit -m init。这个流程本身没错但漏了一步确认你的忽略规则。我见过太多仓库因为一开始没配.gitignore把node_modules、__pycache__、.DS_Store甚至本地配置文件全提交进去了。等到发现的时候这些文件已经在历史里躺了几十个提交想清理就得动filter-branch或者filter-repo那复杂度直接上一个量级。正确的顺序应该是git init→ 创建.gitignore→ 写几行代码 →git add→git commit。.gitignore的语法不复杂但有几个容易忽略的点。比如*.log会忽略所有目录下的日志文件但!important.log可以把它排除在外build/忽略整个目录而build不加斜杠会同时匹配文件和目录。还有一个坑如果你已经git add了某个文件再把它加进.gitignore是不生效的得先用git rm --cached file把它从暂存区拿掉。# 典型的 .gitignore 开头 node_modules/ dist/ *.log .env .DS_Store2.2git add的三种粒度和一个后悔药git add最常用的形式是git add .和git add file但实际工作中经常遇到“我只想提交这个文件里的一部分改动”的情况。比如你改了一个配置文件里面既有格式调整又有功能变更想分成两个提交。这时候git add -p就是神器。它会逐个代码块问你“要不要暂存这一段”你可以按y暂存、n跳过、s把大块拆成更小的块、e手动编辑。我第一次用的时候觉得这操作太 geek 了但用过几次之后就离不开了因为它能让你的提交历史干净得像教科书。另一个容易忽略的是git add -u。它只暂存已经被跟踪的文件的修改和删除不会把新文件加进去。这个在你想“只提交对已有文件的改动先不管新写的草稿”时特别有用。至于后悔药git restore --staged file可以把文件从暂存区撤回到工作区但保留你的修改。注意这个命令在旧版本里叫git reset HEAD file现在两种写法都支持但restore语义更清晰。2.3git commit的信息不是写给现在的你看的我见过太多commit message写“fix bug”、“update”、“修改”的仓库。这种提交历史三个月后连作者自己都看不懂。好的提交信息应该回答两个问题这个提交做了什么以及为什么这么做。格式上我习惯用“动词开头 简短描述”作为标题行空一行然后写详细说明。比如修复用户登录时 token 过期未刷新的问题 之前的逻辑是在请求拦截器里判断 401 后直接跳转登录页 但这样会丢失用户当前的操作上下文。现在改为先尝试刷新 token刷新失败再跳转并在跳转前保存当前路由。git commit还有一个--amend参数用来修改最近一次提交。这个在“提交完发现漏了一个文件”或者“提交信息打错字”的时候非常方便。但要注意如果这次提交已经推送到远程amend之后推送需要强制覆盖在多人协作的分支上要格外小心。2.4git status和git diff的组合拳git status告诉你哪些文件变了git diff告诉你具体变了什么。但很多人不知道git diff有几个变体git diff看工作区和暂存区的差异git diff --staged看暂存区和最后一次提交的差异git diff HEAD看工作区和最后一次提交的差异。这三个的区别在提交前检查的时候特别重要——你git add之后想确认“我到底暂存了什么”就得用--staged。还有一个实用技巧git diff --stat只显示每个文件的改动行数统计不显示具体内容。在改动文件很多的时候先看一眼统计再决定要不要细看某个文件。3. 分支与合并从“不敢切分支”到“随便切”的跨越3.1 分支的本质是一个可移动的指针很多人对分支有心理负担觉得“切分支”是个大动作。其实在git里分支就是一个指向某个提交的指针创建和切换的成本极低。git branch name创建一个新分支git switch name切过去整个过程在本地瞬间完成不涉及任何网络操作。理解这一点之后你就会明白为什么git鼓励“每个功能开一个分支”——因为成本几乎为零。git switch是较新的命令旧版本里用git checkout。两者的区别在于checkout还能用来恢复文件语义太杂所以后来拆出了switch和restore。如果你用的是比较新的git版本建议养成用switch的习惯。# 创建并切换到新分支 git switch -c feature/login # 查看所有分支包括远程 git branch -a # 删除已合并的分支 git branch -d feature/login # 强制删除未合并的分支慎用 git branch -D feature/login3.2merge和rebase的选择不是信仰问题关于merge和rebase的争论网上能吵出几千楼。我的看法是它们解决的是不同的问题没有绝对的好坏。merge会创建一个新的合并提交保留完整的分支历史rebase会把你的提交“搬”到目标分支的最新提交之后历史变成一条直线。什么时候用merge当你想保留“这个功能是在某个时间点从主分支分出去开发的”这个信息时。什么时候用rebase当你想让提交历史更干净、更容易阅读时。我个人的习惯是在本地开发分支上用rebase同步主分支的更新在把功能分支合入主分支时用merge --no-ff保留合并记录。rebase有一个黄金法则不要对已经推送到远程的公共分支做rebase。因为rebase会重写提交历史别人基于旧历史做的工作会对不上。如果你rebase了公共分支别人拉取的时候就会遇到一堆莫名其妙的冲突。3.3 冲突不是灾难是信息合并冲突的本质是git不知道你想保留哪边的改动。它把两边的代码都标出来让你自己决定。冲突标记长这样 HEAD 这是当前分支的代码 这是要合并进来的分支的代码 feature/other解决冲突的步骤很直接找到所有冲突标记决定保留哪部分或者两部分都保留但调整顺序删掉标记行然后git add标记为已解决。但实际工作中冲突往往比这复杂——可能涉及十几个文件每个文件里有多处冲突。这时候git status会列出所有冲突文件git diff可以看具体冲突内容。我踩过的一个坑是解决完冲突后忘了git add直接git commit结果git提示还有未解决的冲突。后来才知道git add这个动作不仅是暂存也是在告诉git“这个文件的冲突我已经处理完了”。3.4cherry-pick只想要那一个提交有时候你在一个分支上做了五个提交但只想把其中一个搬到另一个分支。这时候git cherry-pick commit-hash就派上用场了。它会把这个提交的改动“复制”到当前分支生成一个新的提交。这个命令在“紧急修复要同步到多个版本分支”的场景下特别有用。但cherry-pick也有坑如果目标分支已经包含了这个提交的改动比如之前合并过cherry-pick会产生冲突或者空提交。另外cherry-pick出来的提交和原提交的哈希值不同所以如果后续要合并原分支可能会遇到重复改动的问题。4. 历史修改与误操作恢复那些让你心跳加速的时刻4.1reset的三种模式软、混合、硬git reset是误操作恢复的核心工具但它有三个模式用错了后果不同。--soft只移动HEAD指针暂存区和工作区都不动--mixed默认移动HEAD并重置暂存区但工作区不动--hard三者全重置工作区的修改直接丢失。# 撤销最近一次提交但保留改动在暂存区 git reset --soft HEAD~1 # 撤销最近一次提交改动留在工作区未暂存 git reset HEAD~1 # 彻底丢弃最近一次提交和所有改动危险 git reset --hard HEAD~1我个人的经验是--soft和--mixed可以随便用因为改动还在最多重新提交一次。但--hard一定要在确认工作区没有未提交的重要改动时再用。如果不小心--hard了还有一个救命稻草git reflog。4.2reflog你的时间机器git reflog记录了HEAD的每一次移动包括提交、重置、合并、切换分支等等。即使你reset --hard删掉了提交只要reflog里还有记录就能找回来。用法很简单git reflog看列表找到你想回到的那个状态对应的哈希值然后git reset --hard hash或者git cherry-pick hash。但reflog不是永久的。默认情况下git会保留 90 天的reflog记录过期会被清理。所以如果误操作之后过了很久才发现可能就找不回来了。另外reflog是本地记录不会随着push同步到远程。4.3stash临时保存工作区正在写一个功能突然来了个紧急 bug 要修但当前代码还没写完不想提交。这时候git stash可以把工作区和暂存区的改动“存起来”让工作区回到干净状态。修完 bug 提交后再用git stash pop把改动恢复回来。stash有几个实用参数git stash -u会把未跟踪的文件也存起来git stash list查看所有 stash 记录git stash apply stash{2}恢复指定的 stash 但不删除记录git stash drop stash{2}删除指定记录。我习惯用apply而不是pop因为pop在恢复失败时可能会让 stash 记录消失而apply更安全。4.4 恢复被删除的文件和提交如果误删了一个文件还没提交git restore file可以从最后一次提交里恢复。如果已经提交了删除操作可以用git revert commit创建一个“反向提交”来撤销删除。revert和reset的区别在于revert不修改历史而是新增一个提交来抵消之前的改动所以在公共分支上更安全。还有一种情况你不小心把某个分支删了但那个分支上有还没合并的提交。如果记得分支名可以用git reflog找到分支最后一次指向的提交然后git branch name hash重建分支。5. 远程协作推送、拉取与那些让人抓狂的报错5.1push被拒绝的三种常见原因git push被拒绝是新手最常遇到的挫败之一。报错信息通常长这样! [rejected] main - main (non-fast-forward)。原因很简单远程分支有你本地没有的提交git不允许你直接覆盖。解决办法是先git pull把远程改动拉下来合并再push。但pull本身也可能出问题。如果本地有未提交的改动pull会报错说“Your local changes would be overwritten”。这时候要么先stash要么先commit。我个人的习惯是在pull之前永远保持工作区干净要么提交要么 stash这样能避免很多不必要的麻烦。还有一种拒绝是权限问题报错里会出现Permission denied或403。这通常意味着你没有目标仓库的写权限或者认证信息过期了。检查一下远程地址和凭证配置。5.2fetch和pull的区别git fetch只把远程的改动下载到本地但不合并到当前分支。git pull等于fetchmerge或者rebase取决于配置。我推荐在pull之前先fetch看一眼远程有什么变化比如git fetch origin然后git log HEAD..origin/main --oneline看看多了哪些提交。这样心里有数之后再决定是merge还是rebase。git pull --rebase是很多人喜欢的配置它会把你的本地提交“搬”到远程最新提交之后保持历史直线。但这个操作在本地有未推送的合并提交时可能会出问题所以用之前最好确认一下本地历史。5.3 远程分支的跟踪与清理git branch -vv可以查看本地分支和远程分支的跟踪关系。如果某个本地分支没有对应的远程分支或者跟踪关系断了可以用git branch -u origin/main重新设置。远程分支被删除后本地的origin/main引用不会自动消失可以用git remote prune origin清理掉那些已经不存在的远程分支引用。还有一个场景你push了一个新分支但后来发现名字打错了想删掉远程分支。命令是git push origin --delete branch-name。注意这个操作在有些仓库配置下可能被保护规则阻止需要管理员权限。5.4 多人协作时的提交策略在多人协作的分支上我强烈建议养成“先拉后推”的习惯。每次push之前先pull --rebase把自己的提交放到最新代码之后。这样能最大程度减少合并提交让历史更干净。如果遇到冲突在本地解决完再推而不是把冲突推给别人。另外功能分支的生命周期不要太长。一个分支如果活了两周还没合并大概率会和主分支产生大量冲突。我习惯把大功能拆成小提交分多次合并每次合并的冲突量都可控。6. 排查问题的固定套路从“懵了”到“有章法”6.1 先看状态再看差异最后看历史遇到任何git问题我的第一步永远是git status。它会告诉你当前在哪个分支、有哪些改动、有没有冲突、有没有未跟踪的文件。第二步是git diff或git diff --staged确认具体改了什么。第三步是git log --oneline -10看看最近的提交历史确认HEAD的位置对不对。这三步能解决大部分“不知道发生了什么”的情况。比如你发现代码莫名其妙变了git status显示某个文件被修改了git diff一看是编辑器自动格式化的结果那就git restore掉。再比如你发现提交历史里多了一个不认识的提交git log一看是同事推上来的那就正常拉取合并。6.2 用git bisect定位引入 bug 的提交git bisect是一个被严重低估的工具。它的原理是二分查找你告诉它一个“好”的提交和一个“坏”的提交它会自动切换到中间的那个提交让你测试然后根据结果缩小范围直到找到第一个引入问题的提交。git bisect start git bisect bad # 当前版本有问题 git bisect good abc1234 # 这个版本是好的 # git 会自动切换到中间提交测试后标记 good 或 bad git bisect good git bisect bad # 重复直到找到问题提交 git bisect reset # 结束二分查找这个工具在排查“某个功能之前好好的最近突然坏了”这类问题时特别高效。如果提交历史有几百个提交手动一个个试要疯掉bisect只需要 log2(N) 次测试。6.3git blame不是用来甩锅的git blame file会显示每一行代码最后是被哪个提交修改的。很多人把它当成“甩锅工具”但其实它的真正用途是理解代码的演变。比如你看到一行奇怪的代码blame一下发现是两年前为了兼容某个旧版本加的现在那个版本已经不用了就可以放心删掉。git blame -L 10,20 file可以只看某几行的归属。git blame -w会忽略空白改动避免格式化提交干扰视线。这些参数在排查历史遗留问题时很实用。6.4 常见报错速查表报错信息常见原因解决思路non-fast-forward远程有本地没有的提交先pull再pushYour local changes would be overwritten工作区有未提交改动stash或commit后再操作CONFLICT (content)合并时有冲突手动解决冲突后add再commitdetached HEADHEAD指向了具体提交而非分支git switch branch回到分支Permission denied认证失败或权限不足检查远程地址和凭证fatal: not a git repository当前目录不是仓库cd到正确目录或git init7. 把git用成肌肉记忆的几个习惯7.1 别名配置让常用命令少敲几个字git允许给命令配别名配置写在~/.gitconfig里。我常用的几个[alias] st status -sb co switch br branch -vv lg log --oneline --graph --decorate -20 last log -1 HEAD --stat unstage restore --staged amend commit --amend --no-editgit lg是我用得最多的它用图形化方式显示最近 20 个提交的分支结构一眼就能看出合并关系。git st比git status输出更紧凑适合快速查看。7.2 提交前的自检清单在git commit之前我习惯跑一遍这个清单git status确认没有多余文件、git diff --staged确认暂存内容符合预期、git log -1确认上一次提交信息格式正确。这个习惯帮我避免了很多“提交了不该提交的文件”或者“提交信息写错”的尴尬。还有一个细节如果项目有pre-commit钩子比如代码格式化、lint 检查确保它正常运行。有时候钩子失败会导致提交被阻止但报错信息可能被忽略。我见过有人反复commit失败却不知道是钩子在报错最后发现是代码里有语法错误。7.3 定期清理和整理本地分支太多的时候git branch的输出会变得很长。我习惯每周清理一次已经合并的分支git branch --merged | grep -v main | xargs git branch -d。这个命令会删除所有已经合并到当前分支的本地分支但保留main。远程分支的清理用git remote prune origin它会删除本地缓存的、远程已经不存在的分支引用。这个操作不会影响远程仓库只是清理本地的引用列表。7.4 遇到问题先别搜先看git自己怎么说很多人遇到git报错的第一反应是打开搜索引擎。但其实git的报错信息通常已经包含了解决思路。比如non-fast-forward的报错里会提示你先pull冲突报错会列出冲突文件权限报错会提示你检查认证。花十秒钟读完报错信息往往比搜十分钟更有效。如果报错信息太长看不完可以只看最后几行那里通常是关键提示。或者用git command --help查看该命令的完整文档里面会有常见问题的说明。7.5 保持学习git的进阶工具当你把基础命令用熟之后可以了解一下git worktree同时检出多个分支到不同目录、git submodule嵌套仓库管理、git hooks自动化脚本这些进阶功能。它们不是日常必需但在特定场景下能大幅提升效率。比如git worktree允许你在同一个仓库里同时检出多个分支到不同文件夹这样你可以在一个目录里写功能另一个目录里修 bug不用来回切换分支。这个功能在“紧急修复和日常开发并行”的时候特别有用。最后分享一个我自己的体会git的命令有几百个但日常工作中真正高频使用的也就二三十个。把这二三十个用熟、用透理解它们背后的逻辑比背下所有命令更有价值。遇到没见过的问题git的文档和reflog通常能给你兜底。真正让你从容的不是记住了多少命令而是知道出了问题该从哪里开始查。