方案开发时,同一个平台的代码针对不同的客户,可能有不同的需求改动。当新项目来时,可能一些需求是以往项目做过的,简单粗暴的办法是由开发人员重复的将之前的需求改动porting到新项目上来。而这种做法基本就是纯体力活,本篇文档介绍如何利用Python脚本,根据特殊标签从项目代码里抽取出bug对应的patch,减轻抽取patch的工作量。
需求描述
新项目上一些需求老项目已经做过,需要提供一种自动化方案快速的将这些需求改动抽取成一个个patch,方便快速的将需求导入新项目。项目代码通过git管理,每一个需求对应一个bug号。
需求分析
知道了bug号,则可以通过bug号查看到git提交的记录,利用命令:
git format-patch
生成patch。但如何找出bug对应的是哪个git,又如何通过bug号查找出对应的comment id。看看命令:
git log –oneline
的输出结果:
可以结合grep命令筛选出bug对应的comment id。
至于如何找bug对应的git呢?很显然不可能手动进入git目录去执行上面的命令,有没有一种方法可以将源码下所有git的git log输出结果定向到一个文件里,这样就可以在这个文件里过滤出需要的commit了。答案是肯定的,命令repo forall 就可以办到,查看repo forall的帮助信息:
-p 参数可以输出git的目录,那么将git命令和repo forall结合起来:
repo forall -p -c ’ git log –no-merges –oneline’ > allpatch
它就会将源码下所有git的输出信息汇总到allpatch文件中。顺便提下 git log –no-merges中–no-merges参数的含义:
下图是输出的部分结果:
代码实现
根据上面的分析,脚本的大致框架应该如下:
给出简单的主要代码:
#!/usr/bin/env python
import os
import sys
'''
this script use to create patch from bugnumbers.
'''
class FilterPatch:
def printBreakLine(self):
line = "################################\n"
print line
return line
def helpInfo(self):
print "need input filterPatch file which record bugnumbers \n"
sys.exit(1)
def filterPatch(self,allPatchsFile,filterFile):
allpatchs = allPatchsFile
bugnumbers = filterFile
filterResult = open("filterPatchsResult","w")
fileLineNum = 0
for patch in open(allpatchs,'r'):
if (patch.startswith("project")):
filterResult.write(self.printBreakLine())
filterResult.write("%s" % patch)
print patch
filterResult.write(self.printBreakLine())
fileLineNum = fileLineNum + 3
else:
for bug in open(bugnumbers):
if bug.strip() != None and patch.find(bug.strip()) != -1:
filterResult.write("bug===%spatch====%s\n" % (bug,patch))
filterResult.close()
class CreatePatch:
def createPath(self):
if os.path.exists("filterPatchsResult"):
flag = 0
gitDirName = None
for git in open("filterPatchsResult"):
if (git.startswith("project")):
gitDirName = git.replace("project"," ").strip()
elif (git.startswith("patch====")):
os.chdir("/local/source_code/androidN-source-code/%s" % (gitDirName))
commitId = git.split("====")[1].split(" ")[0]
os.system("git format-patch --start-number %d -1 %s" % (flag,commitId))
curdir = os.getcwd()
os.chdir("/local/source_code/androidN-source-code")
os.system("cp --parents %s/*.patch /local/source_code/androidN-source-code/Patchs" % (gitDirName))
os.system("rm -v %s/*.patch" % (gitDirName))
flag = flag +1
else:
pass
def createAllPatchFile(path):
os.chdir(path)
os.system("repo forall -p -c 'git log --no-merges --oneline' > allpatch")
return os.getcwd() + "/allpatch"
def initArgs():
sourcePath = raw_input("Please input the absolutely path of baseline source code:\n")
if not os.path.isdir(sourcePath):
print "invalid path \n"
print "use default source code path /local/source_code/androidN-source-code\n"
sourcePath = "/local/source_code/androidN-source-code"
bugnumbersFile = raw_input("Please input filterPatch file which record bugnumbers:\n")
if not os.path.isfile(bugnumbersFile):
print "invalid filterPatch file \n"
print "use default filterPatch file bugnumber\n"
bugnumbersFile = "bugnumber"
os.system("rm -rf /local/source_code/androidN-source-code/Patchs")
os.mkdir("/local/source_code/androidN-source-code/Patchs")
os.system("sort -u %s > uniqBugnumber" % (bugnumbersFile))
bugnumbersFile = "uniqBugnumber"
return (sourcePath,bugnumbersFile)
if __name__ == "__main__":
try:
args = initArgs()
allPatchsFile = createAllPatchFile(args[0])
filterPatch = FilterPatch()
filterPatch.filterPatch(allPatchsFile,args[1])
createPatchs = CreatePatch()
createPatchs.createPath()
except KeyboardInterrupt:
print
except EOFError:
print
过滤出来文件内容显示如下:
脚本里已经加入了patch汇总功能。在看下最终输出的patch结构。
可以看到patch已经按照git目录自动汇总在了一起。后续就可以用这些patch去新项目上打patch了。
在添加一个功能,按照bug号来归类patch。
#!/usr/bin/env python
import os
import sys
class GroupPatch:
def group(self,path):
os.chdir(path)
os.system('rm -rf GroupOut')
os.mkdir('GroupOut')
for bug in open("uniqBugnumber"):
bug = bug.strip()
os.system("grep 'number:%s' . -nsr|tee temp" % (bug))
for patch in open("temp"):
gitName = patch.split()[0].split(":")[0]
if not os.path.isdir('GroupOut/%s' % (bug)):
os.system('mkdir -p GroupOut/%s' % (bug))
os.system('cp --parents %s GroupOut/%s' % (gitName,bug))
os.remove('temp')
def initArgs():
PatchsFolder = raw_input("please input path of patchs folder")
if not os.path.isdir(PatchsFolder):
PatchsFolder = "/local/source_code/androidN-source-code/Patchs"
return PatchsFolder
if __name__ == "__main__":
try:
args = initArgs()
group = GroupPatch()
group.group(args)
except KeyboardInterrupt:
print
except EOFError:
print
其输出结果如下:
总结
如果一个bug分别在多个git提交了patch,这种情况脚本能正常抽取出来patch吗?
因为repo forall这一步导出的是所有bug,只要提交记录里有bug号,并且该bug号在需要抽取的bug list里,那么可以正常抽取。在实际运用该脚本时,只要老项目上patch是严格按照功能提交的,并且每个功能都有唯一的特殊标签,就可以用这个标签来筛选patch。按照bug号归类patch只是方便查看一个功能的实现,真正往新项目导入这些patch时,不能按照bug号打patch,因为bug之间是有依赖关系的,如果bug 111依赖bug 0000,那么在打patch时就必须先打bug 000的patch,否则bug111的patch就会打入失败。
- 重复性的工作,拿python写脚本是非常高效的。在没有这个脚本之前,新项目上老需求的导入是分发给各个开发人员完成的。几行简单的命令行组合成的脚本就可以将多人完成的任务在不到一分钟内完成近乎一半(剩下合入patch的工作),多了解点脚本知识还是很有必要的。
- 脚本涉及到的命令汇总.
- repo forall -p -c ‘git log –no-merges’ 对源码下每一个git执行-c 参数里的命令
- sort -u 去重并排序bug list
- git format-patch –start-number -1 对commit输出对应的patch文件,并且patch头以int的值开始