ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

切换分支前先git fetch:避免合入过期代码,掌握远程分支同步核心技巧

切换分支前先git fetch:避免合入过期代码,掌握远程分支同步核心技巧 先问一句你切分支、合并分支之前真的 fetch 过吗别急着点头。我当年也是觉得本地分支不就是跟远程同步的吗直到连续两次翻车——一次把同事刚推的提交当成消失了一次把已经废弃的老代码又合了回来。那之后我给自己定了一条死规矩切换分支、合并分支之前务必先git fetch并且明确以远程分支比如origin/xxx为操作对象。这篇文章不是讲基础命令手册而是把我踩过的坑、验证过的流程和团队里反复出现的假同步问题都复盘一遍希望你能把这些隐患提前掐灭。1. 不先 fetch 就切分支我的两个真实事故现场1.1 事故一同事的新分支消失了前两年团队里有个习惯谁要 review 别人的代码就在 IDE 里点开分支列表双击切过去看。有一次同事在群里喊feature/pay-optimize已经推上去了让我帮忙看看并发问题有没有处理。我照例双击分支名编辑器确实切过去了可翻代码翻了大半天愣是没找到他说改过的那几个类。我的第一反应是他 push 失败了。他一脸无辜地站在我旁边说我明明 push 成功了。两人对着屏幕懵了几分钟最后我用命令行一看才明白我切过去的那个feature/pay-optimize是三天前一次 fetch 留下的旧引用。准确地说我的本地分支版本停在三天前而他把三天的改动一次性推上来之后我的本地分支压根没动。这就是最典型的假切换你以为是切到了最新代码其实切的是本地缓存的旧快照。IDE 分支列表里同名分支通常有好几个双击默认选中本地分支本地分支不 fetch 就永远不知道远程发生了什么。这次事故之后我不再相信 IDE 里那个漂亮的列表了。1.2 事故二把旧代码当成合并来源第二次翻车更隐蔽。那天要把dev合并到release准备发版本。我随手在终端里敲了git checkout release然后git merge devGit 提示合并成功没有冲突一切看起来非常顺利。结果测试跑到一半同事跑过来说dev 上那个数据库连接池的修复怎么没带进来昨天明明合了啊。我愣住了立刻查dev分支的 log。发现问题出在源头上我的本地dev分支停在五天前而同事这几天往远程dev上推了至少十几个 commit其中就包含数据库连接池的修复。我手一抖把五天前的本地dev合并进了release相当于这次发布根本没有包含这几天的任何改动。如果当时我先git fetch再git merge origin/dev这个错误完全没有发生的机会。合代码之前不 fetch就是在拿过期的地图找路。1.3 共同根源我们把本地分支和远程分支混为一谈这两次事故表面上看起来不一样一个切错了、一个合错了但根因是一样的我脑子里默认本地分支和远程分支是同一件事。在 Git 的世界里本地分支和远程分支从来就不是同一件事。本地分支是可读可写的你想怎么动都行远程分支准确说是远程跟踪分支比如origin/dev是 Git 帮你记录上一次和远程仓库通信时远程那个分支长什么样的只读快照。这两个东西一旦分叉所有基于本地的操作都是站在一个可能过期的视角上看问题。我的教训总结成一句话看到origin/xxx才算看到了远程的真实状态看到本地xxx只能代表你上次同步时远程的状态。后面所有章节都是在把这句话变成可执行的习惯。2. 本地分支与远程分支为什么它们天生就会不同步2.1 origin/xxx 不是远程状态是上次同步的缓存先厘清一个概念。你在git branch -a里看到的origin/dev全称是远程跟踪分支remote-tracking branch。它存在的意义是让 Git 在离线状态下也能比较远程和本地差了多少但它本身并不是远程仓库的实时状态而是上一次 fetch 时远程状态的本地缓存。打个比方远程仓库是一栋楼origin/dev是你上次路过时拍的一张照片你手里的本地dev分支是你自己装修出来的房间。照片不能代表楼的实时样貌你的房间更不能。唯一能让照片更新的方式就是重新拍一次对应到 Git 里就是git fetch。所以当你看到有人问为什么我git branch看不到同事的新分支时答案通常只有一个你还没 fetch。本地分支列表只包含本地仓库自己创建的引用以及之前 fetch 过的远程引用。同事一周前推了新分支你不 fetch它就永远不在你的视野里。2.2 fetch 和 pull 的分工别再混淆很多初学者把git pull当成了 fetch 的替代品确实git pull git fetch git merge或rebase但这两者在切换分支、合并分支之前这个场景下有本质区别。我做个表格帮助你快速对照命令做了什么会改工作区吗会改本地分支吗适合场景git fetch只把远程引用更新到本地缓存不会不会切换/合并前安全地获取状态git pullfetch 之后立刻合并/变基到当前分支会会明确要把远程变更拉进当前分支时git pull --rebasefetch 之后立刻变基当前分支会会想保持线性历史时最关键的一点是git pull只能更新当前分支。假设我在master上执行git pull确实把 master 拉新了但本地dev、feature/x这些分支依然是老的。很多人执行完 pull 就以为所有分支都同步了这是个危险误解。只有git fetch才能一次性刷新所有远程跟踪分支这也是为什么我现在的肌肉记忆是操作任何分支之前先一个不带参数的git fetch。2.3 本地分支是怎么悄悄变老的本地分支的老化几乎不可避免。你每次git checkout -b feature/xxx本质上是基于当前某个 commit 创建了新分支之后你在这个分支上提交本地分支往前走。但如果这是一条团队共享的分支比如dev、release那么只要别人向远程推了新提交你的本地dev就相对远程落后了。老化的速度取决于团队节奏。我们团队最忙的时候一天往dev上合三四十个 commit你的本地分支只要半天没拉就已经落后一大截。等你某天心血来潮git merge dev合进去的可能是一个陈旧版本。还有一个常被忽略的来源git checkout同名分支时Git 会优先切到本地分支。如果本地已经有一个feature/login哪怕它很旧你敲git checkout feature/login切到的就是本地旧分支而不是基于origin/feature/login的最新状态。这是 1.1 事故的另一个技术面原因。3. 切换分支之前先 fetch安全切换的标准流程3.1 第一步无副作用的 fetch大胆执行很多人不敢随手git fetch担心它会覆盖本地修改或搞乱工作区。放心git fetch是 Git 命令里最温柔的几个之一它只更新远程跟踪分支不碰你的工作区不动任何本地分支更不会改变 HEAD。我现在只要站到一个仓库面前第一件事通常是git fetch --all --prune拆开解释一下--all表示拉取所有远程仓库如果配置了多个 remote的更新。--prune表示清理远程已经删除的分支在本地留下的引用。比如别人把origin/hotfix-123删了你的本地还留着origin/hotfix-123这个缓存引用--prune会把这种失效引用一并清掉。命令执行完输出会提示你哪些分支更新了、新增了哪些远程引用。这需要联网但如果遇到网络问题比如常见的Failed to connect或 IDE 里报failed to fetch先检查网络和代理配置再回头命令重试。顺便提醒一句这个failed to fetch是网络层错误跟 Git 本身的 fetch 语义无关别混淆。3.2 第二步选择远程分支而不是本地同名分支fetch 只是第一步真正决定你切到哪份代码的是第二步——明确告诉 Git我要基于远程分支创建/切换本地分支。如果你想切的是一个全新远程分支比如同事刚推的feature/pay-optimize标准姿势是git switch -c feature/pay-optimize origin/feature/pay-optimize老项目还在用checkout的话等价写法是git checkout -b feature/pay-optimize origin/feature/pay-optimize这条命令的意思是以远程分支origin/feature/pay-optimize的最新提交为起点创建本地分支feature/pay-optimize并自动建立跟踪关系。这样你切到的就是真正的最新远程代码而不是某个陈旧的同名本地缓存。为什么不直接git checkout feature/pay-optimize因为只要本地仓库里恰好存在同名的本地分支Git 会优先切到本地分支。这时候你需要的不是切换而是基于远程覆盖本地。如果你并不想保留本地旧分支的内容可以这样git branch -D feature/pay-optimize git switch -c feature/pay-optimize origin/feature/pay-optimize但如果你只是想在远程最新代码基础上继续开发且本地没有任何未提交改动很多时候直接切远程分支再继续提交也够用。关键是操作对象永远是origin/xxx而不是裸的xxx。3.3 第三步切完立刻验证跟踪状态切完分支我建议养成一个 3 秒的验证习惯git status -sb这条命令会返回当前分支名和跟踪状态比如## feature/pay-optimize...origin/feature/pay-optimize [behind 2]方括号里的behind 2说明当前分支比远程落后 2 个提交。如果你刚才是用git switch从远程分支创建的本地分支通常不会落后但如果看到ahead 3说明本地多出 3 个提交还没推。这种验证能立刻暴露我是不是切错分支了的问题。有一次我某同事切完分支git status -sb显示behind 6他第一反应是怎么可能我刚建的最后一查才发现自己的本地分支是从旧基线创建的所以才会落后。这个验证动作帮他避免了一次在旧代码上开发半天的惨剧。3.4 切换前先看看工作区脏不脏如果你不是从干净状态切换git switch可能会报错Your local changes to the following files would be overwritten by checkout。这是 Git 的保护机制不是 bug。此时你有两个选择# 方案一临时保存未提交改动 git stash # 切换完处理完事情再切回来 git stash pop我个人建议切换分支前先git status扫一眼确认有没有未提交改动、有没有未跟踪文件。我有一次没检查切换时被 Git 拦住然后随手git stash切过去又忘了 pop最后把一堆改动困在 stash 里花了好一会儿才想起来。总之先看状态再决定是提交、暂存还是直接切。4. 合并分支之前先 fetch把假合并掐死在开始之前4.1 合并前问自己三个问题合代码之前我会机械地问自己三个问题我要合入的目标分支比如release的远程状态我同步过了吗我要合入的源分支比如dev的远程状态我同步过了吗我打算合的是dev还是origin/dev第一个问题管目标分支。如果目标分支落后远程太多你合并完之后还得拉一次而且很可能产生一堆本不该出现的冲突。第二个问题管源分支对应 1.2 事故里的坑。第三个问题最关键它决定了合并的结果到底基于什么版本。这三个问题听起来简单但在高节奏团队里特别容易被跳过。很多事故不是你不会命令而是你在赶时间的焦虑里省略了这个 10 秒的确认动作。4.2 两个安全的合并姿势merge origin/xxx 或 pull合并前先 fetch然后有两个常用姿势# 姿势一先切到目标分支 git switch release # fetch 后合并远程源分支 git fetch origin git merge origin/dev这个姿势的好处是你明确指定要合并的是origin/dev也就是远程最新状态基本杜绝了合了过期分支的可能同时合并过程你可以观察 Git 的提示比如Fast-forward或者Merge made by the ort strategy。另一个姿势是git pull直接在当前分支上拉取并合并远程分支git switch release git pull origin dev这个等价于git fetch origin devgit merge FETCH_HEAD也是安全的。它更适合你不关心其他分支、只想着重合并当前分支的场景。两种姿势我用了很久现阶段我偏向姿势一因为它的语义更直白git merge origin/dev一眼看去就知道合并的是远程分支。如果你发现本地也存在dev但你不知道它是否和远程同步那么git merge dev和git merge origin/dev是完全不同的两个操作前者合本地、后者合远程这个差异一定要刻进脑子里。4.3 合并冲突处理的心态与验证习惯合并冲突本身不是错误它只是 Git 告诉你两边都改了同一块地方我没法替你决定。我在团队里反复讲不怕冲突怕的是不知道为什么会冲突。如果你先 fetch 了、基于origin合并了那么冲突范围一定是真实的代码差异如果你没 fetch 就合并冲突来源可能有一半是过期代码之间的假差异。真正解决冲突时我建议打开每个冲突文件逐个确认保留哪边或者手动合并两边内容。解决完记得执行git add . git commit这步没什么稀奇的但真正的老手会在提交前多看一个东西git diff --stat HEAD这条命令会显示本次合并相对于合并前基线的文件变动列表。如果合并来源是远的你会在 stat 里看到意料之外的大规模变化这时候就能立刻警觉是不是我又合并了不该合并的旧分支比合错之后再翻 log 要省时间得多。5. 验证你确实合并对了三招排查法5.1 git branch -vv 看跟踪关系合并之后怎么确认自己的操作对象是远程分支直接看跟踪关系即可。git branch -vv输出示例dev a1b2c3d [origin/dev] 修复了超时问题 * feature/pay-optimize e4f5a6b [origin/feature/pay-optimize] 调整并发逻辑 release f7g8h9i [origin/release] 发布前版本方括号里显示了这个本地分支跟踪的是哪个远程分支。如果某个分支后面没有[origin/xxx]说明它是一个孤立的本地分支这时候你就要小心它是不是一个陈旧的缓存要不要基于远程重新创建git branch -vv是我合并前最常用的体检命令因为它一眼就能暴露本地分支和远程分支之间有没有对应关系。5.2 git log 看合并来源如果你想看更细的合并细节用这条git log --oneline --graph --decorate -10它会把最近 10 个提交按图形化方式列出来并且标注每个提交属于哪个分支引用。比如你会看到* f7g8h9i (HEAD - release, origin/release) Merge remote-tracking branch origin/dev into release |\ | * a1b2c3d (origin/dev) 修复超时问题 | * b2c3d4e dev: 调整配置 |/ * e4f5a6b 历史提交如果我看到合并提交的说明是Merge remote-tracking branch origin/dev心里就踏实了如果看到Merge branch dev那说明合并的是本地分支不是远程分支就该检查本地分支是否足够新。这一招能帮你区分很多 IDE 界面里看不清的细节。5.3 一旦合错了git reflog 的后悔药即便你流程对了偶尔也会发生合完发现方向不太对的情况。这时候别慌git reflog是你的后悔药。git reflog它会列出你本地 HEAD 最近移动过的历史一行一条比如f7g8h9i HEAD{0}: merge origin/dev: Merge made by the ort strategy e4f5a6b HEAD{1}: checkout: moving from dev to release假设你发现HEAD{1}才是合并之前的正确位置直接重置回去git reset --hard e4f5a6b这个操作只影响本地不会动远程仓库所以是安全的。但要注意reset --hard会丢弃工作区当前状态执行前务必确认没有未提交的改动。团队协作时如果分支已经 push 到远程reset之后再强推需要格外谨慎因为我见过太多强推误伤同事案例这也是为什么我建议大家把验证合并来源放在合完的第一时间。6. 团队协作里的延伸建议让 fetch 成为肌肉记忆6.1 配置 alias 和 fetch 策略习惯这东西靠配置比靠意志力靠谱。我在个人配置里加了一行 alias把高频操作缩成两个字母git config --global alias.sync fetch --all --prune以后只要敲git sync就相当于执行了git fetch --all --prune。我还会配合git config --global pull.rebase true让 pull 默认变基而不是生成一堆无意义的 merge commit。这些配置的目的是降低执行成本让先 fetch 再操作变成一个不需要思考的动作。如果你用 IDE 多于命令行那请在 IDE 的设置里给Fetch All绑定一个快捷键。TortoiseGit 里右键菜单就有 Fetch 选项IntelliJ 系列有CtrlT或菜单Git - FetchVS Code 装 GitLens 之后也能一键 fetch。不管哪个工具核心都一样先点那个刷新按钮再切分支、再合并。6.2 GUI 工具里容易踩的同样坑GUI 工具并没有解决本地分支过期的问题只是把它藏得更深了。以 TortoiseGit 为例它在切换分支对话框里会列出一堆分支看起来都差不多但下面通常有一个Ref或Remote过滤选项。如果你选择的是本地分支列表切过去照样可能是旧代码。IntelliJ 的分支菜单里远程分支一般显示为origin/xxx本地分支显示为xxx双击本地分支就等于我之前说的裸checkout。如果 IDE 的 fetch 操作报错比如日志里出现failed to fetch先别怀疑代码大概率是网络层问题检查代理配置和网络连通性。这个报错跟 Git 语义的 fetch 无关Kubernetes 或 VS Code 远程插件也可能报类似的字眼注意区分场景。6.3 我的实操体会给协作流程定一条规则和团队磨合一段时间后我提了一条非常简单的规则看到远程有新分支或者别人说我推了第一动作永远是 fetch然后基于origin/xxx行动本地分支如果没有对应的远程分支默认当它不可信。这条规则初看很严格但真的能省掉太多无谓的我明明改了怎么看不到的对话。它不要求所有人都懂底层原理只需要记住一个动作顺序fetch → 确认 origin/xxx → 切/合 → 验证。我自己在执行这条规则时最深的一个体会是git fetch几乎没有任何风险它只是让本地仓库记住远程现在长什么样而远程现在长什么样恰恰是切分支、合并分支这个操作最需要的信息。哪怕你最终决定不切换、不合并提前 fetch 一刻也不会亏。另外如果你管理的分支经常被清理建议每周跑一次git fetch --prune。它会把远程已删除分支对应的本地缓存引用一起带走避免本地塞满一堆origin/hotfix-194之类的僵尸分支。这不算什么高深技巧但干净的分支列表会让你在切换和合并时少很多干扰。最后再分享一个小细节每次合并完成后我习惯性看一眼合并提交信息里的来源分支名。如果是origin/xxx放心如果是本地xxx那就立刻用git branch -vv和git log复查一遍。这个习惯帮我挡下了至少三次发布事故也是我现在最想让你带走的一条经验。
返回列表