Git 简单使用:Git Patch

用了这么久的Git,一直没有用心学过,碰到过很多棘手的问题,一旦力所不能及,首先想到的就是用SourceTree替自己解决,最后问题虽然解决了,但自己还是没学会怎么用Git,于是决定趁着这个机会简单梳理一下自己经常会用到的几个命令。

从 add 到 commit

虽然一直在用着Git这个东西,也一直混迹于GitHub,但是对Git用法的掌握一直停留在表面。刚开始接触Git的时候,只知道Git有工作区,缓存区以及历史区,那时候只知道添加到缓存区:git add和添加到历史区:git commit -m,后来用的多了,又学到了把这两个操作合并起来的:git commit -a

merge VS rebase

只在本地操作肯定是不行的,慢慢地接触到了远端,然后就学会了git pushgit pull,但这些操作都是只在master这一个分支上,后来有一天老大说:以后我们再开发新功能或者修复BUG的时候需要新建一个分支,然后提Merge Request,然后自己又去百度怎么创建新分支:git checkout -b,那时候感觉切换分支是一个特别神奇的事情,只需要一行命令,瞬间整个工作区的文件都不一样了。接触到分支以后,自然而然就遇到了分支合并的问题,然后就面临了

1
git merge develop

1
git rebase master develop

的抉择,从字面上就可以分析到,merge是将目标分支上和当前分支上对比没有的commit信息重复一遍并且记录下一个新的commit向后人解释:我在此庄严宣布,我又和develop合并了,现在我们的状态是统一的。

rebase这个如果纯直译的话,应该是:变基,通俗来讲就是:develop由于和master分开太久,在分开的这段时间里,master可能和别人有过一些交流导致它前进了几步,这时候develop为了也保持与时俱进,决定变基!最终的效果就是develop上有了master上所有的commit信息,而develop上的那些commit由于rebase,会丢掉时间信息,其他信息还是会保留,这个其实是通过patch 实现的,在下面我们会详细解释patch的操作。rebase操作的好处就是没有多余的commit,而且整体历史前进是呈线性的,就好比一棵树在树顶长出一个小树枝,这时候这棵树还是只有一条线,虽然可能因为这个树枝长而显得有点弯,后来慢慢地在小树枝生长的同时,树冠也在长,这时候就形成了枝杈,因为有了枝杈的存在,这棵树不再是纯线性,然后这时候如果把这个树枝砍下来,然后重新接在当前树冠的位置,这时候这个树枝就莫名前进了一段距离,保证了这棵树还是只有一条线,这就是rebase

修订历史:reset & –amend

如果就一直这样平平稳稳的前行,莫非不是一件好事,但是这显然是不可能的,直到有一天,我提交了一个错误的commit,急忙去搜索怎么弥补我这样的错误,然后就学到了两种不同的解决方式:

1
git reset --soft HEAD^

1
git commit --amend -m "The new commit message"

其中,reset是用于修改历史版本,并且跟上--soft是会将回退的commit中所作的一些修改放在缓存区,默认的--mixed会将那些修改放回工作区,不在缓存区,而--hard会将工作区以及缓存区和历史区都回退到特定的commit状态,会导致修改丢失,所有用这个的时候需要考虑清楚。
commit --amend没有reset的功能那么强大,它只能修改你上一个commit,比如你还有一些修改也需要提交到上一个commit或者commit的信息有误,都可以通过这个命令进行修改。

进入正题:patch

上面其实都是扯闲篇,真正今天要说的其实是另外一个东西:patch,因为之前没有遇到过与之相关的需求,所有就素未谋面,今天终于碰到了相应的问题,于是急忙去补了一下课,在此就随便写一下。

patch的作用

patch直译过来就是补丁的意思,如果把我们的工作比作是在堆一个乐高机器人,我们的每一个commit都是一块小积木,我们每完成一个commit,这个机器人就更完善一点,而patch就像是有我们已经有一个完成度比较高的机器人,然后另外有一个类似的乐高机器人可能也需要我们当前这个机器人的某个组件,它们不想再做重复性的工作,于是决定从我们这里copy一份,通过的手段就是我们根据现有东西生成一个补丁给它,然后它把这个补丁打上,就有了一个和我们一模一样的模块,我们来简单看一下这个过程都会涉及到哪些命令。

生成patch:diff || format-patch

首先是第一步:生成一个补丁文件,这个过程,又有两种不同的方法来实现,第一种是:

1
2
3
git diff > patch
git diff --cached > patch
git diff branchname --cached > patch

运行其中任意一条命令都会在当前目录下生成一个patch文件,然后就可以拿着这个patch文件去应用了,这几个命令的不同在于--cached,加上这个是对比缓存区和历史区,不加这个是对比工作区和缓存区。

另外一种生成patch文件的方式是:

1
2
3
git format-patch 0f500e4...d37885d
git format-patch -1 -o simple_fix.patch
git format-patch 0f500e4

通过format-patch可以生成某个commit和另一个commit之间若干个commit合成的patch文件(包含收尾两个commit),或者最近若干个commit,或者自某一个commit以来所有的commit形成的patch文件(不包含指定的commit),还可以用-o filename--stdout > filename 定义patch文件输出路径。

这两种生成patch文件的不同在于:前者生成的patch文件是兼容性更强,不局限于git;而format-patch生成的patch文件只能在git中使用,但是后者生成的patch会保留patch开发者的信息,这对版本管理来说是很有帮助的,所有我们最常用的还是format-patch

应用patch

拿到patch文件之后,就是应用补丁,在直接应用补丁之前,我们还可以进行一些额外的操作,比如查看patch文件具体有哪些修改或者检查补丁文件是否有冲突,会涉及到下面两个命令:

1
2
git apply --stat fix_empty_poster.patch
git apply --check fix_empty_poster.patch

如果验证了patch文件没有问题,那么就可以应用了

1
git am -s > fix_empty_poster.patch

The End:挖坑cherry-pick

这样整个patch的过程就结束了,主要就是实现了commit的复用,说到commit的复用,其实还有一个cherry-pick,下次有机会可以实践一下cherry-pick的用法。

参考链接