Git如何优雅的进行版本回退?
本转载自微信公众号 良许Linux
在版本迭代开发过程中,相信很多人都会有过错误提交的时候(至少良许有过几次这样的体验)。这种情况下,菜鸟程序员可能就会虎驱一震,紧张得不知所措。而资深程序员就会微微一笑,摸一摸锃亮的脑门,然后默默的进行版本回退。
对于版本的回退,我们经常会用到两个命令:
- git reset
- git revert
那这两个命令有何区别呢?先不急,我们后文详细介绍。
git reset
假如我们的系统现在有如下几个提交:
其中:A 和 B 是正常提交,而 C 和 D 是错误提交。现在,我们想把 C 和 D 回退掉。而此时,HEAD 指针指向 D 提交(5lk4er)。我们只需将 HEAD 指针移动到 B 提交(a0fvf8),就可以达到目的。
只要有 git 基础的朋友,一定会想到 git reset
命令。完整命令如下:
git reset --hard a0fvf8
命令运行之后,HEAD 指针就会移动到 B 提交下,如下图示:
而这个时候,远程仓库的 HEAD 指针依然不变,仍在 D 提交上。所以,如果直接使用 git push
命令的话,将无法将更改推到远程仓库。此时,只能使用 -f
选项将提交强制推到远程仓库:
git push -f
采用这种方式回退代码的弊端显而易见,那就是会使 HEAD 指针往回移动,从而会失去之后的提交信息。将来如果突然发现,C 和 D 是多么绝妙的想法,可它们已经早就消失在历史的长河里了。
而且,有些公司(比如良许的公司)明令禁止使用 git reset 命令去回退代码,原因与上述一样。所以,我们需要找到一个命令,既可以回退代码,又可以保存错误的提交。这时,git revert
命令就派上用场了。
git revert
git revert的作用通过反做创建一个新的版本,这个版本的内容与我们要回退到的目标版本一样,但是HEAD指针是指向这个新生成的版本,而不是目标版本。
使用 git revert 命令来实现上述例子的话,我们可以这样做:先 revert D,再 revert C (有多个提交需要回退的话需要由新到旧进行 revert):
git revert 5lk4er
git revert 76sdeb
这里会生成两个新有提交:D' 和 C',如下图示:
这里只有两个提交需要 revert,我们可以一个个回退。但如果有几十个呢?一个个回退肯定效率太低而且容易出错。我们可以使用以下方法进行批量回退:
git revert OLDER_COMMIT^..NEWER_COMMIT
这时,错误的提交 C 和 D 依然保留,将来进行甩锅的时候也有依可循。而且,这样操作的话 HEAD 指针是往后移动的,可以直接使用 git push
命令推送到远程仓库里。而这种做法,正是企业所鼓励的。
我们再举个更难一点的例子。
假如现在有三个提交,但很不巧的是,那个错误的提交刚好位于中间。如下图示:
这时,直接使用 git reset 命令将 HEAD 指针重置到 A 提交显然是不行的,因为 C 提交是正确的,需要保留的。先把 C 提交 及 B 提交全部回退,再使用 cherry-pick
命令将 C 提交重新再生成一个新的提交 C'',这样就实现了将 B提交回退的需求。完整的过程如下:
通过以上对比可以发现,git reset 与 git revert 最大的差别就在于,git reset 会失去后面的提交,而 git revert 是通过反做的方式重新创建一个新的提交,而保留原有的提交。在企业里,应尽量使用 git revert 命令,能不用 git reset 命令尽量不用。
推荐阅读:
Java识堂
一个有干货的公众号
- ie6,ie7,ff 的css兼容hack写法
- 使用子查询时应当注意的
- LegacyText的复制的Bug
- 提高WCF服务并发能力的简单处理办法
- 如何给已经有数据的DataTable动态增加一列并赋值
- Gitlab上采用rpm方式快速安装的操作记录
- WCF与IIS集成Windows身份验证的矛盾
- openstack中彻底删除计算节点的操作记录
- 验证码无刷新更换
- 从MapX到MapXtreme2004[12]-SearchNearest!
- Sublime Text的安装与配置
- 复制到浮动层,强调显示某个(多个)区域
- Jquery实现的一种常用高亮效果
- MaCfee导致Asp.net/Jmail无法发送邮件的解决办法
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- Vue 父子组件通信 兄弟组件通信 深层组件通信 方式一览
- Vue使用Element实现增删改查+打包
- 状态管理之Vuex (二) 异步管理
- Spring中AOP相关的API及源码解析,原来AOP是这样子的
- 状态管理之Vuex (一) 基操勿六
- 形式化分析工具(五)使用CAS +语法轻松编写HLPSL规范
- 你知道Spring是怎么将AOP应用到Bean的生命周期中的吗?
- 太实用了!自己动手写软件——密码验证器的界面实现
- 【TBase开源版测评】深度测评TBase的shard分片和冷热分离存储特性
- Python爬虫练手,一个简单的Python资讯采集案例
- 直播带货软件开发过程中,如何实现图片上传
- 太实用了!自己动手写软件——邮件用户名密码验证
- 太实用了!自己动手写软件——SSH、FTP和SQL server的密码破解
- Kaggle Tweet Sentiment Extraction 第七名复盘
- 【翻译】.NET 5中的性能改进