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