|
Chris L Mason |
5c5578 |
#!/usr/bin/env python
|
|
Chris L Mason |
5c5578 |
#
|
|
Chris L Mason |
5c5578 |
# prepares a report on the patches in a series file that do not yet have
|
|
Chris L Mason |
5c5578 |
# mainline tags.
|
|
Chris L Mason |
5c5578 |
#
|
|
Chris L Mason |
5c5578 |
# common usage: patch-report -s summary -r report < series
|
|
Chris L Mason |
5c5578 |
#
|
|
Chris L Mason |
5c5578 |
# -s specifies a file where a summary of patches per user is printed
|
|
Chris L Mason |
5c5578 |
# -r is a report with details about each patch
|
|
Chris L Mason |
5c5578 |
# -t does guards style tagging in the report of patches that can be reversed
|
|
Chris L Mason |
5c5578 |
# -f tries to reverse with rej. This is very slow.
|
|
Chris L Mason |
5c5578 |
# -u prints only details on patches corresponding to a specific user.
|
|
Chris L Mason |
5c5578 |
# this is everything before the @ sign in the email.
|
|
Chris L Mason |
5c5578 |
#
|
|
Chris L Mason |
5c5578 |
# This can try to reverse all the untagged patches, use -p source-dir to
|
|
Chris L Mason |
5c5578 |
# specify the directory where it should try to reverse things.
|
|
Chris L Mason |
5c5578 |
|
|
Chris L Mason |
5c5578 |
#
|
|
Chris L Mason |
5c5578 |
#
|
|
Chris L Mason |
5c5578 |
|
|
Chris L Mason |
5c5578 |
import os, sys, select, popen2, re
|
|
Chris L Mason |
5c5578 |
from optparse import OptionParser
|
|
Chris L Mason |
5c5578 |
|
|
Chris L Mason |
5c5578 |
# counts the number of times we find a given username. We only
|
|
Chris L Mason |
5c5578 |
# consider emails from @suse and @novell, and don't include the domain
|
|
Chris L Mason |
5c5578 |
# name in the counting (so mason@suse.de and mason@suse.com are the same)
|
|
Chris L Mason |
5c5578 |
def countuser(s, users, foundusers, emailre):
|
|
Chris L Mason |
5c5578 |
found = []
|
|
Chris L Mason |
5c5578 |
for m in emailre.finditer(s):
|
|
Chris L Mason |
5c5578 |
u = m.group(1)
|
|
Chris L Mason |
5c5578 |
addr = m.group(2)
|
|
Chris L Mason |
5c5578 |
if not addr.startswith('@suse') and not addr.startswith('@novell'):
|
|
Chris L Mason |
5c5578 |
continue
|
|
Chris L Mason |
5c5578 |
if u in foundusers:
|
|
Chris L Mason |
5c5578 |
continue
|
|
Chris L Mason |
5c5578 |
foundusers[u] = 1
|
|
Chris L Mason |
5c5578 |
n = users.get(u, 0)
|
|
Chris L Mason |
5c5578 |
users[u] = n + 1
|
|
Chris L Mason |
5c5578 |
found.append(m.group(0))
|
|
Chris L Mason |
5c5578 |
return found
|
|
Chris L Mason |
5c5578 |
|
|
Chris L Mason |
5c5578 |
|
|
Chris L Mason |
5c5578 |
parser = OptionParser(usage="usage: %prog [options]")
|
|
Chris L Mason |
5c5578 |
parser.add_option("-u", "--user", help="find a specific user", default="")
|
|
Chris L Mason |
5c5578 |
parser.add_option("-r", "--report-file", help="report output file", default="")
|
|
Chris L Mason |
5c5578 |
parser.add_option("-v", "--verbose", help="verbose", action="store_true",
|
|
Chris L Mason |
5c5578 |
default=False)
|
|
Chris L Mason |
5c5578 |
parser.add_option("-t", "--tag", help="tag patches in report",
|
|
Chris L Mason |
5c5578 |
action="store_true", default=False)
|
|
Chris L Mason |
5c5578 |
parser.add_option("-f", "--rej", help="try using rej", action="store_true",
|
|
Chris L Mason |
5c5578 |
default=False)
|
|
Chris L Mason |
5c5578 |
parser.add_option("-p", "--patch-dir", help="try to reverse patches in path",
|
|
Chris L Mason |
5c5578 |
default="")
|
|
Chris L Mason |
5c5578 |
parser.add_option("-s", "--summary-file", help="summary output file",
|
|
Chris L Mason |
5c5578 |
default="")
|
|
Chris L Mason |
5c5578 |
(options, args) = parser.parse_args()
|
|
Chris L Mason |
5c5578 |
|
|
Chris L Mason |
5c5578 |
# start a two way connection with xargs patch-tag.
|
|
Chris L Mason |
5c5578 |
pipeout, pipein = popen2.popen2("xargs patch-tag -p From -p Signed-off-by -p Acked-by -p Patch-mainline -a Patch-mainline=empty -p Subject")
|
|
Chris L Mason |
5c5578 |
readers = [ sys.stdin, pipeout ]
|
|
Chris L Mason |
5c5578 |
writers = [ pipein ]
|
|
Chris L Mason |
5c5578 |
writeq = []
|
|
Chris L Mason |
5c5578 |
stdindone = False
|
|
Chris L Mason |
5c5578 |
patches = {}
|
|
Chris L Mason |
5c5578 |
|
|
Chris L Mason |
5c5578 |
# records details about all the patches
|
|
Chris L Mason |
5c5578 |
patchinfo = {}
|
|
Chris L Mason |
5c5578 |
|
|
Chris L Mason |
5c5578 |
# keeps the output in the same order as the series read over stdin
|
|
Chris L Mason |
5c5578 |
patchorder = []
|
|
Chris L Mason |
5c5578 |
# a count of the patches found for each user
|
|
Chris L Mason |
5c5578 |
users = {}
|
|
Chris L Mason |
5c5578 |
emailre = re.compile(r"([\w\.]+)(@[\w\.]+)")
|
|
Chris L Mason |
5c5578 |
|
|
Chris L Mason |
5c5578 |
goodwords = [ '2.6', 'yes', 'obsolete', 'never' ]
|
|
Chris L Mason |
5c5578 |
badwords = ['-mm', 'no', 'empty' ]
|
|
Chris L Mason |
5c5578 |
|
|
Chris L Mason |
5c5578 |
# ugly select loop to talk with patch-tag
|
|
Chris L Mason |
5c5578 |
while readers:
|
|
Chris L Mason |
5c5578 |
(r, w, x) = select.select(readers, writers, [])
|
|
Chris L Mason |
5c5578 |
for f in r:
|
|
Chris L Mason |
5c5578 |
if f == sys.stdin:
|
|
Chris L Mason |
5c5578 |
l = f.readline()
|
|
Chris L Mason |
5c5578 |
if l:
|
|
Chris L Mason |
5c5578 |
writeq.append(l)
|
|
Chris L Mason |
5c5578 |
else:
|
|
Chris L Mason |
5c5578 |
del readers[0]
|
|
Chris L Mason |
5c5578 |
stdindone = True
|
|
Chris L Mason |
5c5578 |
if len(writeq) == 0:
|
|
Chris L Mason |
5c5578 |
pipein.close()
|
|
Chris L Mason |
5c5578 |
writers = []
|
|
Chris L Mason |
5c5578 |
elif f == pipeout:
|
|
Chris L Mason |
5c5578 |
line = f.readline().rstrip()
|
|
Chris L Mason |
5c5578 |
l = line.split()
|
|
Chris L Mason |
5c5578 |
if l:
|
|
Chris L Mason |
5c5578 |
# the format is:
|
|
Chris L Mason |
5c5578 |
# file: Patch-mainline: data
|
|
Chris L Mason |
5c5578 |
# data may be empty
|
|
Chris L Mason |
5c5578 |
p = l[0].rstrip(':')
|
|
Chris L Mason |
5c5578 |
if len(l) <= 1:
|
|
Chris L Mason |
5c5578 |
continue
|
|
Chris L Mason |
5c5578 |
htype = l[1].rstrip(':')
|
|
Chris L Mason |
5c5578 |
|
|
Chris L Mason |
5c5578 |
if len(l) < 3:
|
|
Chris L Mason |
5c5578 |
t = 'empty'
|
|
Chris L Mason |
5c5578 |
else:
|
|
Chris L Mason |
5c5578 |
t = " ".join(l[2:])
|
|
Chris L Mason |
5c5578 |
patchinfo.setdefault(p, {}).setdefault(htype, []).append(t)
|
|
Chris L Mason |
5c5578 |
if htype != 'Patch-mainline':
|
|
Chris L Mason |
5c5578 |
continue
|
|
Chris L Mason |
5c5578 |
good = False
|
|
Chris L Mason |
5c5578 |
t = t.lower()
|
|
Chris L Mason |
5c5578 |
for x in goodwords:
|
|
Chris L Mason |
5c5578 |
if x in t:
|
|
Chris L Mason |
5c5578 |
good = True
|
|
Chris L Mason |
5c5578 |
break
|
|
Chris L Mason |
5c5578 |
for x in badwords:
|
|
Chris L Mason |
5c5578 |
# For example, 2.6.16-mm2 is bad
|
|
Chris L Mason |
5c5578 |
if x in t:
|
|
Chris L Mason |
5c5578 |
good = False
|
|
Chris L Mason |
5c5578 |
break
|
|
Chris L Mason |
5c5578 |
if not good:
|
|
Chris L Mason |
5c5578 |
patches[p] = t
|
|
Chris L Mason |
5c5578 |
patchorder.append(p)
|
|
Chris L Mason |
5c5578 |
else:
|
|
Chris L Mason |
5c5578 |
del readers[0]
|
|
Chris L Mason |
5c5578 |
|
|
Chris L Mason |
5c5578 |
if w and writeq:
|
|
Chris L Mason |
5c5578 |
w[0].write(writeq[0])
|
|
Chris L Mason |
5c5578 |
del writeq[0]
|
|
Chris L Mason |
5c5578 |
if stdindone and len(writeq) == 0:
|
|
Chris L Mason |
5c5578 |
pipein.close()
|
|
Chris L Mason |
5c5578 |
writers = []
|
|
Chris L Mason |
5c5578 |
|
|
Chris L Mason |
5c5578 |
if options.report_file:
|
|
Chris L Mason |
5c5578 |
try:
|
|
Chris L Mason |
5c5578 |
outf = file(options.report_file, "w")
|
|
Chris L Mason |
5c5578 |
except IOError:
|
|
Chris L Mason |
5c5578 |
sys.stderr.write("unable to open %s for writing\n" %
|
|
Chris L Mason |
5c5578 |
options.report_file);
|
|
Chris L Mason |
5c5578 |
sys.exit(1)
|
|
Chris L Mason |
5c5578 |
else:
|
|
Chris L Mason |
5c5578 |
outf = sys.stdout
|
|
Chris L Mason |
5c5578 |
|
|
Chris L Mason |
5c5578 |
# optionally try to figure out which patches we can reverse
|
|
Chris L Mason |
5c5578 |
if options.patch_dir:
|
|
Chris L Mason |
5c5578 |
for i in xrange(len(patchorder)-1, -1, -1):
|
|
Chris L Mason |
5c5578 |
p = patchorder[i]
|
|
Chris L Mason |
5c5578 |
fuzz = 0
|
|
Chris L Mason |
5c5578 |
failed = 0
|
|
Chris L Mason |
5c5578 |
files = 0
|
|
Chris L Mason |
5c5578 |
reject_files = 0
|
|
Chris L Mason |
5c5578 |
# we want to be smart about counting the failed hunks
|
|
Chris L Mason |
5c5578 |
patchf = os.popen("patch -f -p1 -d%s -R < '%s'" %
|
|
Chris L Mason |
5c5578 |
(options.patch_dir, p))
|
|
Chris L Mason |
5c5578 |
for l in patchf:
|
|
Chris L Mason |
5c5578 |
if options.verbose:
|
|
Chris L Mason |
5c5578 |
sys.stderr.write(l)
|
|
Chris L Mason |
5c5578 |
l = l.rstrip('\r\n')
|
|
Chris L Mason |
5c5578 |
if l[:14] == 'patching file ':
|
|
Chris L Mason |
5c5578 |
files += 1
|
|
Chris L Mason |
5c5578 |
elif l.find('saving rejects to file') >= 0:
|
|
Chris L Mason |
5c5578 |
reject_files += 1
|
|
Chris L Mason |
5c5578 |
elif l.find('FAILED at') >= 0:
|
|
Chris L Mason |
5c5578 |
failed += 1
|
|
Chris L Mason |
5c5578 |
elif l.find('with fuzz') >= 0:
|
|
Chris L Mason |
5c5578 |
fuzz += 1
|
|
Chris L Mason |
5c5578 |
|
|
Chris L Mason |
5c5578 |
patcherr = patchf.close()
|
|
Chris L Mason |
5c5578 |
if failed == 0 and patcherr == None:
|
|
Chris L Mason |
5c5578 |
if fuzz:
|
|
Chris L Mason |
5c5578 |
patchinfo[p]['Reverse'] = 'fuzzy'
|
|
Chris L Mason |
5c5578 |
else:
|
|
Chris L Mason |
5c5578 |
patchinfo[p]['Reverse'] = 'yes'
|
|
Chris L Mason |
5c5578 |
else:
|
|
Chris L Mason |
5c5578 |
str = "hunks failed %d fuzzy %d files %d reject files %d" % (failed,
|
|
Chris L Mason |
5c5578 |
fuzz, files, reject_files)
|
|
Chris L Mason |
5c5578 |
if options.rej:
|
|
Chris L Mason |
5c5578 |
patchpath = os.path.abspath(p)
|
|
Chris L Mason |
5c5578 |
ret = os.system("cd '%s' ; rej -R -a --dry-run -F -M -p 1 '%s'"
|
|
Chris L Mason |
5c5578 |
% (options.patch_dir, patchpath))
|
|
Chris L Mason |
5c5578 |
if ret == 0:
|
|
Chris L Mason |
5c5578 |
str = "rej resolved " + str
|
|
Chris L Mason |
5c5578 |
patchinfo[p]['Reverse'] = str
|
|
Chris L Mason |
5c5578 |
|
|
Chris L Mason |
5c5578 |
for p in patchorder:
|
|
Chris L Mason |
5c5578 |
h = patchinfo[p]
|
|
Chris L Mason |
5c5578 |
foundusers = {}
|
|
Chris L Mason |
5c5578 |
if options.user:
|
|
Chris L Mason |
5c5578 |
good = False
|
|
Chris L Mason |
5c5578 |
for x in ['Acked-by', 'Signed-off-by', 'From']:
|
|
Chris L Mason |
5c5578 |
if options.user in "".join(h.get(x, [])):
|
|
Chris L Mason |
5c5578 |
good = True
|
|
Chris L Mason |
5c5578 |
break
|
|
Chris L Mason |
5c5578 |
if not good:
|
|
Chris L Mason |
5c5578 |
continue
|
|
Chris L Mason |
5c5578 |
tag = "+nag "
|
|
Chris L Mason |
5c5578 |
# always print From and Subject. Only print acked-by or signed-off-by
|
|
Chris L Mason |
5c5578 |
# if it is a suse/novell person
|
|
Chris L Mason |
5c5578 |
if 'From' in h:
|
|
Chris L Mason |
5c5578 |
l = " ".join(h['From'])
|
|
Chris L Mason |
5c5578 |
outf.write("# From: %s\n" % l)
|
|
Chris L Mason |
5c5578 |
countuser(l, users, foundusers, emailre)
|
|
Chris L Mason |
5c5578 |
if 'Subject' in h:
|
|
Chris L Mason |
5c5578 |
outf.write("# Subject: %s\n" % " ".join(h['Subject']))
|
|
Chris L Mason |
5c5578 |
for x in ['Acked-by', 'Signed-off-by']:
|
|
Chris L Mason |
5c5578 |
if x in h:
|
|
Chris L Mason |
5c5578 |
l = " ".join(h[x])
|
|
Chris L Mason |
5c5578 |
found = countuser(l, users, foundusers, emailre)
|
|
Chris L Mason |
5c5578 |
if found:
|
|
Chris L Mason |
5c5578 |
outf.write("# %s: %s\n" % (x, " ".join(found)))
|
|
Chris L Mason |
5c5578 |
if 'Reverse' in h:
|
|
Chris L Mason |
5c5578 |
t = h['Reverse']
|
|
Chris L Mason |
5c5578 |
if t == 'yes':
|
|
Chris L Mason |
5c5578 |
tag = "+reverse "
|
|
Chris L Mason |
5c5578 |
elif t == 'fuzzy':
|
|
Chris L Mason |
5c5578 |
tag = "+reverse-fuzzy "
|
|
Chris L Mason |
5c5578 |
outf.write("# Reverse: %s\n" % h['Reverse'])
|
|
Chris L Mason |
5c5578 |
if options.tag:
|
|
Chris L Mason |
5c5578 |
p = tag + p
|
|
Chris L Mason |
5c5578 |
outf.write("%s\n\n" % (p))
|
|
Chris L Mason |
5c5578 |
|
|
Chris L Mason |
5c5578 |
if options.summary_file:
|
|
Chris L Mason |
5c5578 |
try:
|
|
Chris L Mason |
5c5578 |
outf = file(options.summary_file, "w")
|
|
Chris L Mason |
5c5578 |
except IOError:
|
|
Chris L Mason |
5c5578 |
sys.stderr.write("unable to open %s for writing\n" %
|
|
Chris L Mason |
5c5578 |
options.report_file);
|
|
Chris L Mason |
5c5578 |
sys.exit(1)
|
|
Chris L Mason |
5c5578 |
else:
|
|
Chris L Mason |
5c5578 |
outf = sys.stdout
|
|
Chris L Mason |
5c5578 |
userk = users.keys()
|
|
Chris L Mason |
5c5578 |
userk.sort()
|
|
Chris L Mason |
5c5578 |
outf.write("Total untagged patches: %d\n" % len(patchorder))
|
|
Chris L Mason |
5c5578 |
for u in userk:
|
|
Chris L Mason |
5c5578 |
if options.user and options.user not in u:
|
|
Chris L Mason |
5c5578 |
continue
|
|
Chris L Mason |
5c5578 |
outf.write("%s: %d\n" % (u, users[u]))
|
|
Chris L Mason |
5c5578 |
|