1:前言
在合作开发领域中,不得不说git这一个版本控制工具是最伟大的发明,在项目开发中已经是IT人必不可少的工具。但是如何给已有项目打补丁,如何在线上运行的项目做增量修改是运维以及开发者需要掌握的一个比较高级的git技术,本文也是笔者自学以及踩坑后才有所心得,如有误区,望读者斧正。
2:背景介绍
近期发现线上有一个bug需要修复,需要紧急上线,因为改动很小,无需整包替换,所以决定采取线上打patch的方式实现修复,因此需要线下根据最新的commit打一个线上对应版本的patch文件,然后将这个patch文件上传到线上,实现打补丁修复。
3:步骤:
(1)在本地创建线上的项目版本的分支
执行以下代码确保补丁分支和线上代码保持一致,这里stable是线上运行的代码的分支名(注意不能完全照抄,依据实际情况自行修改)
git fetchgit checkout -b fix_sg origin/stable
保证打的patch是基于线上项目的基准上的,否则可能会产生冲突以及其他不可预知后果。
(2)检查当前patch分支工作区
检查当前分支工作区干净整洁。
$ git statusOn branch fix_sgYour branch is up-to-date with 'origin/stable'.nothing to commit, working tree clean
(3)修改线上问题的bug后再检查工作区
$ git statusOn branch fix_sgYour branch is up-to-date with 'origin/stable'.Changes not staged for commit: (use "git add <file>..." to update what will be committed) (use "git checkout -- <file>..." to discard changes in working directory) modified: test/services/agents/redis.pyno changes added to commit (use "git add" and/or "git commit -a")
如上所示,要确保只修改了自己想要修改的文件
(4)git commit提交代码
这个不用多说git add + git commit的基础操作。
(5)是否需要git push?
如果,当前的patch修改需要同步到其他所有该项目的分支中,就需要git push到远程,在远程创建一个分支,待日后进行merge或者rebase等其他操作,如果暂时不需要对代码库中其他的分支做操作,可以暂时不需要push,可能下次升级代码会已经修复。笔者这里因为某些原因,这里暂不push,因为不push也可以打patch。
(6)查看commit日志。并给予commit_id生成patch文件
查看提交日志,确保最上层的是自己刚刚第(4)步提交的代码。(笔者对下面数据做了一部分脱敏操作)
$ git logcommit 2ff7887def36438a399c8a1df7265eDate: Fri Sep 23 21:32:32 2022 +0800 bugfix for sgcommit 794f9eea2fedcccc822326e8cef2f1fDate: Tue Sep 20 15:26:19 2022 +0800 Bugfix delete route Fix 'varp_ip' is not defined.
二级果发现最上层的正好是当前最新的提交,且第二层和线上的代码最新的提交也一致,接下来执行如下代码,将最新的提交打成patch文件。
git format-patch + commit ID
执行效果如下
$ git format-patch 794f9eea2fedcccc822326e8cef2f1f0001-bugfix-for-sg.patch
这里需要注意的是,打patch文件使用的这个format-patch指令是开区间,即如果你想要只打刚刚的提交代码为patch,那么后面的commit ID一定是最新的提交之前的一个commit ID。
(7)查看patch文件
执行完(6),在当前的目录下会出现一个0001-bugfix-for-sg.patch的补丁文件。
(8)将该补丁文件上传到线上,并准备打patch
如何上传patch文件这里不再赘述,可以参考笔者这篇文章:https://blog.csdn.net/qq_21583139/article/details/119588733
当然,公司都是由运维负责的,会有其他的运维分发工具这里就不多说了。
正式打patch前,一定要先备份原项目代码,一定要备份,一定要备份!!!
将patch文件放到线上项目目录下(有git工具)查看线上的当前最新commit,确保和打patch时fromat-patch后的是同一个id。
执行以下代码,功能是检查patch文件是否和项目冲突
git apply --check 0001-bugfix-for-sg.patch
注意:这里在执行检查冲突时会有可能产生冲突的,例如该项目中已有他人对同行代码做过更改等导致线上项目代码不干净,此时需要手动介入解决冲突,解决完冲突执行
git am --continue
表示继续,此时再次执行检查冲突指令,当此时执行后,控制台没有任何输出,说明没有冲突,可以打patch,执行
git am 0001-bugfix-for-sg.patch
注意,这个步骤还是有可能报错的,例如之前曾经在这个项目中打过其他的patch,但是某种原因使得这个patch没有打成功,此时再打新的patch会提示如下报错
previous rebase directory /opt/project/.git/rebase-apply still exists but mbox given.
此时有两种解决方法:
(1)直接git am --skip
使用这个方法是有风险的,首先要确保当前的线上代码包是刚更新不久的,且每次升级都是安装包全量替换的,否则可能其他人没有打完的patch会被你跳过,因此需要确保上个失败的patch的作者的意图。当然,直接skip掉后,一定会成功打patch成功。
(2)执行git am --abort
执行这个方法,一般是在征求了上个patch的作者同意后,上一个patch是失败了,但是必须也要打入这个项目中,那么执行这个语句就会回到上一个打的这个patch的环境下,对上一次失败的patch进行处理(注意:执行这个方法可能这个旧的patch的分支很旧,和自己要打的patch的分支相差多个版本,如果可以的话,推荐方法(1))
在具体执行上述两种解决方案前,我们不妨看看上个失败的patch目录下的内容,进入这个目录下,
发现这个目录下内容如下
[root@ rebase-apply]# lltotal 68-rw-r--r-- 1 root root 1086 Sep 21 23:22 0001-rw-r--r-- 1 root root 0 Sep 21 23:22 applying-rw-r--r-- 1 root root 2 Sep 21 23:22 apply-opt-rw-r--r-- 1 root root 54 Sep 21 23:22 final-commit-rw-r--r-- 1 root root 121 Sep 21 23:22 info-rw-r--r-- 1 root root 1 Sep 21 23:22 keep-rw-r--r-- 1 root root 1 Sep 21 23:22 keepcr-rw-r--r-- 1 root root 2 Sep 21 23:22 last-rw-r--r-- 1 root root 30 Sep 21 23:22 msg-rw-r--r-- 1 root root 54 Sep 21 23:22 msg-clean-rw-r--r-- 1 root root 2 Sep 21 23:22 next-rw-r--r-- 1 root root 1 Sep 21 23:22 no_inbody_headers-rw-r--r-- 1 root root 863 Sep 21 23:22 patch 《-----------上一个失败的patch文件-rw-r--r-- 1 root root 1 Sep 21 23:22 quiet-rw-r--r-- 1 root root 1 Sep 21 23:22 scissors-rw-r--r-- 1 root root 1 Sep 21 23:22 sign-rw-r--r-- 1 root root 1 Sep 21 23:22 threeway-rw-r--r-- 1 root root 2 Sep 21 23:22 utf8
这里是可以vim patch看见上一个patch的内容的。
这里我继续执行git am --abort,注意事项见上面,结果如下
[root project]# git am --abortUnstaged changes after reset:M etc/test.conf
因为git am --abort是回退到上一个patch打错的场景,清空所有patch缓存,此时再去查看上述的缓存的patch目录已经不在了。值得注意的是,可能会回退当前项目版本,所以谨慎起见,检查一下commit 提交记录,执行git log对比,结果发现提交记录和线下的一致,只相差了最新的patch记录,此时再次执行如下
git apply --check 0001-bugfix-for-sg.patchgit am 0001-bugfix-for-sg.patch
结果如下:说明打patch成功
[root project]# git am 0001-bugfix-for-sg.patchApplying: bugfix for sg
(9)打完patch后,执行运维剩下的操作,安装,编译,重启等操作即可
4:注意事项
本文所列:
(1)使用git format-patch时,后面的commit id是个‘开区间’取值,如果想要打某两个commit间所有的patch,那就更要注意。
(2)git am --abort 和git am --skip孰优孰劣无法评估,需要依照实际情况而定
(3)打patch前一定要先备份项目,一旦使用了–abort或者–skip产生未预料的情况,及时回滚,以便后续整包替换或者部分代码文件替换这些替代方案。
(4)多个服务同时打同一个patch,会新生成同一个commit_id,如果这些服务不是同一时间打的patch,那么每次打的patch都会在当前的服务中随机hash生成一个新的commit_id,因此,在运维操作中,千万不要以新的patch的commit_id为唯一依据。