
1. 分支不是文件夹先建立正确的操作心智绝大多数人对 Git 分支的误解都始于把它当成文件夹的复制。我刚开始接触 Git 时也是这样——以为创建分支就是把代码复制一份然后在副本上改改完再合并回去。这个理解不能说是错的但它会带来两个非常麻烦的后果一是你会在切换分支和保存进度之间反复犯迷糊二是你会对 Git 偶尔出现的诡异行为感到完全无法理解。分支的本质其实是一条可移动的指针它指向某一次提交。当你执行git branch feature-login时Git 做的只是创建一个新的指针指向当前所在的提交。真正复制出来的代码工作区内容在你git checkout/git switch切换过去之前根本不会有任何变化。这也是为什么创建分支是瞬间完成、完全不占空间的根本原因。理解了指针这个模型一个常见困惑就迎刃而解了为什么在分支 A 上修改了文件切到分支 B 后改动不见了不是因为你的代码丢了而是每个分支的指针指向不同的提交工作区的内容会跟着指针走。你之所以经常在切换分支后找不到刚才的修改多半是忘记提交了或者压根没意识到未提交的改动会跟随工作区而不是跟随分支。另一个需要纠正的直觉是分支的最终归宿一定是合并。在实际开发中很多分支生命周期很短合并完删掉很正常但也有不少分支是用来做实验、验证想法、跑数据任务的跑完直接弃用或删除并不存在合并的必要。把分支当成一种低成本的工作上下文切换机制比当成必须合流的支流更符合真实使用场景。记住一句话分支是便宜的、临时的、可丢弃的而提交记录才是你真正需要小心维护的资产。带着这个心智再往下看任何命令都会顺手很多。还有一个常用的速查概念HEAD指向你当前所在的分支。git branch会在当前分支名前加一个星号git status也会在第一行告诉你处于哪个分支。几乎所有分支相关操作的前提都是明确自己现在在哪所以我把先确认位置列成了一条铁律后面会反复提到。2. 本地分支的创建、切换与提交高频操作的完整套路本地分支操作是每天使用频率最高的场景。我不打算列一个完整的命令大全而是给出一套我自己验证过、在团队协作里也不会出错的组合拳。2.1 创建与切换的姿势选择创建分支有两种常见姿势# 姿势一创建后仍停留在当前分支 git branch feature/payment # 姿势二创建并立即切换过去 git checkout -b feature/payment # 或者新版命令 git switch -c feature/payment大多数时候我应该用姿势二。因为单独执行git branch feature/payment而不切过去唯一的用途是我想记下当前这个提交点但还不想离开现在的工作现场。这种需求确实存在比如你正在一个分支上改到一半突然需要回头验证另一个路径但你并不想提交当前改动——此时记一个分支指针是最优雅的。git switch是 Git 2.23 之后引入的新命令和git checkout在分支切换这件事上是等价的。习惯上我现在全面转向switch系列因为checkout的职责太杂了既能切分支又能恢复文件新手很容易被绕晕。switch只干一件事心智负担小很多。创建分支时还有一个容易忽略的起点问题新分支默认从当前 HEAD 处拉出来如果你想从某个历史提交、某个远程分支或某个 tag 拉分支就需要显式指定起点git checkout -b hotfix/urgent-bug origin/main git branch debug/test-scenario 3f2e1a5第一种写法是从远程 main 分支的状态拉一个修复分支适合线上出 bug 需要立刻基于最新主干开修的场景第二种写法是从指定提交哈希拉一个调试分支适合回溯历史版本做验证。2.2 切完分支先处理一个动作同步上游信息如果你是从远程仓库协作的切换一个刚拉取下来的远程分支时可能遇到git switch直接失败的情况提示让你指定--track。比较省心的做法是直接带上启动跟踪参数git switch -c feature/login --track origin/feature/login这样本地分支和远程分支之间的关联就建立起来了之后的git push/git pull都能省去指定远程分支的麻烦。对于经常忘记这一步的人可以设置全局默认行为让git push直接推送到同名的远程分支git config --global push.default current git config --global --get-regexp branch\..*\.remote2.3 分支上做提交的正确节奏分支内提交代码的标准流程没问题但我特别想强调一个很多人不习惯的动作提交前先用git diff检查提交后立刻用git log -1确认。git diff # 检查未暂存改动 git add . git commit -m feat: 完成支付网关接入 git log -1 --stat # 确认刚提交的内容确实是自己想要的这组动作看起来啰嗦却能挡掉相当大比例的把密钥提交上去了把临时调试代码一起提交了提交信息写错分支了之类的低级事故。尤其是git diff在可视化工具里虽然也能看但命令行里扫一眼新增的行数和文件列表比想象中更能帮你建立对本次改动的整体感知。2.4 忘了切分支就改了代码用 stash 兜底每个用 Git 的人都会遇到这么一刻在 develop 分支上改了半个小时的代码突然发现这里应该开新分支改。直接切分支会报错因为工作区有未提交的改动。此时标准解法是用git stashgit stash push -m 登录功能改造未完成 # 或者简写 git stash git checkout -b feature/login git stash popstash相当于把工作区的改动打包存到一边分支切换干净了再弹出恢复。我有一个常用的微习惯git stash pop之后立刻执行git status和git diff --stat确认改动确实回来了并且没有因为补丁冲突而悄悄丢失部分内容。如果你同时改了好几个文件但只想把其中一部分带走用git stash push -- 路径可以指定文件入栈这样比全部打包更适合精细化处理。需要注意 stash 是全局的不区分分支默认弹出的是最近一次所以养成带描述信息的好习惯非常重要。3. 合并与变基两种整合思路的取舍逻辑分支建好、代码写完接下来必然是整合。Git 里整合分支的主流方式就两条路git merge和git rebase。网上吵这两个谁优谁劣的文章多得能出书但真正在项目里做决定时只需要搞清楚两件事提交历史是是如实记录还是整理叙事团队成员是否共享同一个分支。3.1 git merge保留真实的汇合点git merge的逻辑是在当前分支上新生成一个合并提交把目标分支的历史完整接进来。它尊重开发过程的时间线你什么时候开的特性分支、中途同步过几次主干、最终何时合入全部清清楚楚。git checkout main git pull origin main git merge feature/payment执行合并前我总会先做两个动作先切到目标分支并拉取最新代码再在合并分支上执行一遍测试。很多人在合并时遇到大量冲突本质原因是长时间没同步主干两边文件差距过大。把合并前先同步目标分支养成肌肉记忆后冲突概率会下降一大截。合并之后还有个动作容易被忽略——善用--no-ff。默认情况下如果一个分支领先目标分支若干个提交且没有分叉Git 会执行快进合并fast-forward直接把目标分支指针挪过去不产生额外提交。这样历史是线性的很干净但代价是丢失了这是一个完整功能合入的边界信息。团队追求可回滚粒度时我会约定合并到 main 分支一律用--no-ffgit merge --no-ff feature/payment它强制生成一个合并提交这个提交就是你回滚整个功能的锚点。这个约定在项目发布策略里非常实用推荐采用。3.2 git rebase把你的工作重放到最新基线上rebase的语义是把你当前分支上的提交一个个摘下来以目标分支的最新状态为新的基底依次重新落下去。结果是历史完全线性的仿佛你的特性分支就是在最新主干的基础上开发的一样。git fetch origin git rebase origin/mainrebase 最典型的投放场景是自己的特性分支——你想把主干上的最新改动同步到自己还没合并的分支里同时保持这条分支的提交历史干净。交出去的 PR / MR 里诊断信息、代码风格、提交粒度都是你精心整理过的这就是 rebase 的主场。不过凡事有利有弊rebase 最大的禁忌是绝不 rebase 那些已经推送到远端、并且被别人拉取过的分支。因为 rebase 会重新生成提交哈希其他人基于旧哈希的工作会在下一次同步时产生意想不到的重复提交和冲突。说严重点这属于团队协作事故不在技术讨论范围内。3.3 冲突处理的完整链路从定位到解决不管哪种方式只要两边改过同一个文件冲突就会出现。遇到冲突不要慌不要一上来就想着暴力解决我一般按下面这套链路走用git status看冲突文件清单记住Unmerged paths区块下列出的才是真正需要处理的。打开冲突文件搜索、、这些标记。它们分别表示当前分支的内容、分隔线、被并入分支的内容。根据业务语义决定保留哪边、合并哪边、还是两边都改。不要机械地全删掉。改完后用git add将文件标记为已解决。如果是 merge 冲突最后执行git commit生成提交如果是 rebase 冲突解决后执行git rebase --continue继续直到所有提交都重放完成。我见过太多人在冲突处理时只盯着代码文本完全不看上下文。但实际上大部分冲突的根因不是两边改得不可调和而是两边改了相邻区域各自的结构调整这时候你需要同时理解两边的意图才能做出正确的合并结果。所以冲突其实是一个难得的理解他人代码的机会处理多了你对团队代码演进的敏感度会明显提升。如果真的改糊涂了git merge --abort和git rebase --abort是后悔药可以让你退回冲突之前的状态。这两个命令我一直建议团队里的新人务必背下来它存在的意义不是鼓励放弃而是告诉你可以毫无心理负担地尝试。3.4 只想要部分功能cherry-pick 是精准手术刀有时候你不需要把一个分支整体合并进来只需要其中某几个提交。这种场景在支持多版本并行、热修复挑选需求时特别常见。git cherry-pick就是干这个的git cherry-pick a1b2c3d git cherry-pick e4f5g6h 7a8b9c0这条命令会把指定提交的改动作为新的提交应用到当前分支上。它和 merge 的本质区别是merge 是把一段历史接过来cherry-pick 是只搬运具体的改动内容。用 cherry-pick 时有个最常见的坑——先把基础分支切到目标分支上再执行 cherry-pick。我有一次想从特性分支上摘一个提交到主干结果人还站在特性分支上就直接执行了改动就这么不声不响地落在了特性分支自己头上浪费了半天排查。还有一种场景值得单独说你想把 A 分支的某个功能搬过来但这个功能分散在好几个提交里甚至中间还夹杂着无关改动。这种情况直接用git cherry-pick A..B按范围搬或者看提交粒度太碎就先对该功能做一次git rebase -i把提交整理合并成一个再 cherry-pick 过去。操作虽繁但胜在结果干净。4. 分支清理与误删恢复每个人都该会的善后操作分支用得越勤仓库里堆积的废弃分支就越多。定期清理分支不只是为了让git branch输出好看更是为了减少切换时的心智干扰——一长串几十个分支列在那里光看名字就很难判断哪个是活的、哪个是试完不要的。4.1 本地分支的删除与安全保护删除本地分支的命令很简单git branch -d feature/old-function注意我用的是小写-d它带有一个内置保护只有该分支的提交已经被合并到当前分支或上下游时才会允许删除。如果 Git 检测到分支里还有未合并的提交它会拒绝执行并提示你。这时候如果你确认这些提交确实不要了才用大写-D强制删除git branch -D feature/abandoned-experiment我给自己定的一条规矩是-D只用于我连 diff 都不想再看一眼的分支。凡是还有一丁点可能要用回来的代码我都会先打个 tag 或者把分支名字记下来再删。因为-d能保护你而-D是无情的。4.2 远程分支的删除与追踪分支清理远程分支的删除稍微不同需要用到 push 操作git push origin --delete feature/merged-work这条命令执行后远端仓库的该分支会被移除。本地虽然删除不了远端但本地还保留着对该分支的追踪记录特别是那些别人已经删掉、但本地还残留的远程分支信息需要通过git remote prune origin或者直接执行git fetch --prune来清理。很多人在git branch -a里看到一堆奇怪的远程分支残留多半就是这个原因。顺便提醒一句远程仓库里删掉的分支相关 PR / MR 往往也会随之关闭或标记为已合并这属于平台行为各平台略有差异但大方向是删分支该合并的先合并该验证的先验证别随手乱删。4.3 误删分支后的黄金五分钟删除之后立刻发现自己还有提交被一起删了这种事故在团队里真的发生过。好消息是所幸 Git 的回收机制非常友好被删除分支所指向的提交只要还没有被垃圾回收git gc/ 长时间未使用就仍然存在于对象库里只是没有引用指向它。恢复的办法很简单git reflogreflog会列出 HEAD 的历史移动记录每一行都包含一个提交哈希。你只需要找到删除分支前最后一次切换到那个分支的记录信息里通常能看到分支名和操作行为拿到那个哈希然后从它重新拉回分支git checkout -b feature/recovered 3f2e1a5如果 reflog 记录也过期了还有一个办法是通过git fsck --lost-found找出所有未被引用的悬空提交然后逐个检查。这个操作要花点时间但比 reflog 记得更深一层。我个人建议团队每个人都把git reflog这个命令刻在脑子里。它就像一个时光机绝大多数好像丢了东西的场景都靠它救回来过不止一次。4.4 合理的事项清理大批量删除时的小技巧批量清理分支时一个个敲删除命令太慢了。比较实用的做法是先用管道把要删的分支列表过滤出来再批量执行git branch --merged | grep -v main\|develop | xargs git branch -d这条命令的意思是把所有已经被合并到当前分支的分支列出来排除掉主干和开发分支逐条执行安全删除。写之前一定先不要带xargs单独跑一遍第一段看清楚列表里都是谁再动手。批量操作的共同铁律就是先预览后执行。5. 多分支并行开发从原理到 IDE 集成的实战细节热词里频繁出现的几个场景——idea 切换分支tortoisegit 切换分支vscode 同项目多分支同时开发——本质上都是在问同一件事多分支并行开发时怎么在工具层面做得顺手又不会搞乱。5.1 并行工作区的两种策略并行开发的第一个问题是物理层面的同一份代码我要同时维护两个不同版本的改动应该怎么办方案一串行切换。在同一份本地目录里用git switch在不同分支之间来回切换。优点是简单、只占一份磁盘空间缺点也很明显每次切换都要处理一个心智切换的成本而且如果有未提交改动切换是会被拒绝的。方案二多个工作目录。这是进阶玩法直接在文件系统层面克隆多份仓库每份目录 checkout 一个独立分支。比如同时维护feature-a和feature-b两份代码各自独立跑测试、独立改代码只在需要时拉取最新主干。git worktree命令就是为这个场景设计的git worktree add ../project-feature-a feature-a git worktree add ../project-feature-b feature-bworktree的强大之处在于它允许同一个仓库同时被多个工作目录关联每个目录都可以独立切换分支、提交代码完全互不干扰。这个功能在需要同时验证两个不兼容的改动、或前端需要分别调试多版本场景时简直救命。我自己的经验是一旦用过 worktree几乎就不太愿意回到串行切换的老路上去了。5.2 IDE 里切换分支前的三秒检查不管用什么 IDE切换分支这个动作在所有图形工具里都对应同一个底层操作 checkout / switch。IDE 的图形按钮只是把命令包了一层底层的规则没有任何变化。所以最重要的不是记住按钮在哪而是记住切分支前先确认三件事当前分支有没有未提交的改动有的话先 commit、stash、或选择带回IDE 通常会给选项。当前分支有没有未推送的提交切走后这些提交还在不会丢但容易忘记。目标分支是否存在远程分支需要先 fetch否则本地列表里看不到。IntelliJ IDEA 里切换分支的位置在右下角的 Git 分支小图标点开后你能看到本地分支列表和远程分支列表。远程分支单独显示在 Remote Branches 目录下双击可以直接切换IDEA 会自动帮你创建对应的追踪本地分支。TortoiseGit 在 Windows 资源管理器里右键就能看到 Switch/Checkout 菜单弹出的对话框里可以选分支也可以选择远程分支并自动建立本地追踪关系。VS Code 左下角的源控制面板点击分支名称就能切换也可以用快捷键CtrlShiftP输入 Git: Checkout to... 快速操作。工具虽然不同底层逻辑完全一致。5.3 同项目多分支同时开发的真实场景拆解前端同学在 VS Code 里同项目多分支同时开发的典型操作流是这样的起一个主工作目录跑着 main 分支的开发服务器再起一个 worktree 目录跑着 feature 分支的验证环境。两份代码独立监听不同端口互不影响。这种方式比在一个目录里来回 switch更适合被反复打断的日常开发节奏。这个流程里有一个容易踩坑的地方是node_modules。worktree 创建的目录不会自动复制依赖包切换过去之后要先执行一次依赖安装。如果你同时维护两个分支且依赖差异较大可以只给其中一个目录安装依赖另一个用与仓库无关的构建缓存来处理。这个属于工程化细节团队内部可以约定统一口径。5.4 可视化工具的隐藏能力比较、合并与历史追溯IDE 里最有价值的往往不是切换分支的按钮而是分支的对比和合并操作。IDEA 在 Git 面板里选中两个分支右键选择 Compare会清晰列出所有差异文件列表逐文件 diff 非常高效。合并时 IDEA 会弹出三路合并窗口左边当前分支、右边引入分支、中间结果区冲突块高亮展示解决起来比命令行直观不少。但我也要泼一盆冷水IDE 的三路合并虽然方便它不会替你判断业务意图。很多合并错误正是因为在图形界面里看着很快、点得很快却没有仔细理解每一块冲突为何存在。所以我的建议是可视化工具适合查看和初步解决涉及复杂逻辑的冲突把它们从窗口里拽到编辑器里上下文完整了再动手。6. amend、reflog 与疑难现象提升分支操作段位的几个深水区到了这个阶段分支的日常操作你已经很顺了。但实战中总有一些看起来不对、又说不清为什么的疑难现象以及一些能大幅提升效率的进阶命令。这一节专门聊这些。6.1 git commit --amend 的正确用法git commit --amend的作用是修改上一次提交它最原汁原味的应用场景是提交完发现漏了一个文件、或者提交信息里写错字了不想为此多产生一条fix typo之类的冗余提交。git add forgotten-file.txt git commit --amend --no-edit--no-edit表示沿用原提交信息如果你只是想补充提交内容这个参数能省掉打开编辑器的麻烦。但这里有个非常重要的警告amend 会改写提交哈希。如果你此前已经把这次提交推送到了远端再 amend 之后直接git push会被拒绝因为历史不一致了。解决方法是强制推送git push --force-with-lease但强制推送本身就意味着你正在改写远端历史除非是自己在独立的分支上操作否则不建议在共享分支上乱用。6.2 fatal: not a git repository 现象排查这个报错几乎每个 Git 新手都见过提示信息直译是这不是一个 git 仓库或者其任意父目录都不是。它出现的原因基本就两类一是在仓库外目录执行了 git 命令二是仓库的.git目录出了问题。排查思路很直接先执行pwd看当前路径再用ls -la确认目录里是否有.git文件或.git目录。如果仓库本身没问题通常只是终端当前目录不对切换到仓库根目录再执行就行。如果.git确实丢失比如误删或复制项目时漏了就需要从远端重新克隆或者把本地代码保存到临时目录后重新关联远端这个操作不在文章范围内但记住一点.git是整个版本历史的载体丢了就真的丢了历史只能从远端拉取恢复。6.3 为什么改完代码切分支总是被拒绝这个问题本质上就是未提交改动与切换目标分支之间存在冲突。可以这样理解当前工作区里的改动还没落地而这些改动所基于的位置和目标分支的位置可能不一致Git 无法保证切过去之后改动还能干净地保留。此时 Git 会锁定切换操作强迫你先处理掉工作区状态。处理方式上面已经提过commit 或 stash。这里我补充一个技巧如果只是临时想去别的分支看个东西用git stash最轻量如果你想把这些改动完整保留在当前分支上可以用git commit -m wip再切走——我管这个叫临时落地虽然提交信息很丑但至少改动有了明确的归属。回来之后再soft reset或者 amend 整理都来得及。6.4 分支名和文件同名时的 switch 歧义有一个很少人注意、但碰到就会卡住的场景当你执行git checkout feature时如果当前目录里恰好有一个叫feature的文件或文件夹Git 会把它理解成恢复这个文件而不是切换到这个分支于是你会得到奇怪的报错或行为。解决方法是强制指定切换分支语义git checkout feature --branch feature git switch feature # switch 不存在这种歧义这也是我为什么一直推荐用git switch代替git checkout的原因之一底层行为做了语义分离能避开好几种历史遗留的歧义。6.5 大文件导致 clone 卡住认识 git lfs 场景热词里出现了好多次 git lfs大文件场景在分支操作里确实会带来具体可感知的干扰clone 卡住、切换分支时卡住、合并时卡住。本质原因是 Git 的 diff 机制对待大二进制文件非常不友好哪怕一个 50MB 的模型文件只改了一个字节整个文件都会被重新纳入计算。git lfs 的思路是把大文件的实际内容存到远端单独存储仓库里只保留一个指针引用这样 clone 时仓库主体轻快很多按需拉取大文件。分支操作里与 LFS 相关的常见困惑是 clone 卡住 / 切换分支时明明只改了一行却要拉几百 MB 的 LFS 对象。这类问题通常不是命令层面能解决的而是仓库结构和缓存策略层面的。一个可以立刻尝试的补救是清理未使用的 LFS 缓存以及检查是否设置了lfs.fetchexclude排除规则。这里顺便回应热词里出现过的git config --global --unset lfs.fetchexclude——它就是一条移除 LFS 排除规则的命令如果你之前设置了排除某些目录的 LFS 拉取现在想恢复完整拉取这条命令正好对症。用得不多但见过它说明你已经在 lfs 配置这个深水区门口了。6.6 SSH 认证失败与分支操作的关系ssh 认证失败 git在热词里也出现了。分支操作本身不认识 SSH但 fetch / push 阶段如果触发默认的 SSH 认证就有可能出现报错。最常见的处理是重新配置密钥并注册到远端平台或者测试连通性ssh -T gitgitee.com如果认证失败优先检查密钥路径、是否把公钥添加到远端平台账户、以及~/.ssh/config的配置是否正确。这里不展开安全细节但可以告诉你一个经验如果你换过电脑、重装过系统90% 的 SSH 认证失败都是因为本机密钥没有重新配置而不是远端问题。7. 关于分支工作流的最后几条实战建议写了这么多终归还是要把分支这件事落回到日常开发流程里。以下几条建议是我在几个团队里推行过、并且反复验证有用的一些规则供参考。第一分支命名要一眼看懂生命周期。比如feature/前缀表示新功能bugfix/表示修复hotfix/表示紧急修复release/表示发布准备chore/表示维护性工作。命名统一后无论是自己翻历史还是别人接手项目信息获取成本都会大幅降低。这个成本每天都在发生累积起来很可观。第二主干分支养成合并前拉取、合并后验证的习惯。拉取是为了确保基线最新验证是为了确保合并没有引入回归。这两个动作单看很小但它们能把冲突地狱和上线即事故这两大问题挡在门外。第三不要害怕删除分支。分支本身是一种廉价的指针删除分支不等于删除代码真正的提交都躺在仓库对象里。需要用的时候git reflog总能在黄金期内帮你找回来。真正该怕的是从来不清理分支让仓库变成一堆无法判断状态的僵尸分支集合。第四提交信息写清楚为什么。这一点在分支合并和 cherry-pick 时体现得特别明显——当你需要比较多个提交来挑选功能迁移时写得含糊的提交信息会让你被迫打开 diff 逐个看而写得好的提交信息能让你直接精确锁定该搬哪几个提交。很多团队的分支管理很规范但败在提交信息上。第五多版本并行时优先考虑 worktree。同一份代码在不同分支之间来回切换心智成本远比想象的大。worktree 让每个分支有独立的物理空间测试、调试、环境依赖都互不干扰是最适合并行开发的轻度方案。在大量团队场景里我观察到一个普遍的规律分支命令本身不难学真正拉开差距的是对指针模型的理解程度、对提交历史的维护意识、以及遇到异常时能不能冷静运用 reflog 这类工具找出路。把本文涉及的这些命令在真实项目里各跑几遍特别是有意识地用 switch 代替 checkout、用 stash 处理切换打断、用 cherry-pick 搬运具体提交你的分支操作就没有明显的死角了。