资讯详情

资讯详情

Git分支操作全攻略:从查看到创建再到合并,一篇讲透

我先把话说在前面Git 里最容易被忽略、却又最值钱的能力不是提交、不是推送而是分支。标题里这套“查看分支、创建分支、合并分支”的操作几乎是每个开发者在日常工作中绕不开的“高频三连”。前面几篇讲了 Git 的安装配置、基础提交和远程仓库同步看完之后很多朋友会提交了、会推送了但一碰到“这个分支到底是怎么来的”“我到底该在哪个分支上干活”就发懵。这篇就把这三类操作拆开揉碎从命令到图形界面从原理到排错一次讲完。不管是刚入门、敢动手敲命令的新手还是被分支合并折腾过的老手都可以拿这篇当参考。1. 为什么要认真对待分支先说透分支存在的意义1.1 分支解决的最大问题让开发并行而不是排队设想一下如果整个项目只有一条主线所有人都在这条线上提交会发生什么张三在改登录页面李四在改支付模块两个人每完成一个小改动就往上推提交记录很快就变成了一团乱麻。更麻烦的是如果张三改到一半发现思路不对想回退结果主线已经被李四的十几个提交推着走了根本没法干净地回退。分支解决的就是这个“并行开发”的问题。每个功能任务拉一个独立分支大家各改各的互不干扰等到代码稳定了再把分支合并回主线。这就像一条主干道旁边的临时施工便道——施工期间不影响主路通行完工之后再重新接回主路。这个“隔离”带来的好处非常直接你可以在一半代码还没写完的情况下随便提交不用担心污染主干可以随时切回主线去看某段历史代码可以让同一个团队里多个功能同时推进而不是排队等别人。1.2 分支的物理本质一个名字、一个哈希、一个指针很多教程喜欢把分支说得玄乎但如果你去看 Git 的底层实现会发现它其实特别朴素。在我的仓库里.git/refs/heads/目录下几乎每一个分支都对应一个文件文件内容就一行是一个 40 位十六进制的提交哈希值。比如当前分支是dev那dev这个文件里存着 dev 分支最近一次提交的 SHA-1 值。HEAD 又是一个特殊的指针它指向你当前所在的分支比如你切到mainHEAD 就指向refs/heads/main。理解这层物理本质对后面的操作至关重要创建分支为什么那么快因为它只是新建一个文件写入一个 40 位的哈希值不是复制代码不是拷贝目录。分支之间为什么互不影响因为每个分支文件只记录自己那条线上的最新提交。合并分支到底在做什么说穿了就是把某个分支的提交历史“并入”当前分支让当前分支的指针能覆盖到那边的改动。很多朋友学 Git 学了几个月遇到merge和reset还是懵就是因为没从“指针”的角度去理解。记住这句话分支就是一组提交的入口而分支名只是这个入口的名字。2. 查看分支动手之前先看清楚你在哪2.1 从 git status 到 git branch把常用命令变成肌肉记忆想知道自己当前在哪个分支最偷懒的方式其实是git status它第一行就写着On branch dev。只不过大家平时习惯性忽略它直接看下面的文件变动。而git branch不带任何参数会列出本地的所有分支并在当前分支前面加一个星号。这个命令的潜台词是看分支列表而不是看当前分支。如果你只想查当前分支名可以用git branch --show-current输出干干净净就一行名字很适合在脚本里用。下面这张表是我平时最常用的几个“查看”命令命令作用典型场景git branch列出本地分支带星号为当前分支日常快速查看git branch --show-current只输出当前分支名脚本、自动化场景git branch -v显示每个分支最近一次提交的哈希和提交说明了解各分支推进到哪了git branch -vv在-v基础上增加跟踪关系显示本地分支和远程分支的对应检查分支是否跟踪了远程git branch -a列出所有本地分支和远程分支想同时看本地和远端git branch -r只列出远程分支看远端有哪些分支git branch --merged列出已并入当前分支的分支准备清理无用分支git branch --no-merged列出未并入当前分支的分支评估哪些分支还没合并有一类场景比较特别在 IDEA、VSCode 这类工具里你右下角会直接显示当前分支名很多人就不再看命令行了。但命令行依然有它不可替代的价值——当你想批量处理分支或者想看哪些分支已经合并完该删了命令行的效率高得多。2.2 用 git log 看分支的分叉和走向git branch只能告诉你“有哪些分支”但它没法告诉你“这些分支是怎么分叉的”。想看分叉结构最直接的方法是git log --oneline --graph --all --decorate这条命令会以图形方式展示所有分支的提交历史。我随便画一个示意输出大家感受一下* f3b2d1f (HEAD - dev) 修正登录校验逻辑 | * a41c0e8 (feature/pay) 接入微信支付回调 |/ * 8e1f2aa 完成商品列表功能 * 9a2c3b0 初始化项目在这个示意里8e1f2aa是公共祖先提交之后dev和feature/pay分道扬镳各自产生了新提交。HEAD停在dev上说明当前工作区在 dev 分支。注意--decorate参数很关键它会把分支名、标签名直接标在提交旁边省得你对照哈希去猜。--all则是把所有分支都纳入显示范围包括远程分支。还有一个老板键级别的命令是git show-branch它会把分支提交以“矩阵”的形式列出来虽然初次看有点眼花但用于快速判断两个分支的分叉点挺有用。不过现在有了git log --graph日常我基本只用后者。2.3 在可视化工具里查看分支以及“已删除分支为什么还在”很多朋友不习惯纯命令行更喜欢在 IDEA、VSCode、TortoiseGit 里看分支。这些工具的图形界面其实很友好IntelliJ IDEA右下角分支选择器或者 Git 工具窗口里的 Branches 面板。VSCode底部栏左侧显示当前分支名点一下就能弹出分支列表。TortoiseGit 小乌龟右键仓库目录选择“Show log”上方“Branches”区域会列出所有分支不同颜色代表不同分支的提交。可视化工具的一个典型坑是远端分支明明已经被删除了本地工具里却还显示着。这是怎么回事因为本地还残留着指向远程分支的“远程跟踪引用”。解决方法是执行git fetch --prune或者对特定远程执行git remote prune origin。执行完之后远端已删除的分支引用就不会再出现在本地了。用 VSCode 的朋友也可以直接在命令面板里执行“Git: Fetch (Prune)”效果一样。3. 创建分支从哪建、怎么切、以及最容易忽略的细节3.1 创建分支的三种方式以及它们真正的区别创建分支最常见的写法有三种git branch feature/login git checkout -b feature/login git switch -c feature/login第一行git branch feature/login只是创建一个名为feature/login的分支但它不会切换过去你当前还在原来的分支上。如果只是想让一个分支“先存在”用这个没问题。第二种git checkout -b feature/login是“创建并切换”的组合等于执行了git branch feature/login再执行git checkout feature/login。这是绝大多数人每天都用的命令我也是这个用法用了很多年。第三种git switch -c feature/login是 Git 2.23 版本引入的新写法。官方把checkout拆成了两个职责更清晰的命令git switch负责切换分支git restore负责恢复文件。如果你刚接触 Git直接学switch反而更不容易混淆。我的建议是不管用checkout -b还是switch -c选一个顺手的一直用下去。没必要两个都背关键是理解“创建分支”和“切换分支”这两件事在底层是可以拆开的。3.2 从指定提交、标签、远程分支拉新分支分支默认从当前 HEAD 创建但很多场景下你想从别的地方开一条新线git branch fix/login 3a4b5c6这条命令表示从提交3a4b5c6创建一个新分支fix/login。提交哈希怎么找用git log --oneline翻一下就行。更多时候你是想从远程分支开一个本地分支出来。比如远端已经有一个origin/dev你想在本地开一个同名分支跟踪它git checkout -b dev origin/dev如果本地还不存在dev而远程只有一个origin/dev那么你甚至可以偷懒直接说git checkout dev。Git 会玩一个“魔法”发现本地没有这个分支但远端有于是自动为你创建本地分支并设置好跟踪关系。跟踪关系设置的直接好处是以后你在这个分支上直接git pull、git pushGit 会默认跟origin/dev打交道不用每次手动加远程名和分支名。查一下跟踪关系可以用前面提到的git branch -vv。3.3 本地还有未提交的修改时能不能切换分支这个坑几乎每个新手都会踩。我在当前分支改了一堆代码还没提交突然想去另一个分支修个紧急问题直觉上会直接git checkout切过去结果 Git 可能弹出一个错误也可能顺利切换但代码状态变得很诡异。关键规则是这样的当前状态切换是否被拦截处理方式工作区干净没有未提交修改不拦截直接切直接切换有未提交修改但目标分支没有同名文件冲突不拦截修改会被“带过去”直接切换但要留意带去哪了有未提交修改且目标分支同名文件内容不同拦截提示 commit 或 stash先提交或用git stash暂存很多朋友问“idea 当前分支未提交checkout 其他分支”会怎样本质就是第二条和第三条的情况。Git 不是不让切是发现切了之后会把未提交的改动带到目标分支造成混乱所以它在第三种情况下选择拒绝。如果你不改完不想提交可以先暂存git stash push -m 登录样式改了一半 git checkout feature/hotfix处理完紧急任务后切回来再把暂存的内容取出git checkout dev git stash popstash是一个另起一头的临时储物柜存放你改到一半的工作区内容。它救过我不下十次。4. 合并分支从命令到 IDE完整走一遍4.1 合并前的那点小事状态、基线、方向合并分支这件事操作上很简单但错误成本很高。我先把合并前必须做的三件事提纲挈领写出来确认工作区状态确认当前分支到底是谁确认本地分支已经同步到最新。首先工作区有未提交修改时最好不要直接合并。否则一旦合并产生冲突你会同时面对“自己的改动”和“合并产生的冲突”很难厘清。所以合并前先git status看一看有未提交修改就先 commit 或 stash。其次确认当前分支。很多人犯过一个经典错误在dev分支上敲git merge dev然后一脸茫然地问“为什么没有反应”。因为 Git 的git merge是把“参数指定的分支”合并到“当前分支”你站在 dev 上要合并 dev等于自己合并自己。所以要把 dev 合并到 test 的正确做法是git checkout test git pull origin test git merge dev再次本地分支要尽量同步到最新。如果你在 test 分支上test 远端已经前进了一大截而你的本地 test 还停留在几天前合并出来的结果很可能包含一堆过时历史。标准的操作是在合并前先git pull。4.2 fast-forward 与 --no-ff不只是参数问题很多教程把 fast-forward 和“普通合并”讲得很复杂但实际逻辑很简单。假设当前 test 分支指向提交 Adev 分支基于 A 往后走了提交 B 和 C。那么把 dev 合并到 test 的时候test 没有产生任何新的提交它只要把指针从 A 直接挪到 C 就行了。因为 dev 上包含了 test 需要的所有提交而且在历史上是“一条直线向前”的这种合并叫 fast-forward快进合并Git 默认会这样做。但注意一个关键点快进合并不产生新的“合并提交”分支历史会保持直线。这看起来很好但如果团队喜欢“保留合并痕迹”比如把 feature 分支合并到 dev 的时候希望专门留一个“整合了某个功能”的提交那快进合并满足不了需求。不想要快进合并就加参数git merge --no-ff dev意思是“哪怕能快进也强制生成一个合并提交”。你可以在git log --oneline里看到一个节点写着 “Merge branch dev into test”。这种写法适合需要保留功能合并痕迹的场景。反过来如果你想要完全线性的历史还可以用git merge --ff-only dev。这个参数的意思是“如果不能快进就拒绝合并”。它可以用来防止不小心生成一堆复杂的合并提交适合对历史整洁度有执念的团队。4.3 完整实操把 dev 合并到 test我用一个最经典的工作流场景来演示本地有 dev 和 test 两个分支test 是集成测试分支dev 是新功能开发分支现在要把 dev 的代码合并到 test。第一步切到 test 分支并同步最新git checkout test git pull origin test第二步执行合并git merge dev如果没有冲突Git 会输出“Fast-forward”或者“Merge made by the ort strategy”之类的提示。然后你直接推送即可git push origin test如果有冲突Git 会提示你冲突文件清单这一节先不展开第五节专门讲。在 IDEA 里操作路径是右下角分支选择器切到test→ 菜单栏 Git → Merge into Current... → 选择dev→ 点 Merge。在 TortoiseGit 小乌龟里路径是右键仓库目录 → Switch/Checkout 切到 test → 再右键 → Merge... → 选择 dev → 确认。流程与命令行完全一一对应只是少了要敲字的环节。很多同学看到“idea dev 分支代码合并到 test”的热搜词找的就是这个操作。本质上不管用什么工具合并的对象和方向是一致的先切到目标分支再操作合并来源分支。4.4 合并不是越多越好什么时候适合合并什么时候要谨慎我得提醒一句分支合并操作本身没有成本但它会改变主干的历史结构。如果你的 dev 分支拓扑已经乱成一团直接合并到 test 会让 test 上出现两个大分叉。这种情况有些人会在合并前先对 dev 进行一次整理比如用rebase把 dev 的提交重新铺到 test 最新提交之上保证合并时 Git 能尽量走快进路径。我的个人习惯是本地个人分支可以随意 rebase 整理再合并公共分支比如 test、main上的合并始终用git merge尽量不在公共分支上做 rewrite 操作。这个习惯帮我规避了很多团队协作事故。5. 冲突处理与合并中断从一脸懵到游刃有余5.1 冲突是什么时候产生的以及它到底“冲突”了什么先给你一颗定心丸不是所有合并都会冲突。只有当两个分支都修改了同一个文件的同一片段并且 Git 无法判断哪边是最终结果时才会产生冲突。如果只是两个分支各改了不同文件Git 通常可以自动合并不需要人插手。打个比方你和同事同时编辑了同一份文档的同一个段落你写了“付款成功后自动跳转”他写了“付款成功后发放优惠券”。Git 没法替你们决定保存哪句话于是它把两句话都留着让你来拍板。它在文档里插入了冲突标记并告诉你“我需要你亲自解决”。当冲突发生时Git 的提示一般是这样的CONFLICT (content): Merge conflict in src/login.js Automatic merge failed; fix conflicts and then commit the result.看到CONFLICT (content)字样基本就是“内容冲突”需要你手动处理。5.2 冲突标记解读与解决操作打开冲突文件你会看到类似这样的内容 HEAD 这里是当前分支test上的代码 这里是合并进来的分支dev上的代码 dev这三段标记很好理解 HEAD到之间是当前分支也就是你执行 merge 时所在的分支的内容到 dev之间是合并来源分支的内容。解决冲突就是决定最终保留哪段、删掉哪段然后把三行冲突标记全部删除。如果你想要当前分支的内容就保留下面的 HEAD 段如果你想要 dev 的内容就保留 dev 段如果两边都想要就把它们手工拼接在一起。有些配置下文件中还会出现这样的段落||||||| 3a4b5c6 这是合并前两个分支的公共祖先版本这个段落是 diff3 风格的冲突标记显示的是“两个分支分叉之前的公共内容”能帮你理解原始状态。当你不确定怎么取舍时看这段公共祖先内容非常有帮助。可以用git config merge.conflictStyle diff3开启这个风格。解决完文件内容后执行git add src/login.js git commit注意合并冲突解决后的提交直接git commit就行Git 已经帮你把提交信息准备好了默认是Merge branch dev into test之类的内容。即使你不喜欢也可以先提交再修改总比卡在“冲突未解决”状态好。用 IDEA 的朋友更方便在提交面板里右键冲突文件 → Merge会打开一个三方合并视图中间是结果区域左右两侧分别显示当前分支和合并分支。点箭头可以把左边或右边的代码块移到中间非常直观。5.3 合并没合完能不能终止能。如果你在解决冲突的过程中发现不对劲或者意识到自己合并错了分支可以立刻终止合并git merge --abort这个命令会把工作区恢复到合并开始之前的状态相当于撤销一切合并动作。注意它只对“正在进行的合并”有效如果合并已经完成并提交了那就不是 abort 的事了。合并完成之后如果发现结果不对还有一个“后悔药”git reset --hard ORIG_HEADORIG_HEAD是 Git 在 merge、reset 等操作前自动保存的“上一个位置”。执行这条命令可以回到合并开始之前的提交。但我要提醒一句reset --hard会丢弃工作区里所有未提交的修改所以执行前务必确认自己不需要保留当前改动。如果没有 push 到远端这个操作是安全的如果已经 push 了那就不能用 reset 了只能通过反提交或 revert 来纠正而且还要跟团队同步处理非常麻烦。5.4 合并和变基分清楚别乱用这里不可能不提到 rebase。很多人分不清git merge和git rebase的区别说一个最直白的版本merge 是把两条线拧在一起产生一个“融合点”rebase 是把你自己的提交一个个“摘下来”再按顺序铺到另一条线上整个过程像重新书写历史。rebase 的好处是提交历史看起来特别干净全是线性的一条线代价是会改写提交哈希也就是改写了历史。如果那个分支已经被推送到了远端而且别人也在用你就不能随便 rebase否则两个人会对着同一组提交产生两套哈希混乱程度会翻倍。我的原则再强调一遍个人分支随便 rebase公共分支一律 merge。团队协作时用约定固定下来大家按一个路子走比天天争论谁对谁错高效多了。6. 分支实战中高频问题与工作流建议6.1 分支相关高频问题速查我把这些年遇到的分支问题搜罗了一遍整理成下面的速查表非常实用。问题现象大概率原因推荐处理不记得当前在哪个分支一时迷糊git branch --show-current当前分支有未提交修改切到别的分支失败目标分支与当前改动冲突git stash暂存切完再git stash pop分支列表里还显示着已删除的远程分支本地缓存了远端旧引用git fetch --prune或git remote prune originIdea 右下角不显示当前分支新版 IDE 布局变化打开 Git 工具窗口或状态栏设置重新开启Idea 拉取分支提示 rebasingPull 更新方式设置成了 rebase在 Settings → Version Control → Git 里将 Update 方式改为 Merge删除分支报错分支还没合并完用git branch -D强制删除但要确认内容确实不要了合并之后发现合并错了分支操作前没确认方向合并产生前用git merge --abort已提交且未推送用git reset --hard ORIG_HEAD命令提示 Cannot lock ref可能是.git目录下有残留的锁文件退出 IDE删除.git下对应的 lock 文件再试远程分支太多想清理线上合并策略太随意约定功能合并后删除远程分支本地定期git fetch --prune这个表我建议收藏一下遇到同类问题直接来查。很多看起来吓人的报错实际原因往往很简单。6.2 一套简单实用的分支工作流建议分支的使用其实没有绝对正确的标准但有一套组合拳非常通用我强烈建议小团队直接套用开发新功能时从 dev 分支拉一个功能分支命名带上功能号比如feature/order-123-list。功能开发过程中频繁提交提交信息写清楚“做了什么”别写“update”这种毫无信息量的字。功能自测通过后先把 dev 最新代码拉下来合并进功能分支确认没有冲突再把功能分支合并回 dev。合并回 dev 时优先考虑git merge --no-ff保留一个合并节点方便后续追溯“这个功能是什么时候进来的”。test 分支作为集成测试分支从 dev 合并过来测试通过后再从 test 或 dev 合并到 main。下线或合并完成的功能分支及时删除。分支不是资产堆得越多心智负担越重。这套流程里分支的查看、创建、合并都会用到而且越用越顺手。等到你形成肌肉记忆就不会再被“该在哪个分支上操作”这种问题卡住了。还有一个可能被忽略的小点合并之前先看一眼git log --oneline -5确认你给自己留的提交状态是能解释得清的。如果连自己都解释不清最近几个提交在干嘛那合并不如晚点做。我个人习惯里还有一条任何一次 merge 之前我都会先运行git status确认当前分支名和干净状态再运行git pull同步基线。因为等你在一个十分钟的合并冲突里发现“哦原来我在别的分支上合并了一个错误的分支”时心态已经很难救了。Git 分支的逻辑真的不复杂难的是每次动手前保持清醒。希望这篇围绕查看、创建、合并展开的内容能帮你把动手前该看的、动手时该做的、动手后该收的都捋顺一遍。
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →