一次git事故

在开发过程中,我们的版本管理方式是,一版本一分支。之所以这样,是因为随着开发的进行,版本随时会分叉(这里分叉是指,两个版本都需要后续开发,但是对同一模块的功能需要不一样的微调)。

随着分叉之后的持续开发,就会导致多个分支之间差别越来越大。因此,如果所有版本都添加同样功能或修改同一个Bug时,是不可以采用git merge的方式来进行的。因为很难找到他相同的父节点。这时patch会是一个很好的工具。

而这次事故正是git patch功能使用不当引起的,下面来模拟出一个完整的事故现场。

在最开始我们有一段原始代码如下,可以明显看到,在第18行代码中,将dst错打成了src。

void foo(std::vector<int> &src, std::vector<int> &dst, int id)
{
	for (auto iter = src.begin(); iter != src.end(); )
	{
		if (*iter == id)
		{
			iter = src.erase(iter);
		}
		else
		{
			++iter;
		}
	}
	for (auto iter = dst.begin(); iter != dst.end(); )
	{
		if (*iter == id)
		{
			iter = src.erase(iter);
		}
		else
		{
			++iter;
		}
	}
	dst.push_back(id);
	return ;
}

以此次commit为节点,分切出分支ver1和ver2。

然后在ver1分支上修改上述错误并使用git commit提交,由于也许有很多并行版本都有这个问题,一个一个去改很消耗时间还容易错。因此可以使用`git formatch-patch HEAD~1`来制作一个patch文件,我们得到的patch文件内容如下:

From 3c3a434d131c8cea38eb77b7b0f4681b78ae4172 Mon Sep 17 00:00:00 2001
From: findstr <findstr@sina.com>
Date: Sun, 4 Mar 2018 15:43:43 +0800
Subject: [PATCH] bugfix

---
 a.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/a.c b/a.c
index 241c07f..1157ab4 100644
--- a/a.c
+++ b/a.c
@@ -15,7 +15,7 @@ void foo(std::vector<int> &src, std::vector<int> &dst, int id)
 	{
 		if (*iter == id)
 		{
-			iter = src.erase(iter);
+			iter = dst.erase(iter);
 		}
 		else
 		{
--
2.4.6

切到ver2分支后,先手动将18行修改提交一次,再使用`git am 0001-bugfix.patch`来重复修复这个Bug。

预期中,执行完git am之后,只有两种情况。一是这个文件完全没有变化,二是会出现conflict。

然而,事实并没有想象中的那么美好,执行完git am之后,第7行的src被改成了dst。

以上基本就是整个事故的全部还原过程。

这种事故很难发现,一旦发现却很容易就知道原因。

分析一下patch文件,就会立即发现,整个patch行为是靠以下7行代码来定位的。

 	{
 		if (*iter == id)
 		{
-			iter = src.erase(iter);
+			iter = dst.erase(iter);
 		}
 		else
 		{

恰巧,由于在git am之前,我手动修改了这一行,导致在执行git am时,不可能会匹配到这一块出错的代码。更巧的是,git可以使用个patch中的代码块匹配到第4行的代码,即然匹配成功了,git自然就会将修改应用。

从这个事故中,可以得到几点启示。

1. patch并不是100%可靠的,执行之后最好查看一下结果
2. 大扩号另起一行,对版本管理工具不友好:D
3. DRY原则 不但对人适用,对版本管理工具同样适用

ps. 如果实在没有办法,就尽量扩大git diff输出的代码块大小,`git config –global diff.context 30`即可把代码块增加致30行



发表评论