#!/usr/bin/env python # vim: set fileencoding=UTF-8: # Copyright 2013 Red Hat, Inc. # Author: Jan Pokorný # Licensed under MIT license import sys import re import getopt from os import environ from subprocess import Popen, PIPE from threading import Thread # svn checkout http://python-patch.googlecode.com/svn/trunk/ python-patch # pushd python-patch # patch <../python-patch-allow-for-externalizing-hunk-again.patch # popd # ln -s python-patch/patch . import patch re_h = re.compile( r'^Hunk \#(\d+) succeeded at (\d+)(?: with fuzz (\d+))? \(offset (-?\d+) lines?\)\.$', re.MULTILINE ) use_threading = True #use_threading = False def adjustment(ret, stderrdata): print >>sys.stderr, "ret = {0}\nstderrdata = {1}".format(ret, stderrdata) for match in re_h.finditer(stderrdata): print >>sys.stderr, match.groups() ret = int(match.groups()[3]) break else: ret = 0 return ret def hunk_worker(hunk, header, tres, i, cmd, **kwargs): proc = Popen(cmd, **kwargs) str_hunk = str(hunk) partial_patch = header + '\n' + str_hunk _, stderrdata = proc.communicate(partial_patch) delta = adjustment(proc.wait(), stderrdata) tres[i] = delta def proceed(opts, args): if not args: args.append('-') cmd_args = reduce(lambda a, x: a + list(x), opts, []) #print >>sys.stderr, "cmdargs: {0}, opts: {1}".format(cmd_args, opts) null = open('/dev/null', 'a') cmd = (lambda *a: a)('/usr/bin/patch', '-o-', *cmd_args) e = dict(LC='C') for name, value in environ.iteritems(): if name in ('TMPDIR', 'TMP', 'TEMP'): e[name] = value kwargs = dict(stdin=PIPE, stdout=null, stderr=PIPE, env=e) for arg in args: arg = arg if arg != '-' else '/dev/stdin' ps = patch.fromfile(arg) if not ps: print >>sys.stderr, "Bad patch file: {0}".format(arg) continue for p in ps.items: header = '{0}--- {1}\n+++ {2}'.format('\n'.join(p.header), p.source, p.target) print header tres = [0 for hunk in p.hunks] ts = [True for hunk in p.hunks] tmax = 10 avg = 0.0 cnt = 0 avg_limit = 50.0 len_ts = len(ts) while any(ts): # ts[i] = False if not to continue with that or bool(x) == True # otherwise (None is a temporary local state!) for i in xrange(len_ts): if not ts[i]: continue if not use_threading: hunk_worker(p.hunks[i], header, tres, i, cmd, **kwargs) ts[i] = None continue if i >= tmax: blockers = filter(lambda (x, y): bool(y), enumerate(ts[:i])) while len(blockers) >= tmax: for ii, it in blockers: it.join(0.001) if not it.is_alive(): ts[ii] = None break else: continue break t = Thread(target=hunk_worker, args=(p.hunks[i], header, tres, i, cmd), kwargs=kwargs) ts[i] = t t.start() for i in xrange(len_ts): t = ts[i] if t and use_threading: t.join() ts[i] = None if ts[i] is None: avg = (avg * cnt + tres[i]) cnt += 1 avg /= cnt ts[i] = False for i in xrange(len_ts): ts[i] = ts[i] or abs(tres[i] - avg) > avg_limit if i > 0: ts[i] = ts[i] or abs(tres[i] - tres[i-1]) > (avg_limit/2) if i < len_ts - 1: ts[i] = ts[i] or abs(tres[i] - tres[i+1]) > (avg_limit/2) if ts[i]: hunk = p.hunks[i] delta = int(tres[i] + (tres[i] - avg) / 1.25) hunk.startsrc += delta hunk.starttgt += delta avg_limit *= 1.25 for i in xrange(len_ts): delta = tres[i] hunk = p.hunks[i] if delta: hunk.startsrc += delta hunk.starttgt += delta print str(hunk), null.close() if __name__ == '__main__': # see PATCH(1) opts, args = getopt.getopt(sys.argv[1:], 'p:f:h', ['strip=', 'fuzz=']) if ('-h', '') in opts: print >>sys.stderr, "Usage: {0} {{[-p|-f]}} [patch-or-stdin]" \ .format(sys.argv[0]) sys.exit(0) sys.exit(proceed(opts, args))