入职第一周我从同事桌上捡到一份被翻得卷边的Git速查笔记。上面写满了add、commit、push、pull这些命令旁边用红笔圈着几个大写单词checkout、reset、rebase。那会儿我以为只要把这些背熟就等于会Git了。等我真正在项目里因为一次错误的reset丢了一整天的代码、又花两个小时在冲突标记里找哪段代码该留下的时候才知道版本控制这堂课躲是躲不掉但背命令绝对学不会。这篇Git使用教程我不打算按命令字典的顺序讲而是按工作流讲。说人话就是你什么时候该做什么、为什么会卡住、卡住了怎么爬出来。适合刚接触版本控制的新手也适合那种“会上传代码但一遇问题就慌”的过渡型选手。读完你能得到的不是命令清单而是一套遇到任何项目都能直接落地的操作思路。1. 先把心智模型建对Git不是存文件是存“快照”很多人学Git最大的障碍是用Word文档的思路去理解它。Word里“另存为”之后文件变成了一个带时间戳的新副本。Git不一样它不关心你“文件开了哪些窗口”它关心的是“整个项目在某一个瞬间长什么样”。1.1 三个区域是你和Git打交道的三个房间Git的日常操作本质是在三个区域之间移动东西工作区你电脑里实实在在看得见摸得着的文件目录编辑器打开、改代码都在这里。暂存区一个临时存放“我准备记录哪些改动”的中间层很多人叫它Index。仓库Git真正保存快照的地方所有历史记录都在这里。区域存在哪你想对谁说话工作区你的磁盘目录git status、git diff暂存区.git目录里的索引文件git add、git reset不带--hard仓库.git目录里的对象库git commit、git log为什么必须有暂存区你可以把它理解成购物车。去超市你不会推着购物车装一件就结账一次而是先挑好几样统一结账。提交代码本质上也是这个道理你可能给同一个文件改了三处地方但其中只有两处和“修复登录超时”这件事有关另一处是顺手调了个样式。把相关的改动先放进暂存区再一起提交历史记录里的每一条就都能讲清楚一件事。1.2 提交记录不是时间线是一串快照链每次执行git commitGit会把当前暂存区里的内容打包成一个快照然后记下这个快照的元信息——作者、时间、提交说明——以及它的父提交是谁。下一次提交时快照里哪怕只改了一个字符Git也会重新生成一份完整的快照而不是记录“第85行改了一个字符”。这一点和SVN那种记录文件差异的方式有本质区别。Git每次提交都是完整的项目状态版本只是它内部用一种非常聪明的压缩方式让重复内容只存一次。这也是为什么Git切换分支那么快——因为分支切换只是挪动一个指针指向不同的快照链并非真的去把整个目录恢复一遍。理解了“快照”而不是“补丁”你就能想通很多现象为什么Git能离线提交一大堆commit然后一次性推送为什么rebase能重排历史为什么reset回退到某次提交之后后面那些提交的改动并不会“消失”它们只是没有了引用成了孤儿提交。慢一点把“提交项目拍了一张完整照片”这个念头焊在脑子里后面所有操作都不再玄学。2. 每天都会碰到的五个场景这样操作不会翻车命令是场景的答案。把场景搞明白了你自然知道该敲哪条命令。下面五个场景我几乎每天都会收到同事提问每一个都对应一套标准动作。2.1 场景一新项目第一次提交怎么搞在空白目录里跑git init会生成一个.git隐藏文件夹。别去动它里面是Git的数据库。第一次提交前最重要的不是急着add而是先建一个合理的.gitignore文件。.gitignore里写上哪些内容凡是“由工具生成、每个人机器上都不一样、丢了能重新生成”的东西都要忽略。比如Node项目的node_modules、Java项目的target目录、编辑器配置文件、IDE自己生成的.iml、.idea等。把这些提前挡住commit历史会干净很多别人clone下来也不会有你机器上的本地配置残留。然后执行git init git add . git commit -m chore: init project提交信息里的chore前缀是什么后面第5章专门讲先留个印象。第一次提交有个小心思首条提交尽量小而基础别把一堆用不上的脚手架和依赖全塞进来。一个项目的第一个快照决定了后面所有人clone下来看到的第一眼给它点体面。2.2 场景二代码写到一半想保存个临时状态典型情况是你正在改A功能改到一半忽然生产环境报了一个更紧急的bug得切过去修。分支没建代码没提交直接切分支会带着未提交的改动一起过去容易污染现场。这时候先别忙git add和git commit思考一下这个“半成品”该不该进入历史记录。答案通常是否定的。半成品提交进历史会让commit记录里出现“完成一半的东西”后面自己回看都尴尬。正确做法是用stashgit stash # 切去修bug改完、提交、切回来 git stash popgit stash就是“把工作区先临时收进一个抽屉”pop再把抽屉里的东西原样拿出来。文件没提交、也没丢抽屉锁在仓库里干净利落。要注意的是stash默认不收纳新出现的未跟踪文件如果你有新建的文件记得用git stash -u把未跟踪文件也一并收进来。这个坑我踩过不止一次写了一个新工具类忘记加-u切走再切回来新文件还孤零零留在原地还以为丢了。2.3 场景三改乱了想退回上一个状态这个场景最容易把人绕晕因为Git提供了好几个“看起来都有返回功能”的命令checkout、restore、reset。我先把它们各自管哪块区域说清楚。git restore 文件把工作区里的某个文件还原成暂存区里的样子。说白了就是“刚改的内容我不要了”。git restore --staged 文件把暂存区里“已经添加但还没提交”的状态取消掉文件不会变只是不再是待提交状态。新版Git里想取消git add就用这个。git checkout -- 文件老版写法和git restore作用一样现在更推荐restore。git reset --hard 把当前分支指针、暂存区、工作区全部重置到某个提交这一步要非常小心因为工作区未提交的改动会被直接丢弃。安全口诀是改坏单个文件用restore想撤掉整个提交再用reset。而且reset --hard之前永远先确认两件事——当前没被提交的改动你是否还要以及这个提交之后有没有同事基于它开发了别的东西。分布式版本控制里“撤销”这件事不只是你一个人的事别人的仓库不会因为你reset就跟着改变。2.4 场景四提交完才发现漏了文件或写错了说明第一次提交完立马看到文件列表里少了一个本该提交的配置文件。这种“提交后一拍大腿”的时刻人人都经历过。小修补直接用amendgit add 漏掉的文件 git commit --amendamend的意思是“把最后一次提交里哭天地地地改一下”把刚才漏掉的改动追加进上一次提交提交历史不会因此多出一条新记录。它不会增删历史只是把上一个快照开封、换个新的封皮。注意amend会生成一个新的提交对象原来提交的SHA会变化。所以如果你已经把这个分支推到远程且别人已经拉取就不要amend了否则pull的时候Git会一脸困惑怎么同一个名字的历史突然变了样。至于“写错了提交信息”同样是amend进入交互界面把说明改掉再退出即可。核心逻辑给“最后一个commit”做外科手术用amend给“前面某几个commit”做手术得用rebase -i第5章再展开。2.5 场景五想知道这个旧版本当时改了什么排查线上问题时常见操作是找到“出问题版本之前的那个版本”对比代码。git log看提交历史git diff看差异git show看某次提交的具体内容三个命令组合起来就是一条完整的事故分析链。先git log --oneline看提交ID和说明选定嫌疑提交。然后git show 提交ID能看到这次提交涉及哪些文件、每处改动是什么。想知道两个版本之间的整体差异用git diff 提交A 提交B。看某个文件在历史里的演进用git log -p 文件名。我排查代码时几乎不看那些花哨的图形化工具命令行三件套配合通配符就够用。偶尔配合git log --author某开发者能快速定位某个人写的所有提交。这套动作熟练后你会爱上这种“考古式”的代码追溯感。3. 分支让并行工作成为可能也让事故有了温床分支可能是Git最强大、也最容易被误解的功能。新手往往会刻意躲开分支觉得切来切去会乱。其实分支从数据结构上看轻量得惊人——它只是一个指向某个提交的可移动指针。你创建一百个分支并不会让仓库多一百个副本只是多了一百个指针而已。3.1 理解了“指针”你就理解了一半的分支操作新项目默认分支通常叫main它就是一个指针指向最新提交。当你执行git checkout -b feature/loginGit会用现有提交创建新指针并让HEAD就是你当前所在位置指向它。此后你在这条分支上提交feature/login会跟着前进而main纹丝不动。两条线从此各走各的互不干扰。实际工作中我最常用的团队分支模型简化下来只有四类主分支只接受合并不允许直接提交永远保证能发布。功能分支从一个基线拉出来开发完再合回主分支。修复分支专门处理紧急问题从主分支拉出修完后快速合并。集成分支多人开发的大型项目里把各功能分支先汇合测试的地方只在新功能跨模块时用。这套模型的灵魂是主分支要一直保持“随时可发布”的状态。功能分支可以在上面折腾、推倒、重来但别把半成品带回家。3.2 合并merge和rebase的本质区别把分支合回主分支选择merge还是rebase是Git世界里经久不衰的辩论题。merge会生成一个特殊的合并提交历史里出现一个“分叉线”谁在那条叉上、哪边先合进来记录得一清二楚。它保留了真实发生过的并行开发轨迹。劣势是历史图会被信息淹没提交记录里夹杂一堆Merge branch xxx into main。rebase则是把自己的提交“摘下来”接到目标分支的最新位置上分支历史从这之后整整齐齐一条线。理想情况下的rebase能把一个长达两周的功能分支压缩成“视觉上连续的几个提交”看起来像一个人在主线上一气呵成。代价是改写历史如果这个分支已经被别人拉过去做了开发rebase会把别人的仓库搞得无所适从。我的规则很简单功能分支在自己手里没推过远程随便rebase已经推了远程且别人也在用就merge。团队协作中历史整齐很重要但比“整齐”更重要的是“所有人对同一段历史有一致的认知”。3.3 怕切分支先搞清楚工作区和分支的关系有人不敢切分支是因为觉得“切分支换草稿纸”。其实更准确的理解是每次切换分支时Git会比较当前分支快照和工作区文件的差异。如果工作区没有未提交改动切换是秒级的因为它只是移动一下指针并同步文件一旦工作区有未提交的改动Git就会出现抵触因为那个供快照“还原”的底子已经被你改花了。所以遇到“无法切换到另一个分支”的报错不要慌按这个步骤处理git status看那个文件是modified还是untracked需要保留的修改git stash -u收起来需要保留且已经能提交的先提交不需要的脏改动git restore丢弃做完任选其一分支就能顺畅切换了。这个“三步让位法”我每次培训都要讲因为它背后才是分支机制最关键的认知分支切换前先把工作区收拾干净。4. 冲突所有人都会遇到关键是怎么体面地解决冲突不是Bug是版本控制系统的正常反馈。它本质上是在说两处修改都对同一块代码做了改动我无法替你判断谁是对的。解决冲突就是行使这个判断权而这个过程拼的不是命令熟练度是思考的仔细程度。4.1 冲突长什么样先学会读它的语言当你执行git merge或pull遇到冲突时Git会在有问题的文件里插入冲突标记格式长这样 HEAD 你当前分支里的内容 从别的分支合并过来的内容 feature/login到之间是“当前所在分支”的版本到之间是“被你合并进来那条分支”的版本。你的任务很直白把这两个标记连同不需要的内容删掉只保留最终该留的那份。千万别留任何标记符否则代码直接编译不过还会被同事嘲笑。真正决定冲突解决质量的是选择“留谁”的思考过程。是两边都要、都改成一版还是只留一边这需要你了解两个改动各自的意图而非盲目用“我这边”覆盖“那边”。我处理冲突有一个固定顺序先看双方各自想解决什么问题再看两个问题放在一起会不会打架然后才动手合并代码。步骤大概是打开冲突文件看 HEAD的代码块标记它的意图看另一边代码块的意图判断两者是否互补互补就撮合成一段互斥就明确保留优先级删除标记保存文件git add该文件继续完成合并4.2 冲突里常见的坑乱删、乱选、乱提交第一个坑是“赢家通吃”把自己分支的版本全留下觉得“反正我的是最新的”。实际情况往往是对方在上周已经修复过一个边界条件你的“最新”恰好摧毁了那个修复。所以冲突解决时有一条底线怀疑自己检查对方。对方那段的注释、变量名、空行都可能藏着线索别一删了之。第二个坑是“解决完不测就提交”。视觉上代码拼接得很顺实际上接口默认参数变了、函数返回类型变了编译过不了。我给自己立的规矩是凡是解决完冲突的文件一律重新编译/跑一遍对应模块的测试再提交。这一步能挡掉大部分冲突后遗症。第三个坑是“不重新合并就顺手提交”。解决完冲突git add后分支之间的合并状态还没有完成必须git commit把“合并冲突解决”这个整体凝固进历史。这一步漏掉Git后续的状态会一直停留在合并未完成所有新操作都会被挡在一个“MERGE_HEAD pending”的警告后面。4.3 减少冲突靠的是习惯不是命令冲突最多的场景永远是大家在同一区域反复横跳改代码。想减少冲突比命令更重要的是习惯功能分支不要活太久。两周以上就是事故高发区频繁从主分支拉取更新把未来冲突提前消化掉。每个提交只改一件事颗粒度小合并时差额自然小。提交前看git diff了解自己到底改了什么合并时心里有数。多人协作时避免同时重构同一模块。真到重构群里吼一声大家把更小的功能拆开。这四条里“提交前看git diff”回报率最高也是我后来带新人时第一个强制要求。它不光是减少冲突更让你对自己的每处改动都保持掌控。5. 提交信息该写什么才算“会协作”提交信息最容易被新手忽略但对团队长期协作的伤害也最大。“update file”“fix bug”“没写”这类提交信息半年后连自己都看不懂当时在干什么更别说去git blame定位一段远古时代的代码是谁引入的了。5.1 参考约定式提交但别把它当成负担约定式提交Conventional Commits是一套很轻的描述规范核心是在提交信息开头用前缀标明改动类型feat新增功能fix修复缺陷docs文档改动style格式调整不影响代码逻辑refactor重构不改功能不改修Bugtest测试相关chore构建、工具、依赖等杂项比如feat: 新增登录接口的限流逻辑 背景登录接口近期被高频调用出现响应缓慢。 实现在接口入口增加基于令牌桶的限流超出阈值直接返回429。后面那段说明不是可有可无它记录了“为什么要这么改”比实现细节更有价值。细节可以被代码本身回答但改动动机永远需要人类文字来说明。这是我在一次线上事故后彻底想明白的排查到最后唯一能告诉我们“为什么这里要catch这个异常而不继续往上抛”的就是当初那个提交信息。5.2 提交的颗粒度决定历史的可读性信息写得好提交粒度也得对味。理想的一个提交能让人只看它的标题就知道改动范围和目的。它可能是一个功能的完整实现、一个Bug的完整修复、或者一次重构里某个独立的步骤。最忌讳把10个不相关的改动揉进一个提交也忌讳一个完整功能碎成10个零散提交。实际操作里“提交粒度”的判断标准就一句话如果我三个月后回看这段历史我想看到多少条记录三个月这个词不太准但思路是这样。一次提交应该是一个自洽的小故事能讲清楚就合格。如果当天提交得乱七八糟、一条历史信息都拿不出手怎么办改革历史的重任就落到rebase -i头上git rebase -i HEAD~5命令会打开编辑器让你调整最后5条提交的顺序、合并、修改说明。把三条“update file”合并成一条逻辑完整的feat把中间那条test挪到最后历史的可读性瞬间提升一个量级。唯一警告只对“还没推到远程”的本地提交操作推过的分支不要乱动否则团队其他人的本地历史会和你产生大量合并噪声。5.3 提交信息在事故排查时的价值线上出问题后最值钱的线索往往不是代码而是提交历史。时序上看哪次提交引入了变化为什么引入这个行为在哪个版本第一次出现顺着提交日志从旧往新扫通常很快能锁定嫌疑改动。我常用的排查姿势是git log --oneline -n 20把最近20条提交按时间排一下配合每个提交的变更文件列表再结合“这个Bug只影响某接口”的条件把候选范围缩小到三四个提交内。比菜鸟时代一处处git diff文件夹来得高效得多。提交信息写得好等于给未来的自己和接手项目的同事留了一批关键线索。让人在几小时之内找到根因这种体验就是好提交信息的直观回报。6. 生产环境里我还离不开的几个小技巧最后来点提效层的东西。这些不是必学但用上之后工作舒适度会明显提升尤其对每天高频操作Git的人来说。6.1 配置别名缩短每天重复敲的命令Git支持命令别名为常用命令配置短记法能省大量输入时间git config --global alias.ci commit git config --global alias.co checkout git config --global alias.br branch git config --global alias.st status git config --global alias.last log -1 HEAD --stat配置完git ci、git co、git br就能直接用了。这些别名存在用户级配置文件里换一台新电脑照抄一遍即可。我还有个特别常用的git ll配置成log --oneline --graph --decorate -15一眼看出最近15条提交和分支走向看项目状态比任何UI都直观。6.2 大文件和仓库瘦身团队协作最怕什么大文件误入仓库。比如某个同事把几百兆的安装包提交进历史后来的每个clone都背着这个包袱。Git擅长存文本不擅长存二进制大块头。误提交大文件后光删除当前版本还不够大文件还躺在历史快照里仓库体积依然膨胀需要重写历史或使用专门工具才能彻底清除。所以预防远大于治疗第一.gitignore提前挡第二提交前检查git status里有没有异常的二进制文件第三确实需要版本管理的大资源文件放到专门的托管服务里去。如果已经误提交了仓促之间可以先用这种延缓手段git rm --cached 路径/大文件这会把这个文件从暂存区里摘出去但它仍留在历史快照里。彻底根治需要重写历史建议在专人的指导下进行别自己硬上免得把所有人的分支历史都搞乱。6.3 误操作之后git reflog是你的救命稻草这一条我放在最后压轴。很多人把reset --hard当成洪水猛兽误操作后觉得世界末日。其实Git有一个“后悔药”机制叫reflog——它记录了HEAD指针在过去一段时间内的每次移动轨迹。哪怕是reset --hard之后reflog里仍能看到“重置前HEAD指向哪次提交”按原路返回就行。git reflog # 找到慌乱前的提交ID git reset --hard 那个提交IDreflog把Git的容错能力提到很高的水准这也是我敢放心教别人用reset的根本原因只要reflog记录还在几乎任何误操作都能救回来。耗尽的只是“时间窗口”不是“数据”本身。甚至分支被删了只要reflog里能看到该分支最后一次指向的提交也能重新拉一个分支回来。这套机制多练两次你对Git的信心会进一步提高。总的来说我从入职第一天拿到那张卷边的速查表到现在习惯性地提交前git diff、核对提交信息、顺手给常用命令配置别名——一路走来最大的体会是Git的难度不在命令多少而在心智模型对不对。把“快照、指针、区域”这三个概念吃透工作流会自己浮出水面。下一次你在命令行里卡住了别急着搜命令先问自己一句“我现在处于哪个区域、想让数据流向哪里”答案往往自己就出来了。