
05-Git常用高阶操作reset/rebase/cherry-pick/merge冲突解决前言做开发三年Git 命令只会add、commit、push三件套这在团队协作里可不够用。当你需要撤销提交、整理分支历史、或者把某个功能单独摘到另一个分支时reset、rebase、cherry-pick这些高阶操作就派上用场了。本文从实际场景出发把这些命令掰开揉碎讲清楚顺带把最让人头疼的 merge 冲突解决流程走一遍。一、git reset三种模式三种后果git reset的核心作用是移动 HEAD 指针当前分支的最新提交位置。根据模式不同对暂存区和工作区的影响也不同。1.1 --soft只动 HEADgitreset--softHEAD~1效果HEAD 回退到上一个提交但暂存区和工作区保持不变。适用场景提交后发现 commit message 写错了或者想把几个连续的提交合并成一个。代码改动都还在暂存区里直接重新commit即可。1.2 --mixed默认动 HEAD 暂存区gitreset--mixedHEAD~1# 或者直接gitreset HEAD~1效果HEAD 回退暂存区被重置但工作区代码不变。适用场景提交后发现多 add 了文件想重新选择要提交的内容。改动还在只是从暂存区退回到了工作区。1.3 --hard动 HEAD 暂存区 工作区gitreset--hardHEAD~1效果HEAD 回退暂存区和工作区全部被重置到指定提交的状态。适用场景本地写的代码完全不要了回到某个干净的版本。注意此操作不可逆除非用 reflog 找回生产环境慎用。一个记忆技巧soft → 只动 HEADmixed → 动 HEAD 暂存区hard → 动 HEAD 暂存区 工作区。从轻到重逐步覆盖。1.4 三种模式对比模式HEAD暂存区工作区风险等级--soft移动不变不变低--mixed移动重置不变中--hard移动重置重置高不可逆二、git rebase变基原理与黄金法则2.1 什么是变基rebase变基的意思是把当前分支的提交搬到目标分支最新提交的后面让提交历史变成一条直线。# merge 之后的提交历史有分叉 A---B---C---D (main) \ E---F (feature) # rebase 之后的提交历史直线 A---B---C---D---E---F (feature)变基过程中E 和 F 会被重新应用到 D 之后生成新的提交 E’ 和 F’SHA 值变了但内容相同。2.2 常用命令# 将 feature 分支变基到 maingitcheckout featuregitrebase main# 交互式变基整理最近3个提交合并、修改message等gitrebase-iHEAD~3交互式变基可以做的操作pick保留该提交squash将该提交合并到上一个提交reword修改提交信息drop丢弃该提交2.3 rebase 的黄金法则永远不要对已经推送到远程仓库的公共分支执行 rebase。原因很简单rebase 会重写提交历史生成新的 SHA 值。如果别人基于旧的提交历史在开发你 rebase 后一 push别人的本地分支就全乱了。简单记rebase 只用于本地分支整理公共分支用 merge。三、git cherry-pick精准挑选提交有时候你只想把某个分支上的某几个提交拿到当前分支而不是合并整个分支。比如 feature 分支上修了一个 bug你想把这个修复单独提到 main 分支但不想把其他还没完成的提交带过来。3.1 基本用法# 挑选单个提交gitcherry-pickcommit-hash# 挑选多个提交gitcherry-pickhash1hash2hash3# 挑选一个范围不包含起始提交包含结束提交gitcherry-pickstart-hash..end-hash3.2 实战场景无人售货柜项目中固件分支firmware-v2上修了一个传感器读取的 bug提交 hash 是a3f5b2c。主干分支main也需要这个修复gitcheckout maingitcherry-pick a3f5b2cgitpush origin main就这么简单精准拿走不多带一个提交。cherry-pick 也会产生冲突解决方式和 merge 冲突一样后面统一讲。四、git merge 冲突产生与解决流程4.1 冲突是怎么产生的当两个分支修改了同一个文件的同一行代码Git 无法自动判断保留哪个版本就会产生冲突。gitcheckout featuregitmerge main# Auto-merging src/main.java# CONFLICT (content): Merge conflict in src/main.java4.2 冲突文件长什么样打开冲突文件你会看到类似这样的标记public void checkSensor() { HEAD int threshold 50; // feature分支的修改 int threshold 80; // main分支的修改 main if (sensorValue threshold) { alert(); } } HEAD到之间是当前分支feature的代码到 main之间是传入分支main的代码4.3 解决冲突的标准流程第一步查看冲突文件列表gitstatus# Unmerged paths:# both modified: src/main.java第二步打开冲突文件手动编辑保留正确的代码删除冲突标记。比如这里我们判断threshold应该取 80publicvoidcheckSensor(){intthreshold80;if(sensorValuethreshold){alert();}}第三步标记冲突已解决gitaddsrc/main.java第四步完成合并gitcommit-mmerge: 合并main分支解决传感器阈值冲突或者用git merge --continue一键完成。4.4 冲突解决的三条原则看懂再改理解两段代码各自的意图别盲目保留一端改完测试解决冲突后必须跑一遍编译和测试确保逻辑没有断裂小步合并频繁同步主干分支减少长期分叉带来的大面积冲突4.5 rebase 冲突的解决rebase 产生冲突时解决流程类似但命令不同# 解决冲突后gitadd冲突文件gitrebase--continue# 如果想中止 rebase回到开始前gitrebase--abort五、命令速查表操作命令风险回退提交保留改动git reset --soft HEAD~1低回退提交改动退回工作区git reset --mixed HEAD~1中回退提交彻底丢弃改动git reset --hard HEAD~1高整理提交历史git rebase -i HEAD~3中挑选提交git cherry-pick hash低合并分支git merge branch中中止合并git merge --abort低中止变基git rebase --abort低总结命令核心用途一句话记忆reset回退到历史提交soft 轻、mixed 中、hard 重rebase整理提交历史为直线只在本地用别碰公共分支cherry-pick精准摘取单个提交像摘樱桃挑熟的那颗merge合并分支冲突不可怕看懂再改掌握这四个命令日常 Git 协作基本够用了。关键不是记住命令参数而是理解每一步操作背后到底动了什么——HEAD、暂存区、工作区搞清楚这三者Git 就不会再让你头疼。