-
Notifications
You must be signed in to change notification settings - Fork 71
/
Copy pathmake_patch.py
executable file
·396 lines (328 loc) · 16.3 KB
/
make_patch.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
#!/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()