397 lines
16 KiB
Python
397 lines
16 KiB
Python
|
#!/usr/bin/env python
|
|||
|
# -*- coding: utf-8 -*-
|
|||
|
# Power by lisidong v1.0 20171102
|
|||
|
|
|||
|
import os, sys, getopt
|
|||
|
import shutil
|
|||
|
import time
|
|||
|
import fileinput
|
|||
|
import zipfile
|
|||
|
|
|||
|
class Config:
|
|||
|
TYPE_DIFF = 1
|
|||
|
TYPE_DIFF_WITH_NEW_FILE = 0
|
|||
|
TYPE_CACHE = 2
|
|||
|
TYPE_HISTORY = 3
|
|||
|
|
|||
|
#可手动修改如下参数,执行时可不带参数
|
|||
|
P_TYPE = TYPE_DIFF
|
|||
|
P_GITS = []
|
|||
|
P_HISTORY_DIFF_BETWEEN = []
|
|||
|
P_NAME = ""
|
|||
|
P_MESSAGE = ""
|
|||
|
P_PLATFORM_VERSION = ""
|
|||
|
|
|||
|
BASE_DIR = os.path.split(os.path.realpath(__file__))[0] + "/../"
|
|||
|
P_OUT_ROOT_DIR = BASE_DIR + "AUTO_CREATE_PATCH/"
|
|||
|
P_OUT_PATCH_DIR = P_OUT_ROOT_DIR + time.strftime('%Y_%m_%d', time.localtime(time.time())) + "/" + P_NAME + time.strftime('%Y_%m_%d', time.localtime(time.time())) + "/"
|
|||
|
P_OUT_README = P_OUT_PATCH_DIR + "README.txt"
|
|||
|
P_OUT_SOURCE_FOLDER = P_OUT_PATCH_DIR + "source/"
|
|||
|
P_OUT_DIFF_FOLDER = P_OUT_PATCH_DIR + "diff/"
|
|||
|
P_OUT_DIFF_PATCH_NAME = P_NAME + ".patch"
|
|||
|
P_TEMP_FILE = P_OUT_ROOT_DIR + "temp.patch"
|
|||
|
P_TEMP_DIR = P_OUT_ROOT_DIR + "temp/"
|
|||
|
|
|||
|
@staticmethod
|
|||
|
def setname(name):
|
|||
|
Config.P_NAME = name;
|
|||
|
Config.P_OUT_PATCH_DIR = Config.P_OUT_ROOT_DIR + time.strftime('%Y_%m_%d',
|
|||
|
time.localtime(time.time())) + "/" + name + time.strftime(
|
|||
|
'%Y_%m_%d', time.localtime(time.time())) + "/"
|
|||
|
Config.P_OUT_README = Config.P_OUT_PATCH_DIR + "README.txt"
|
|||
|
Config.P_OUT_SOURCE_FOLDER = Config.P_OUT_PATCH_DIR + "source/"
|
|||
|
Config.P_OUT_DIFF_FOLDER = Config.P_OUT_PATCH_DIR + "diff/"
|
|||
|
Config.P_OUT_DIFF_PATCH_NAME = Config.P_NAME + ".patch"
|
|||
|
|
|||
|
class FileState:
|
|||
|
name = ""
|
|||
|
hashfrom = ""
|
|||
|
hashto = ""
|
|||
|
cachestatus = ""
|
|||
|
diffstatus = ""
|
|||
|
|
|||
|
def __init__(self, name, hashfrom, hashto, cachestatus, diffstatus):
|
|||
|
self.name = name
|
|||
|
self.hashfrom = hashfrom
|
|||
|
self.hashto = hashto
|
|||
|
self.cachestatus = cachestatus
|
|||
|
self.diffstatus = diffstatus
|
|||
|
|
|||
|
def isdir(self):
|
|||
|
return os.path.isdir(self.name)
|
|||
|
|
|||
|
def istypecache(self):
|
|||
|
return self.cachestatus not in [' ', '?', '']
|
|||
|
|
|||
|
def istypediff(self):
|
|||
|
return self.diffstatus not in ['', ' ']
|
|||
|
|
|||
|
def dump(self):
|
|||
|
return self.cachestatus + self.diffstatus + " " + self.name
|
|||
|
|
|||
|
def dumpall(self):
|
|||
|
return "name=" + self.name + " hashfrom=" + self.hashfrom + " self.hashto=" + self.hashto + \
|
|||
|
" cache=" + self.cachestatus + " diff=" + self.diffstatus
|
|||
|
|
|||
|
allgits = []
|
|||
|
gitslist = []
|
|||
|
gitsdic = {}
|
|||
|
|
|||
|
|
|||
|
def parseConfig():
|
|||
|
try:
|
|||
|
shortargs = 'd:D:c:p:b:n:m:v:h'
|
|||
|
longargs = ['diff=', 'DIFF=', 'cache=', 'patch=', 'between=', 'name=', 'message=', 'version=', 'help']
|
|||
|
opts,args= getopt.getopt( sys.argv[1:], shortargs, longargs)
|
|||
|
print 'opts=',opts
|
|||
|
print 'args=',args
|
|||
|
|
|||
|
for opt,arg in opts:
|
|||
|
#print 'prints',opt,arg
|
|||
|
if opt in ('-d','--diff'):
|
|||
|
Config.P_TYPE = Config.TYPE_DIFF
|
|||
|
Config.P_GITS = arg.split(";")
|
|||
|
if opt in ('-D', '--DIFF'):
|
|||
|
Config.P_TYPE = Config.TYPE_DIFF_WITH_NEW_FILE
|
|||
|
Config.P_GITS = arg.split(";")
|
|||
|
elif opt in ('-c','--cache'):
|
|||
|
Config.P_TYPE = Config.TYPE_CACHE
|
|||
|
Config.P_GITS = arg.split(";")
|
|||
|
elif opt in ('-p', '--patch'):
|
|||
|
Config.P_TYPE = Config.TYPE_HISTORY
|
|||
|
Config.P_GITS = arg.split(";")
|
|||
|
elif opt in ('-b', '--between'):
|
|||
|
Config.P_HISTORY_DIFF_BETWEEN = arg.split(";")
|
|||
|
elif opt in ('-n', '--name'):
|
|||
|
Config.setname(arg)
|
|||
|
elif opt in ('-m', '--message'):
|
|||
|
Config.P_MESSAGE = arg
|
|||
|
elif opt in ('-v', '--version'):
|
|||
|
Config.P_PLATFORM_VERSION = arg
|
|||
|
elif opt in ('-h', '--help'):
|
|||
|
usage()
|
|||
|
sys.exit(1)
|
|||
|
|
|||
|
except getopt.GetoptError:
|
|||
|
print 'getopt error!'
|
|||
|
usage()
|
|||
|
sys.exit(1)
|
|||
|
|
|||
|
def init():
|
|||
|
global gitslist,allgits
|
|||
|
if os.path.exists(Config.P_OUT_PATCH_DIR):
|
|||
|
shutil.rmtree(Config.P_OUT_PATCH_DIR)
|
|||
|
if os.path.exists(Config.P_TEMP_DIR):
|
|||
|
shutil.rmtree(Config.P_TEMP_DIR)
|
|||
|
makefiledirvalid(Config.P_TEMP_DIR)
|
|||
|
|
|||
|
os.chdir(Config.BASE_DIR)
|
|||
|
for git in fileinput.input(".repo/project.list"):
|
|||
|
allgits.append(git.strip())
|
|||
|
|
|||
|
for git in (allgits if len(Config.P_GITS) == 0 else Config.P_GITS):
|
|||
|
gitslist.append(git[:-1] if git.endswith("/") else git)
|
|||
|
|
|||
|
def initgits(gitslist):
|
|||
|
|
|||
|
if Config.P_TYPE == Config.TYPE_HISTORY:
|
|||
|
if len(Config.P_GITS) == 0:
|
|||
|
for gitdir in gitslist:
|
|||
|
gitsdic[gitdir] = initgitshistory(gitdir, Config.P_HISTORY_DIFF_BETWEEN[0].split(":")[0], Config.P_HISTORY_DIFF_BETWEEN[0].split(":")[1])
|
|||
|
else:
|
|||
|
for gitdir, between in zip(gitslist, Config.P_HISTORY_DIFF_BETWEEN):
|
|||
|
gitsdic[gitdir] = initgitshistory(gitdir, between.split(":")[0], between.split(":")[1])
|
|||
|
else:
|
|||
|
for gitdir in gitslist:
|
|||
|
gitsdic[gitdir] = initgitsdiff(gitdir)
|
|||
|
return gitsdic
|
|||
|
|
|||
|
def initgitsdiff(gitdir):
|
|||
|
os.chdir(Config.BASE_DIR + gitdir)
|
|||
|
#print 'dir=', os.getcwd()
|
|||
|
|
|||
|
gitfiles = []
|
|||
|
if Config.P_TYPE == Config.TYPE_DIFF or Config.P_TYPE == Config.TYPE_DIFF_WITH_NEW_FILE:
|
|||
|
for line in os.popen("git diff --raw").readlines():
|
|||
|
filestate = FileState(line.split()[5].strip(), line.split()[2].strip()[:-3], line.split()[3].strip()[:-3], "", line.split()[4].strip())
|
|||
|
gitfiles.append(filestate)
|
|||
|
|
|||
|
if Config.P_TYPE == Config.TYPE_DIFF_WITH_NEW_FILE:
|
|||
|
for line in os.popen("git status -s").readlines():
|
|||
|
if line.split()[0].strip() == "??":
|
|||
|
filestate = FileState(line.split()[1].strip(), "", "", "?", "?")
|
|||
|
gitfiles.append(filestate)
|
|||
|
|
|||
|
elif Config.P_TYPE == Config.TYPE_CACHE:
|
|||
|
for line in os.popen("git diff --cached --raw").readlines():
|
|||
|
filestate = FileState(line.split()[5].strip(), line.split()[2].strip()[:-3], line.split()[3].strip()[:-3], line.split()[4].strip(), "")
|
|||
|
gitfiles.append(filestate)
|
|||
|
|
|||
|
return gitfiles
|
|||
|
|
|||
|
def initgitshistory(gitdir, difffrom, diffto):
|
|||
|
os.chdir(Config.BASE_DIR + gitdir)
|
|||
|
#print 'dir=', os.getcwd()
|
|||
|
gitfiles = []
|
|||
|
|
|||
|
if isdatetype(difffrom) and isdatetype(diffto):
|
|||
|
os.system("git diff --after=" + difffrom + " --before=" + diffto + " ")
|
|||
|
list = os.popen("git log --format=\"%H\" --after=\"" + difffrom + "\" --before=\"" + diffto + "\"").readlines()
|
|||
|
difffrom = list(0)
|
|||
|
diffto = list(list.count()-1)
|
|||
|
|
|||
|
for line in os.popen("git diff --raw "+ difffrom + " " + diffto):
|
|||
|
filestate = FileState(line.split()[5].strip(), line.split()[2].strip()[:-3], line.split()[3].strip()[:-3], "",
|
|||
|
line.split()[4].strip())
|
|||
|
gitfiles.append(filestate)
|
|||
|
return gitfiles
|
|||
|
|
|||
|
def isdatetype(datestr):
|
|||
|
try:
|
|||
|
if ":" in datestr:
|
|||
|
time.strptime(datestr, "%Y-%m-%d %H:%M:%S")
|
|||
|
else:
|
|||
|
time.strptime(datestr, "%Y-%m-%d")
|
|||
|
return True
|
|||
|
except:
|
|||
|
return False
|
|||
|
|
|||
|
|
|||
|
def domakepatch(gitsdic):
|
|||
|
makefiledirvalid(Config.P_TEMP_FILE)
|
|||
|
if Config.P_TYPE == Config.TYPE_DIFF or Config.P_TYPE == Config.TYPE_DIFF_WITH_NEW_FILE:
|
|||
|
for gitdir, gitfiles in gitsdic.iteritems():
|
|||
|
if len(gitfiles) == 0:
|
|||
|
continue
|
|||
|
os.chdir(Config.BASE_DIR + gitdir)
|
|||
|
os.system("git diff --binary > " + Config.P_TEMP_FILE)
|
|||
|
#print gitdir
|
|||
|
for file in gitfiles:
|
|||
|
#print file.name + " " + file.diffstatus
|
|||
|
|
|||
|
if file.diffstatus in (['M', 'A', '?'] if Config.P_TYPE == Config.TYPE_DIFF_WITH_NEW_FILE else ['M', 'A']) :
|
|||
|
assert copyfile(Config.BASE_DIR + gitdir + "/" + file.name, Config.P_OUT_SOURCE_FOLDER + gitdir + "/" + file.name), "copy fail"
|
|||
|
|
|||
|
if (file.diffstatus in ['?']):
|
|||
|
if os.path.isdir(file.name):
|
|||
|
os.system(
|
|||
|
"git diff --no-index " + Config.P_TEMP_DIR + " " + file.name + " >> " + Config.P_TEMP_FILE)
|
|||
|
else:
|
|||
|
os.system(
|
|||
|
"git diff --no-index /dev/null " + file.name + " >> " + Config.P_TEMP_FILE)
|
|||
|
|
|||
|
assert copyfile(Config.P_TEMP_FILE, Config.P_OUT_DIFF_FOLDER + gitdir + "/" + Config.P_OUT_DIFF_PATCH_NAME, True), "copy fail"
|
|||
|
|
|||
|
if Config.P_TYPE == Config.TYPE_CACHE:
|
|||
|
for gitdir, gitfiles in gitsdic.iteritems():
|
|||
|
if len(gitfiles) == 0:
|
|||
|
continue
|
|||
|
os.chdir(Config.BASE_DIR + gitdir)
|
|||
|
os.system("git diff --cached --binary > " + Config.P_TEMP_FILE)
|
|||
|
assert copyfile(Config.P_TEMP_FILE, Config.P_OUT_DIFF_FOLDER + gitdir + "/" + Config.P_OUT_DIFF_PATCH_NAME, True), "copy fail"
|
|||
|
|
|||
|
for file in gitfiles:
|
|||
|
if file.cachestatus in ['M','A']:
|
|||
|
#print file.dump() + " "+ file.hashto
|
|||
|
os.system("git show " + file.hashto + " > " + Config.P_TEMP_FILE)
|
|||
|
assert copyfile(Config.P_TEMP_FILE, Config.P_OUT_SOURCE_FOLDER + gitdir + "/" + file.name, True), "copy fail"
|
|||
|
|
|||
|
if Config.P_TYPE == Config.TYPE_HISTORY:
|
|||
|
for gitdir, gitfiles in gitsdic.iteritems():
|
|||
|
if len(gitfiles) == 0:
|
|||
|
continue
|
|||
|
os.chdir(Config.BASE_DIR + gitdir)
|
|||
|
for file in gitfiles:
|
|||
|
|
|||
|
#print "git show --binary " + file.hashto
|
|||
|
os.system("git show --binary " + file.hashto + " > " + Config.P_TEMP_FILE + "_")
|
|||
|
assert copyfile(Config.P_TEMP_FILE + "_", Config.P_OUT_SOURCE_FOLDER + gitdir + "/" + file.name, True), "copy fail"
|
|||
|
#print "git diff --binary " + file.hashfrom + " " + file.hashto + " >> " + Config.P_TEMP_FILE
|
|||
|
os.system("git diff --binary " + file.hashfrom + " " + file.hashto + " >> " + Config.P_TEMP_FILE)
|
|||
|
assert copyfile(Config.P_TEMP_FILE, Config.P_OUT_DIFF_FOLDER + gitdir + "/" + Config.P_OUT_DIFF_PATCH_NAME, True), "copy fail"
|
|||
|
|
|||
|
def packpatch():
|
|||
|
os.chdir(Config.P_OUT_PATCH_DIR + "../")
|
|||
|
patchname = Config.P_NAME + time.strftime('%Y_%m_%d', time.localtime(time.time()))
|
|||
|
os.system("tar -czf " + patchname + ".tar.gz " + patchname)
|
|||
|
print "OUT:" + os.path.abspath(patchname)
|
|||
|
|
|||
|
def makefiledirvalid(dir):
|
|||
|
desdir = ""
|
|||
|
if os.path.isdir(dir):
|
|||
|
desdir = dir
|
|||
|
else:
|
|||
|
desdir = os.path.dirname(dir)
|
|||
|
if not os.path.exists(desdir):
|
|||
|
os.makedirs(desdir, mode=0o777)
|
|||
|
return desdir
|
|||
|
|
|||
|
def copyfile(src, des, deletesrc=False):
|
|||
|
try:
|
|||
|
desdir = makefiledirvalid(des)
|
|||
|
if os.path.isdir(src) and os.path.isdir(des):
|
|||
|
os.rmdir(desdir)
|
|||
|
shutil.copytree(src, desdir)
|
|||
|
else:
|
|||
|
shutil.copy(src, des)
|
|||
|
|
|||
|
if deletesrc:
|
|||
|
os.remove(src)
|
|||
|
#os.removedirs(src)
|
|||
|
except Exception, e:
|
|||
|
print "error---:" + str(e)
|
|||
|
return False
|
|||
|
else:
|
|||
|
return True
|
|||
|
|
|||
|
def check():
|
|||
|
assert set(gitslist).issubset(set(allgits)), '请检查.repo/project.list中是否包含有如下仓库名字:%s'%gitslist
|
|||
|
if Config.P_TYPE == Config.TYPE_HISTORY:
|
|||
|
if (len(gitslist) == 0):
|
|||
|
assert len(Config.P_HISTORY_DIFF_BETWEEN) == 1, "当前为HISTORY(-p)模式,请检查-p -b 对应的数组大小是否一致,仓库以’;‘分割"
|
|||
|
else:
|
|||
|
assert len(gitslist) == len(Config.P_HISTORY_DIFF_BETWEEN), "当前为HISTORY(-p)模式,请检查-p -b 对应的数组大小是否一致,仓库以’;‘分割"
|
|||
|
|
|||
|
assert Config.P_NAME != None and Config.P_NAME != "", "-n name:名字不能为空"
|
|||
|
assert Config.P_MESSAGE != None and Config.P_MESSAGE != "", "-m message:描述不能为空"
|
|||
|
|
|||
|
def usage():
|
|||
|
usage = '''
|
|||
|
用法: make_patch [选项...]
|
|||
|
生成标准格式的PATCH文件,可以从DIFF、CACHE、HISTORY中抽取特定格式,提高PATCH的可追溯行和可读性。
|
|||
|
|
|||
|
Examples:
|
|||
|
|
|||
|
make_patch -d "android/build/;lichee/linux-3.10" -m message -n name -v "H6 v1.0" #上述两个仓库的diff抽取成补丁
|
|||
|
make_patch -D "android/build/;lichee/linux-3.10" -m message -n name -v "H6 v1.0" #上述两个仓库的diff(包括未跟踪的文件)抽取成补丁
|
|||
|
make_patch -c "android/build/;lichee/linux-3.10" -m message -n name -v "H6 v1.0" #上述两个仓库的暂存区(cached中)抽取成补丁
|
|||
|
make_patch -p "android/build/;lichee/linux-3.10" -b "HEAD^:HEAD;HEAD^^:HEAD" -m message -n name -v "H6 v1.0" #上述两个仓库的历史提交中抽取补丁
|
|||
|
make_patch -p "android/build/;lichee/linux-3.10" -b "2017-01-01:2017-02-01;hash1:hash2" -m message -n name -v "H6 v1.0" #上述两个仓库的历史提交中抽取补丁
|
|||
|
可修改make_patch.py中的必要属性(Config中:P_TYPE,P_GITS,P_HISTORY_DIFF_BETWEEN,P_NAME,P_MESSAGE,P_PLATFORM_VERSION),而后直接执行make_patch.py即可(参数赋值)(不推荐)
|
|||
|
|
|||
|
主操作模式:
|
|||
|
|
|||
|
-d, --diff 类型1:创建各仓库的diff差异补丁(git diff ),各仓库以';'分割。如-d "android/frameworks/base;android/build/"
|
|||
|
-D, --DIff 类型2:创建各仓库的diff差异(同时包括未追踪的新文体)补丁,各仓库以';'分割。如-D "android/frameworks/base;android/build/"
|
|||
|
-c, --cache 类型3:创建各仓库的已加入到暂存区的差异补丁(git diff --cache),各仓库以';'分割。如-D "android/frameworks/base;android/build/"
|
|||
|
|
|||
|
-p, --patch 类型4:创建提取各仓库的历史提交内容,各仓库以';'分割。如-D "android/frameworks/base;android/build/",需要-b参数描述抽取点。
|
|||
|
-b, --between 配合-p使用,描述对应仓库的抽取点(hash值,时间等),以':'分割两个抽取点,以';'分割各仓库。如-b "HEAD^:HEAD;2017-01-01:2017-01-02;hash1:hash2"
|
|||
|
|
|||
|
-m, --message 此补丁的描述信息,必选。
|
|||
|
-n, --name 此补丁的名字,必选。
|
|||
|
-v, --version 此补丁的适用(或当前发布时)的平台与版本信息等,必选
|
|||
|
|
|||
|
-h, --help 打印此帮助信息
|
|||
|
|
|||
|
|
|||
|
TIPS:
|
|||
|
1、补丁抽取后必须检查是否正确,同时注意log打印。
|
|||
|
2、当仓库被修改得相对复杂(比如含有未跟踪的文件、未暂存的工作区、暂存的文件等),建议将需要抽取的补丁add到暂存区(而后用-c模式),有利于更清晰明确抽取目标diff。
|
|||
|
3、当使用-p模式时,针对-b参数较为严格(对参数要求较高,如当仓库只有一个提交,HEAD^记录会找不到),需要谨慎使用!
|
|||
|
|
|||
|
'''
|
|||
|
print usage
|
|||
|
|
|||
|
def dumpinfo(gitsdic):
|
|||
|
for (key,values) in gitsdic.items():
|
|||
|
print "key=[" + key + "]"
|
|||
|
if (len(values) == 0):
|
|||
|
print '\033[1;31;40m'
|
|||
|
print "Empty,maybe something wrong with this git "
|
|||
|
print '\033[0m'
|
|||
|
for value in values:
|
|||
|
print " " + value.dump()
|
|||
|
|
|||
|
def patchreadme(gitsdic):
|
|||
|
readme = '''
|
|||
|
----------------------------------------------------------------------------------------------------
|
|||
|
补丁名称:''' + Config.P_NAME + '''
|
|||
|
适用版本:''' + Config.P_PLATFORM_VERSION + '''
|
|||
|
发布日期:''' + time.strftime('%Y-%m-%d', time.localtime(time.time())) + '''
|
|||
|
|
|||
|
文件结构:
|
|||
|
├── source 所有涉及修改的源码文件
|
|||
|
├── diff 所有涉及的修改
|
|||
|
└── README.txt 本说明文件
|
|||
|
|
|||
|
使用方法:
|
|||
|
在对应的diff_patch下的各个目录使用git apply ***.patch即可,如发生冲突,请使用source目录手动移植。注意查阅log信息,存在删除文件时较容易忽略。
|
|||
|
|
|||
|
补丁说明:''' + Config.P_MESSAGE + '''
|
|||
|
|
|||
|
----------------------------------------------------------------------------------------------------
|
|||
|
File INFORMATION
|
|||
|
|
|||
|
'''
|
|||
|
makefiledirvalid(Config.P_OUT_README)
|
|||
|
os.mknod(Config.P_OUT_README)
|
|||
|
file = open(Config.P_OUT_README, 'r+')
|
|||
|
file.write(readme)
|
|||
|
|
|||
|
for (key,values) in gitsdic.items():
|
|||
|
#print "key=" + key + " value=" + values
|
|||
|
file.write(key + "\n")
|
|||
|
for value in values:
|
|||
|
file.write(" " + value.dump() + "\n")
|
|||
|
file.write("\n")
|
|||
|
file.write('''
|
|||
|
----------------------------------------------------------------------------------------------------
|
|||
|
File Tree List
|
|||
|
|
|||
|
''')
|
|||
|
file.flush()
|
|||
|
file.close()
|
|||
|
os.chdir(Config.P_OUT_PATCH_DIR)
|
|||
|
os.system("tree -a >> " + Config.P_OUT_README)
|
|||
|
|
|||
|
parseConfig()
|
|||
|
init()
|
|||
|
check()
|
|||
|
|
|||
|
initgits(gitslist)
|
|||
|
dumpinfo(gitsdic)
|
|||
|
domakepatch(gitsdic)
|
|||
|
patchreadme(gitsdic)
|
|||
|
packpatch()
|