ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Git任务切换实战:stash、分支合并与SSH认证全攻略

Git任务切换实战:stash、分支合并与SSH认证全攻略 先抛个真实场景。上个月周五下午我正蹲在feature/pay-redesign分支上改支付页样式改到一半测试群炸了线上首页白屏领导说十分钟内必须出热修包。我下意识敲了git checkout master结果 Git 直接甩了一屏error: Your local changes to the following files would be overwritten by checkout。工作区里十几个改动文件全卡在原地那一刻我整个人是僵的。后来用git stash push -u -m pay redesign wip三秒存好现场切分支修 bug发布完再切回来继续写。这个从手忙脚乱到勉强拿捏的过程就是我今天想跟你完整复盘的东西Git 下面的任务切换到底应该怎么玩。这篇内容大量来自我自己踩过的坑也会把 Git 安装配置、SSH 认证失败、分支合并这些高频问题全串起来讲透。1. 先搞清楚为什么切个分支就手忙脚乱1.1 工作区不是你想切就能切很多人第一次遇到切换分支被拒时第一反应是 Git 有毛病。其实 Git 的底层逻辑特别死板它允许你自由切换分支的前提是切换后工作区内容必须能和目标分支完全对齐不能出现文件内容上的冲突。展开说Git 管理状态的核心是三棵树工作区你肉眼看到的文件、暂存区index你执行git add后改动被记录的地方、版本库HEAD当前分支最后一次提交的完整快照。你执行git checkout master时Git 实际干了三件事把 HEAD 指针从当前分支挪到 master用 master 指向的提交覆盖暂存区再用暂存区同步工作区。如果工作区或暂存区里有和 master 版本不一致的已跟踪文件Git 就会认为覆盖会导致代码丢失于是直接拒绝切换。这就好比你在办公桌上改一份正在传阅的合同改到一半同事要把他的版本拿给你看但前提是你得先把桌面恢复成原样。你手上捏着红笔没放下桌面乱七八糟他当然不敢把新文件放上来。1.2 未提交改动去向的三种状态要彻底搞懂切换为什么失败得把工作区里的文件状态分清楚。按 Git 的视角未提交改动其实分三种已跟踪但未暂存比如你打开某个已入库的.java文件改了几行但没git add。这种状态是切换分支报错的重灾区因为 Git 必须决定这些改动是留在工作区还是被目标分支覆盖。已暂存你执行了git add改动进了暂存区但还没 commit。这种状态同样会触发切换保护机制。未跟踪文件新建文件Git 从没见过它比如src/newModule.js。这种文件默认不在版本控制里切换分支时通常能顺利带过去。前两种状态只要目标分支里对应文件内容和当前分支不一致就会被切分支拦下来。第三种状态理论上不拦但它会像幽灵一样跟着你到处跑在 A 分支新建的文件切到 B 分支后它还在工作区如果不小心提交就变成 B 分支的代码了。1.3 任务切换的真实成本与影响范围表面上看切分支被拒只是个报错但它引发的连锁反应远不止丢代码这么简单。我把任务切换失败造成的影响分成了四层每一层我都亲身体验过第一层是现场丢失。为了强行切分支有人直接git checkout -- .把未提交改动全扔了等切完才发现刚才改的那版设计稿没了。这属于不可逆操作唯一救星是 IDE 本地历史但有时候连本地历史也救不回来。第二层是提交污染。有人怕丢改动干脆在当前分支直接 commit然后把半成品代码推到远端。最惨的后果是同事git pull拉下来一个跑不起来的版本你还会在分支历史上留下一条wip提交后续 code review 都得围绕这条垃圾记录解释半天。第三层是上下文切换成本。人脑不是寄存器你从功能 A 切到热修 B再切回 A重新加载刚才改到哪个变量那个函数为什么这么命名这些上下文至少需要十几分钟。切换越频繁大脑缓存失效越严重效率断崖式下跌。第四层是团队协作混乱。一旦把中间态代码提交到了共享分支轻则同事本地出现冲突重则直接带着错误代码上线。所以任务切换这件事从一开始就不该靠手速快硬扛而是要靠一套固定流程把风险和成本压到最低。2. 现场保护第一课git stash 的深度玩法2.1 基础用法三秒存现场Git 官方给任务切换准备的最重要武器就是stash。它的作用是把当前未提交的改动整体打包存到一个临时抽屉里让工作区回到干净状态。行为上很像游戏里的存档你随时可以读取也可以选择删掉存档。基础的命令组合是这样的# 保存现场建议一定加 -m 写备注不然 list 里全是 WIP on xxx 根本认不出来 git stash push -m feat: 支付页样式改到一半 # 查看所有现场存档 git stash list # 恢复到最近一次存档并把这个存档从列表里删掉 git stash pop # 恢复到指定存档但保留它在列表里适合想恢复后对比一下再决定 git stash apply stash{1} # 删除某个指定存档 git stash drop stash{1}注意pop和apply的区别。pop等于apply drop日常恢复现场最顺手apply适合你还想留着这个存档做个对照的场景。我自己习惯是只要能确认恢复成功一律用pop避免 stash list 越堆越长到最后自己都忘了哪个是哪个。另外还有一个恢复现场后的隐藏动作stash只恢复了工作区改动不会自动帮你执行git add。也就是说之前已经git add过的文件pop回来之后会回到已暂存状态吗实测下来git stash push默认会把暂存区和工作区一起打包pop时也会一起还原暂存状态基本能保住。但这有个前提restore 过程中没有发生冲突。一旦冲突冲突文件会落在工作区暂存状态直接被打散需要手动重新处理。2.2 别让 untracked 文件坑你两个必学参数git stash push默认只存已跟踪文件的改动。什么意思如果你新建了一个文件但还没git add它属于 untracked 状态普通 stash 根本不会理它。切完分支你会发现老文件都干净了但新文件还在工作区孤零零躺着。等你切回来pop它也不会出现在恢复列表里很容易在某个分支上被误提交。解决方法是加-u参数git stash push -u -m wip: 支付页重构包括新组件-u是--include-untracked的缩写会把所有未跟踪文件也一起打包。如果连忽略文件gitignore里的也想一起存用-a参数但那个场景很少见日常有-u就够。另一个参数是--keep-index它的作用是stash 保存全部改动但保留暂存区里的内容不动。举个例子你做了两个文件改动一个已经git add一个还没你想先把已暂存的那部分提交掉再处理另一个。这时候就可以用git stash push --keep-index -m 只提交已暂存的部分 git commit -m feat: 完成 A 模块 git stash pop这个玩法在代码 review 前特别有用先只提交符合规范的代码把还在实验期的改动留在 stash 里。2.3 stash 与临时分支怎么选别看 stash 好用它并不是所有任务切换场景的最优解。我自己总结了一条经验改动少、时间短用 stash改动多、跨设备、时间可能超过半天用临时分支。原因有三个。第一stash 存在本地仓库的.git内部不推送到远端如果你要换电脑继续干活stash 是不会跟着走的。第二stash list 里堆太多存档后查找和区分成本暴涨我见过有人存了二十多个 stash最后全删了重新攒。第三stash 无法参与代码评审它只是你本地的草稿箱如果改动量大到需要别人 review那它本来就是一条应该提交的分支。所以当我预估这个任务要持续一天以上或者我需要在家里的电脑上继续写我会直接用分支来存档git switch -c wip/pay-redesign git add -A git commit -m wip: 支付页重构中间态 git switch master # 处理紧急任务这种 WIP 分支推送上去后就算电脑炸了代码也不丢。等任务恢复直接git switch wip/pay-redesign继续写写完用交互式 rebase 把中间态提交整理干净再合回主干。这个习惯在经历过一次笔记本硬盘报销之后我是真的再也没丢过代码。2.4 pop 冲突避坑stash 也不是百分百安全最常见的坑是pop的时候撞上冲突。场景是你在master分支上 stash 了一个现场然后切到feature/order分支干了一段时间干完切回master时顺手git pull拉了新代码然后再pop。这时候如果新拉的 master 上恰好改了你 stash 里涉及的文件Git 就能恢复现场但会告诉你产生了冲突。pop冲突后有两个后果必须牢记第一stash 列表里的这个存档不会自动被删除Git 怕你恢复失败丢数据所以留着它。第二冲突文件处于unmerged状态你需要在 IDE 里逐个解决冲突然后手动git add和commit这个过程和合并分支时解冲突完全一样。我的处理习惯是pop之前先看一眼目标分支有没有动过相关文件git stash show -p stash{0} | head -n 100或者干脆用git stash show stash{0}看哪些文件被改过。如果这个存档只改了我自己的两三个模块而目标分支最近的提交也集中在这几个模块上那就要做好冲突的心理准备提前把 IDE 打开准备手动合并。其实冲突本身不可怕真正可怕的是你pop完看到满屏的 HEAD然后心一慌不知道从哪下手。后面第 3.4 节我会专门讲合并冲突的底层处理逻辑理解了那套逻辑stash 冲突也就是个流程问题。3. 分支操作与现场恢复一套完整的热修流程3.1 新分支怎么建才安全任务切换的核心操作依然是分支所以新建分支的姿势很重要。Git 里分支本质上只是一个指向提交的指针git switch -c feature/xxx的意思是基于当前 HEAD 创建一个新指针然后切过去。关键问题来了当前 HEAD 是哪个提交直接决定了你新分支的起点。很多人习惯在本地master分支上直接git switch -c feature/xxx这是隐患最大的习惯。因为本地 master 可能已经落后于远端 master等你开发完准备合回主干会发现别人的新代码和你的改动纠缠在一起冲突解到怀疑人生。正确的做法是先同步远端再基于最新 master 拉分支git fetch origin git switch -c feature/pay-redesign origin/master注意这里的origin/master是本地仓库记录的远端分支状态git fetch origin之后它会被更新到远端最新。这样建出来的新分支起点就是远端最新代码后续冲突概率大大降低。如果你们团队已经用上了main作为主干名把命令里的master换成main即可。3.2 IDEA 创建新项目拉取 Git 的姿势很多从 IDE 入门 Git 的同学会直接从 IDEA 的欢迎页点Get from VCS粘贴仓库地址点 Clone然后项目被拉下来。这本身没毛病但有几个细节没处理好的话后面能卡你半天。先说 IDEA 里创建新项目拉取 Git 的标准路径。打开 IDEAFile - New - Project from Version Control粘贴仓库 URL选好目录点 Clone。仓库拉下来之后IDEA 第一次打开会有两个很容易踩的坑一是 Maven 或 Gradle 项目需要重新加载依赖耐心等右下角进度条跑完不要急着改代码二是 IDEA 会提示 Unregistered VCS root detected 或者问你是否信任这个项目如果平时都是自己拉自己的仓库直接信任即可但如果是拉一个来路不明的外部项目建议先扫一遍再信任。切换任务时IDEA 里最常用的操作其实是CtrlShiftA调出Git Branches面板然后点Checkout。如果你在 IDE 里改了一堆代码没提交点 Checkout 时 IDEA 会弹一个Local Changes提示问你是Shelve Changes还是Smart Checkout。这个Smart Checkout本质上就是自动帮你 stash但它的处理方式更细能保留的改动尽量保留冲突部分再询问你。我个人更依赖命令行因为看得清楚但如果你已经习惯 IDEAShelve Changes也是一个不错的现场保护方案它比 stash 更直观而且可以在 IDE 的版本控制面板里管理。3.3 热修完成后合回主干热修分支写完代码、测试通过、推送之后最后一步是把修复合回主干。很多新人直接把 hotfix 分支的代码merge到本地 master再 push流程也没问题但缺少了一些细节保护。我推荐一套完整的热修合入流程# 回到 master 并同步远端 git switch master git pull --ff-only origin master # 合入热修分支--no-ff 是为了保留一条清晰的合并记录 git merge --no-ff hotfix/homepage-white-screen # 推送主干 git push origin master # 删除本地和远端的热修分支 git branch -d hotfix/homepage-white-screen git push origin --delete hotfix/homepage-white-screen其中git pull --ff-only这个参数值得重点讲一下。--ff-only的意思是只允许快进式合并如果本地 master 落后于远端且和远端产生分叉它会直接拒绝避免自动生成一个毫无意义的 merge commit。而在合入热修分支时我故意用--no-ff因为热修这种爆炸性变更在历史上留下一个独立的合并节点后续回溯成本会低很多。3.4 合并merge/rebase/cherry-pick怎么选分支合并是这个流程里最绕不开的技术点也是热词里git分支合并搜索量高的原因。Git 提供三个主要合并手段我来逐个拆merge把另一条分支的全部提交合并到当前分支会产生一个合并提交除非是快进合并。优点是操作简单、保留原始提交历史缺点是多出很多 merge commit日志会显得杂乱。用git merge --no-ff feature/xxx是最常见的团队协作姿势。rebase把当前分支的提交摘下来在目标分支最新提交后面重新放一遍。放完之后历史是一条直线非常干净。代价是提交的 hash 会改变如果这些提交已经被别人拉走了强制 rebase 会引发团队灾难。所以它的使用铁律是只 rebase 自己还没推送到远端的提交。我用 rebase 的场景基本是本地开发了几条提交想和最新 master 合并历史时执行git rebase origin/master把冲突一个个解决掉再 push。cherry-pick把某一条分支上的某个具体提交复制到当前分支。它适合那种只想要一个修复不想把整条分支都合过来的场景。比如你发现 hotfix 分支里第 3 个提交修了一个小问题但整条分支还没测试完可以先 cherry-pick 这个提交到 master 应急git cherry-pick 7a3f9b2三种方式都有适用的场景但新手我应该只推荐 merge。原因很朴素merge 的冲突处理是一次性的所有冲突都在一个合并提交里解决rebase 则可能让你在每一个被重放的提交上都处理一遍冲突折磨程度成倍上升。等 merge 用得很顺了再尝试 rebase 收历史。合并时冲突文件长什么样打开冲突文件后你会看到 HEAD到是当前分支的内容到 feature/xxx是待合入分支的内容。你需要手动保留正确部分删掉分隔符然后git add这个文件最后git commitmerge 时或git rebase --continuerebase 时。这个过程没有魔法就是在编辑器里做内容取舍。4. 环境准备从安装到 SSH 认证的常见坑4.1 Git 安装与第一份配置前面聊了这么多操作如果你机器上还没装好 Git 或者配置不对后面全是白搭。这里把安装和初始化配置串一下。Windows 用户去官网下载 Git for Windows 安装包一路 Next 即可安装过程中可以顺手把Git Bash添加到右键菜单。装好后打开 Git Bash执行git --version看到版本号就成功了。macOS 用户最简单的方式是brew install gitLinux 用户根据发行版走apt install git或yum install git。这个环节坑不多真正坑人的是装完后的全局配置没做导致提交记录里出现一串看不懂的user.name占位符。全局配置至少要设置用户名和邮箱git config --global user.name 你的名字 git config --global user.email 你的邮箱这两个信息会跟着每次提交走代码评审平台GitHub、GitLab 等就是靠它关联账号的。如果邮箱写错你的提交在平台上的头像和账号关联会出现幽灵提交解决起来非常麻烦。另外一个我强烈建议在 Windows 上设置的全局配置是行尾符规则git config --global core.autocrlf trueWindows 下打开旧项目时最常见的问题就是整个文件的 diff 全红但肉眼根本看不出有什么区别。原因就是行尾符Windows 用 CRLFLinux/macOS 用 LF。Git 在不同平台间转换行尾时处理不当就会触发这种假 diff。core.autocrlf true的意思是提交时把 CRLF 转成 LF检出时转回 CRLF这样跨平台协作基本不会出乱子。macOS/Linux 用户如果涉及跨平台可以设置core.input true但 Windows 上直接true最省心。还有两个我必配的全局项git config --global init.defaultBranch main git config --global alias.co checkout git config --global alias.br branch git config --global alias.st statusinit.defaultBranch main让新仓库默认不要叫 master别问问就是历史包袱现在新项目用 main 是主流习惯。alias 是给常用命令起短名省得敲那么多字母。4.2 SSH 认证失败的排查实录热词里有个ssh认证失败 git我太懂这个了。几乎每换一台新电脑Git 和代码平台之间的 SSH 连接都要折腾一轮。最经典的现象是git clone gitgithub.com:foo/bar.git # 输出 Permission denied (publickey).按我的排查顺序来效率最高第一步确认本地有没有密钥ls -la ~/.ssh/如果里面没有id_ed25519和id_ed25519.pub就先生成一个ssh-keygen -t ed25519 -C 你的邮箱一路回车即可除非你想设置 passphrase。生成后再确认公钥内容cat ~/.ssh/id_ed25519.pub第二步把公钥添加到远端平台。GitHub 在 Settings - SSH and GPG keys 里 New SSH keyGitLab 在 Preferences - SSH Keys。把id_ed25519.pub里那一整行复制进去保存。然后测试连接ssh -T gitgithub.com # 如果显示 Hi xxx! Youve successfully authenticated说明通了如果你是 GitLab 用户就测ssh -T gitgitlab.company.com。如果测试还是 Permission denied进入第三步检查 SSH agenteval $(ssh-agent -s) ssh-add ~/.ssh/id_ed25519这一步经常被忽略。特别是你手动把私钥文件拷贝到.ssh目录、但 agent 里没注册的情况下认证就是会失败。还有一种情况你用sudo执行 Git 命令时HOME环境变量会变导致 SSH 找不到.ssh目录也会报 Permission denied。这种时候别死磕直接不用 sudo 再试一次。如果以上都排查完还是失败检查你的 clone 地址是不是写错了。gitgithub.com:foo/bar.git是 SSH 格式https://github.com/foo/bar.git是 HTTPS 格式混用会牛头不对马嘴。另外公司内网 GitLab 可能用的不是 22 端口需要在.ssh/config里添加端口配置这个下一节一起讲。4.3 多账号场景下的 SSH 配置很多人的实际状态是公司 GitLab 一个账号、个人 GitHub 一个账号两个账号生成的 SSH key 不能共用一个。如果你把个人 GitHub 的公钥加到公司 GitLab 上或者反过来都会出现奇怪的认证失败。正确的做法是给不同平台配置不同的 key 和别名。先分别生成两把 keyssh-keygen -t ed25519 -C workcompany.com -f ~/.ssh/id_ed25519_work ssh-keygen -t ed25519 -C personalgmail.com -f ~/.ssh/id_ed25519_personal然后在~/.ssh/config里做分流Host github.com HostName github.com User git IdentityFile ~/.ssh/id_ed25519_personal Host gitlab.company.com HostName gitlab.company.com User git IdentityFile ~/.ssh/id_ed25519_work这样当 Git 连接github.com时会自动用个人 key连接公司 GitLab 时用工作 key。配置完记得ssh -T gitgithub.com和ssh -T gitgitlab.company.com分别测一遍。跟 SSH 多账号配套的还有 Git 本身的本地身份设置。一个仓库可能需要以公司身份提交另一个仓库用个人身份你可以在仓库目录下执行不带--global的配置git config user.name 你的中文名 git config user.email workcompany.com这样这个仓库里的提交就只绑定公司账号。如果之前用错了身份提交可以用后文会讲的git rebase和commit --amend去修正但最好的做法是在第一次提交前就确认身份。5. 命令速查与场景化作战手册5.1 高频命令速查表先放一份我在任务切换中最常用的命令清单覆盖了 90% 的日常操作。你可以把它存成一个 cheat sheet或者干脆做成 shell alias。使用场景命令补充说明查看当前状态git status最常用没有之一查看分支git branch -vv能看到每个分支和远端追踪关系快速切换分支git switch feature/xxx也支持git checkout feature/xxx回到上一个分支git switch -和 shell 的cd -一样好用基于远端最新建分支git switch -c feature/xxx origin/master起点很关键保存现场git stash push -u -m 描述加-u连新文件一起存恢复现场git stash pop恢复并删除存档查看存档列表git stash list出现两次以上说明该清理了创建临时存档分支git switch -c wip/xxx git add -A git commit -m wip跨设备或长时间任务用拉取最新代码git pull --ff-only origin master避免意外合并提交合并分支git merge --no-ff feature/xxx推荐保留合并记录查看提交图git log --graph --oneline --all --decorate一分钟看懂仓库全貌丢弃工作区改动git restore .需要确认真的不想要了再执行查看某文件历史git log --follow -- file.txt--follow能跟踪重命名查看一个具体的提交git show commit-id含 diff 内容git switch -这个命令我要单独吹一下。它跟cd -一样能在最近两个分支之间反复横跳。热修完成切回开发分支再想回看热修分支时一个git switch -就解决省得每次打一长串分支名。5.2 三个经典切换场景演练场景一开发到一半紧急线上 bug。当前状态feature/pay-redesign分支上改了一堆文件有新增有修改还没提交。需要切到 hotfix 分支修线上问题。git stash push -u -m pay redesign wip 2026-06-06 git switch -c hotfix/homepage-white-screen origin/master # 这里修 bug、提交、按 3.3 节流程合入 master 并推送 git switch feature/pay-redesign git stash pop如果没有冲突直接回到开发状态有冲突按 IDE 提示解决。场景二新任务开工前发现本地 master 已经不是最新的了需要拉取并创建新分支。git switch master git pull --ff-only origin master git switch -c feature/order-status origin/master重点新分支永远不要基于一个过时的本地 master 创建。git switch -c feature/xxx origin/master是让我少吵无数架的命令。场景三代码改错分支了要迁到正确分支。常见于你忘记先切分支直接在master上开写。这时候不要慌git stash push -u -m 误提交到 master 的改动 git switch -c feature/correct-branch origin/master git stash poppop后工作区恢复继续正常提交。这个操作能救回我无数次手滑比git reset重来安全得多。5.3 把切换成本降到最低的工作流建议命令掌握的再多都不如从流程上减少切换次数。我现在的个人工作流基本固定成下面这套第一每个任务从开始就拥有自己的分支命名直接用feature/需求标识。绝不在 master/main 上直接写代码哪怕只是改一个标点。第二任务进行中坚持小步提交一个文件改完就git commit不要攒到晚上一次性提交。小步提交的最大好处是你随时可以把当前分支丢下切走甚至整个分支作废损失都极小。第三每次切换前强制三秒钟思考这个现场是要用 stash 还是建 WIP 分支。临时小改动用 stash大功能开发用 WIP 分支这个判断想清楚能少踩很多坑。另外我还强烈建议你给 shell 配上 Git 分支提示。现在的 zsh 主题大都能自动显示当前分支名bash 用户也可以通过 Git 官方提供的git-prompt.sh实现。当命令提示符里时刻显示着feature/pay-redesign时你很难在错误分支上操作太久。最后分享一个压箱底的小技巧。如果你跟我一样在多个任务之间高频切换并且经常需要同时开几个分支并行干活git worktree值得了解一下git worktree add ../pay-redesign feature/pay-redesign这个命令会在仓库外面新建一个工作目录里面直接挂着你指定的分支。这样你就有了两个互不干扰的工作区一个文件夹在写支付页重构另一个文件夹在修线上 bug每个工作区都有自己的独立分支、独立改动完全不需要 stash 和 pop。我现在的多任务切换已经彻底依赖 worktreestash 反而只在极短切换时使用。它的唯一缺点是需要习惯同一个仓库放在不同目录的思维但你一旦用上大概率就回不去了。任务切换这件事说白了不是考验手速而是考验你有没有一套在任何突发状况下都能稳定保存现场、切换分支、处理合并和恢复进度的固定动作。在我自己踩过无数次坑之后现在的顺序已经很机械先看git status再决定切分支方式切过去先git pull --ff-only干完回来再git switch -和git stash pop。这套流程下来手忙脚乱确实变成了勉强拿捏。你按照这篇里的流程跑一两次应该能找到属于你自己的节奏。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进