-- eric
#!/usr/bin/python3
#
# ketchup 1.0.1
#    http://github.com/psomas/ketchup 
#    git://github.com/psomas/ketchup.git
#
# Orignial code by:
#    Copyright 2004 Matt Mackall <m...@selenic.com>
#
# Added code for new RT location and maintained until 2011 by:
#    Steven Rostedt <srost...@redhat.com>
#
# Now maintained by:
#    Stratos Psomadakis <pso...@cslab.ece.ntua.gr>
# 
# Contributors:
#    Baruch Even <bar...@debian.org>
#    Pavel Machek <pa...@ucw.cz>
#    Johann Felix Soden <joh...@gmx.de>
#    Carsten Emde <c.e...@osadl.org>
#
# This software may be used and distributed according to the terms
# of the GNU General Public License, incorporated herein by reference.
#
# Usage:
#
# in an existing kernel directory, run:
#
#  ketchup <version>
#
# where version is a complete kernel version, or a branch name to grab
# the latest version
#
# You can override some variables by creating a ~/.ketchuprc file.
# The ~/.ketchuprc is just a Python script, eg. it might look like this:
#
# kernel_url = 'http://kernel.localdomain/pub/linux/kernel'
# archive = os.environ["HOME"] + '/tmp/ketchup-archive'
# gpg = '/weird/path/to/gpg'
#

import re, sys, urllib, os, getopt, glob, shutil, subprocess

def error(*args):
    sys.stderr.write("ketchup: ")
    for a in args:
        sys.stderr.write(str(a))
        sys.stderr.write("\n")

def qprint(*args):
    if not options["quiet"]:
        sys.stdout.write(" ".join(map(str, args)))
        sys.stdout.write("\n")

def lprint(*args):
    sys.stdout.write(" ".join(map(str, args)))
    sys.stdout.write("\n")

def fancyopts(args, options, state, syntax=''):
    long = []
    short = ''
    map = {}
    dt = {}

    def help(state, opt, arg, options = options, syntax = syntax):
        lprint("Usage: ", syntax)

        for s, l, d, c in options:
            opt = ' '
            if s: opt = opt + '-' + s + ' '
            if l: opt = opt + '--' + l + ' '
            if d: opt = opt + '(' + str(d) + ')'
            lprint(opt)
            if c: lprint('   %s' % c)
        sys.exit(0)

    options = [('h', 'help', help, 'Show usage info')] + options

    for s, l, d, c in options:
        map['-'+s] = map['--'+l]=l
        state[l] = d
        dt[l] = type(d)
        if not d is None and not type(d) is type(help): s, l = s + ':', l + '='
        if s: short = short + s
        if l: long.append(l)

    if "KETCHUP_OPTS" in os.environ:
        args = os.environ["KETCHUP_OPTS"].split() + args

    try:
        opts, args = getopt.getopt(args, short, long)
    except getopt.GetoptError:
        help(state, None, args)
        sys.exit(-1)

    for opt, arg in opts:
        if dt[map[opt]] is type(help): state[map[opt]](state,map[opt],arg)
        elif dt[map[opt]] is type(1): state[map[opt]] = int(arg)
        elif dt[map[opt]] is type(''): state[map[opt]] = arg
        elif dt[map[opt]] is type([]): state[map[opt]].append(arg)
        elif dt[map[opt]] is type(None): state[map[opt]] = 1

    return args

# Default values
kernel_url = 'https://cdn.kernel.org/pub/linux/kernel'
archive = os.environ["HOME"] + "/.ketchup"
rename_prefix = 'linux-'
rename_with_localversion = False
wget = "/usr/bin/wget"
gpg = "/usr/bin/gpg"
precommand = postcommand = None
default_tree = None
local_trees = {}

# Functions to parse version strings

def tree(ver):
    # every version must start with x.y, exit otherwise
    t = re.match(r'((\d+)\.\d+)', ver)
    if t == None:
        error("Invalid tree version: %s!" % ver)
        sys.exit(-1)

    # linux versions >=3.0 use only the first digit to define the 'tree'
    ret = int(t.group(2)) 
    if ret >= 3:
        return ret

    # 2.x linux trees 
    return float(t.group(1))

def rev(ver):
    p = pre(ver)

    # 3.x trees
    if tree(ver) >= 3:
        r = re.match(r'\d+\.(\d+)', ver)
    # 2.x trees
    else:
        r = re.match(r'\d+\.\d+\.(\d+)', ver)

    # if there's no match, the version is invalid
    try: r = int(r.group(1))
    except: 
        error("Invalid tree version!")
        sys.exit(-1)

    #for -rc versions return the previous stable version
    if p: r = r - 1
    if r == -1 and tree(ver) == 3: r = 39
    if r == -1 and tree(ver)>3: r = 19
    
    return r

def pre(ver):
    # 3.x trees
    if tree(ver) >= 3:
        p = re.match(r'\d+\.\d+(\.\d+)?-(rc\d+)', ver)
    # 2.x trees
    else:
        p = re.match(r'\d+\.\d+\.\d+(\.\d+)?-(rc\d+)', ver)

    try: return p.group(2)
    except: return None

def post(ver):
    # 3.x trees
    if tree(ver) >= 3:
        p = re.match(r'\d+\.\d+\.(\d+)', ver)
    # 2.x trees
    else: 
        p = re.match(r'\d+\.\d+\.\d+\.(\d+)', ver)
    
    try: return p.group(1)
    except: return None

def prenum(ver):
    # 3.x trees
    if tree(ver) >= 3:
        p = re.match(r'\d+\.\d+(\.\d+)?-rc(\d+)', ver)
    # 2.x trees
    else:
        p = re.match(r'\d+\.\d+\.\d+(\.\d+)?-rc(\d+)', ver)
    
    try: return p.group(2)
    except: return None

def prebase(ver):
    # 3.x trees
    if tree(ver) >= 3:
        p = re.match(r'(\d+\.\d+(\.\d+)?(-rc\d+)?)', ver)
    else:
        p = re.match(r'(\d+\.\d+\.\d+(\.\d+)?(-rc\d+)?)', ver)

    try: return p.group(1)
    except: return None

def revbase(ver):
    t = tree(ver)
    r = rev(ver)
    # make sure 3.0-rcs are patched correctly against 2.6.39
    if t == 3 and r == 39:
        t = 2.6

    if t == 4 and r == 19:
        t = 3

    return "%s.%s" % (t, r)

def base(ver):
    v = revbase(ver)
    if post(ver): v += "." + post(ver)
    return v

def forkname(ver):
    # 3.x trees
    if tree(ver) >= 3:
        f = 
re.match(r'\d+\.\d+(\.\d+)?(-(rc|pre)\d+)?(-([a-zA-Z]+)(-)?(\d+)?)?',
            ver)
    # 2.x trees
    else:
        f = 
re.match(r'\d+\.\d+\.\d+(\.\d+)?(-(rc|pre)\d+)?(-([a-zA-Z]+)(-)?(\d+)?)?',
            ver)
    
    try: return f.group(5)
    except: return None

def forknum(ver):
    # 3.x trees
    if tree(ver) >=3:
        f = 
re.match(r'\d+\.\d+(\.\d+)?(-(rc|pre)\d+)?(-([a-zA-Z]+)(-)?(\d+)?)?',
            ver)
    #2.x trees
    else:
        f = 
re.match(r'\d+\.\d+\.\d+(\.\d+)?(-(rc|pre)\d+)?(-([a-zA-Z]+)(-)?(\d+)?)?',
            ver)

    try: return int(f.group(7))
    except: return None

def fork(ver):
    # 3.x trees
    if tree(ver) >= 3:
        f = 
re.match(r'\d+\.\d+(\.\d+)?(-(rc|pre)\d+)?((\.\d+)?-(\w+(-)?(\d+)?))?',
            ver)
    # 2.x trees
    else:
        f = 
re.match(r'\d+\.\d+\.\d+(\.\d+)?(-(rc|pre)\d+)?((\.\d+)?-(\w+(-)?(\d+)?))?',
            ver)
    try: return f.group(4)
    except: return None

#FIXME: at some point the Makefile for 3.x kernels will change
def get_ver(makefile):
    """ Read the version information from the specified makefile """
    part = {}
    parts = "VERSION PATCHLEVEL SUBLEVEL EXTRAVERSION CKVERSION".split(' ')
    m = open(makefile)
    for l in m.readlines():
        for p in parts:
            try: part[p] = re.match(r'%s\s*=\s*(\S+)' % p, l).group(1)
            except: pass

    # atm we use VERSION.PATCHLEVEL for Linus' releases and EXTRAVERSION for
    # the -stable releases
    if part['VERSION'] >= '3'and part['SUBLEVEL'] == '0':
        version = "%s.%s" % tuple([part[p] for p in parts[:2]])
    else:
        version = "%s.%s.%s" % tuple([part[p] for p in parts[:3]])
    version += part.get("EXTRAVERSION","")
    version += part.get("CKVERSION","")

    n = None
    try: n=open("localversion-rt")
    except IOError:
        pass
    if n:
         version += n.read()[:-1]

    # required for -next patchset
    try: n = open("localversion-next")
    except: return version

    version += n.read()[:-1]

    return version

def get_localversion():
    v = ''

    for name in glob.glob('localversion*'):
        try: v += open(name).readline().strip()
        except: pass

    try:
        c = open('.config').read()
        v += re.search(r'^CONFIG_LOCALVERSION="(.+)"', c, re.M).group(1)
    except: pass

    return v

def compare_ver(a, b):
    """
    Compare kernel versions a and b
    """
    if a == b: return 0

    c = cmp(float(tree(a)), float(tree(b)))
    if c: return c
    # compare correctly the 3.0-rc versions
    ra, rb = rev(a), rev(b)
    if (tree(a) == 3 and ra == 39): 
        ra = -1
    if (tree(a) > 3 and ra == 19): 
        ra = -1
    if (tree(b) == 3 and rb == 39):
        rb = -1
    if (tree(b) > 3 and rb == 19):
        rb = -1
    c = cmp(ra, rb)
    if c: return c
    c = cmp(int(post(a) or 0), int(post(b) or 0))
    if c: return c
    c = cmp(prenum(a), prenum(b))
    if c: return c
    c = cmp(forkname(a), forkname(b))
    if c: return c
    return cmp(forknum(a), forknum(b))

def last(url, pat="(.*/)"):
    print(url, pat)
    tokenize = re.compile(r'(\d+)|(\D+)').findall
    def natural_sortkey(string):          
            return tuple(int(num) if num else alpha for num, alpha in 
tokenize(string))
    n = None
    res = []
    for l in urllib.urlopen(url).readlines():
        search_res = re.finditer('(?i)<a\s+href="%s"\s*>' % pat, l)
        for m in search_res:
            if ".." not in m.group(0): res.append(m.group(1))
    if res:
        res.sort(key=natural_sortkey)
        n=res[-1]
    return n

def latest_mmotm(url, pat):
    for l in  urllib.urlopen(url + "/broken-out/mm.patch").readlines():
        m = re.search('-EXTRAVERSION = (\S+)', l)
        if m: n = m.group(1)

    latest = latest_dir(os.path.dirname(version_info['4-rc'][1]), 
('patch-(.*%s).xz' % n)) 

    return latest + "-mm1"

def latest_ck(url, pat):
    url = "http://ck.kolivas.org/patches/3.0/";
    url += last(url,"(\d.*)/")
    part = fork(last(url,"(\d.*)/"))
    stable = latest_dir(os.path.dirname(version_info['3'][1][0]), 
version_info['3'][2])

    return stable + part

def latest_pf(url, pat):
    url = "http://pf.natalenko.name/sources/";
    url += last(url)
    part = last(url,"(.*patch.*)")
    print("Part: ", part)
    part = re.search('\d+(.\d+)+-pf\d+', part).group(0)
    
    return part

def latest_dir_lt(url, pat):
    """Find the latest link to the stable release series that is used local"""
    cwd=os.getcwd()
    lv = None
    if os.path.isdir(options["directory"]):
        os.chdir(options["directory"])
        try:
            lv = get_ver('Makefile')
        except:
            lv = None
        os.chdir(cwd)
    if not lv:
        qprint("No local version found. Use newest kernel release instead.")
        return latest_dir(url, pat)
    local_revbase = revbase(lv)
    p = []
    url = url % { "kernel_url": kernel_url }
    for l in urllib.urlopen(url).readlines():
        m = re.search('"%s"' % pat, l)
        if m and revbase(m.group(1))==local_revbase:
            p.append(m.group(1))

    if not p: return None

    p.sort(compare_ver)
    return p[-1]

def latest_rt(url, pat):
    """ Find the link to the latest rt kernel patch """
    urls = []
    url_parent = os.path.dirname(url)
    postfix = ""
    while '%' in url_parent[1:]:
        postfix = os.path.basename(url) + "/" + postfix
        url_parent = os.path.dirname(url_parent)
    if postfix:
         postfix = "/" + postfix
    url_parent = url_parent  % { "kernel_url": kernel_url }
    if "2.6" in pat:
         m = last(url_parent, "(2\.6.*/)*")
    else:
         m = last(url_parent, "(\d.*/)")
    if not m:
          return None
    l = latest_dir(url_parent + '/' + m + postfix , pat)
    return l

def latest_dir(url, pat):
    """Find the latest link matching pat at url after sorting"""
    p = []
    url = url % { "kernel_url": kernel_url, "revbase":"" }
    for l in urllib.urlopen(url).readlines():
        m = re.search('"%s"' % pat, l)
        if m: p.append(m.group(1))

    if not p: return None

    p.sort(compare_ver)
    return p[-1]

def find_info(ver):
    t = tree(ver)
    # 3.x and 2.x kernels must be handled differently
    if t >= 3:
        b = "%d" % t
    else:
        b = "%.1f" % t
    f = forkname(ver)
    p = pre(ver)

    s = b
    if f:
        s = "%s-%s" % (b, f)
    elif p:
        s = "%s-rc" % b

    return version_info[s]

def version_urls(ver):
    """ Return the URL for the patch associated with the specified version """
    i = find_info(ver)[1]
    if type(i) != type([]):
        i = [i]

    v = {
        'full': ver,
        'tree': tree(ver),
        'base': base(ver),
        'prebase': prebase(ver),
        'fork': fork(ver),
        'revbase': revbase(ver),
        'kernel_url': kernel_url
        }

    l = []
    for e in i:
        l.append(e % v)

    return l

def patch_path(ver):
    patch_name = os.path.basename(version_urls(ver)[0])
    if re.search(r'\d+\.\d+', patch_name) == None:
        suf = re.search(r'\.tar\.(gz|(bz(\d+)?))', patch_name).group(0)
        patch_name = re.sub(r'(\.tar\.(gz|(bz(\d+)?)))', "-%s%s" % (ver, suf), 
patch_name)
    
    return os.path.join(archive, patch_name)

def download(url, f):
    qprint("Downloading %s" % os.path.basename(url))
    if options["dry-run"]:
        return 1

    if not options["wget"]:
        p = urllib.urlopen(url).read()
        if p.find("<title>404") != -1:
            return None
        open(f, 'w').write(p)
    else:
        e = os.system("%s -c -O %s %s" %
                      (options["wget"], f + ".partial", url))
        if e & 255:
            error("wget terminated by signal.")
            sys.exit(1)
        if e:
            try:
                # remove empty files
                if os.path.getsize(f+".partial")==0:
                    os.unlink(f+".partial")
            except OSError:
                pass
            return None
        os.rename(f + ".partial", f)
    return 1

def check_if_gpg_key_available(url, f, sign):
    if options["no-gpg"] or options["dry-run"] or not options["gpg-path"]:
        return 1
    qprint("Check if GPG key is available...")
    sf = f + sign
    if not download(url+sign, sf):
        sf = os.path.splitext(f)[0] + sign
        sign_url = os.path.splitext(url)[0] + sign
        if not download(sign_url, sf):
             error("signature download failed")
             return 0

    process= subprocess.Popen([options["gpg-path"], "--no-tty", "--batch", 
"--verify",sf,sf],
                              stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
    output, unused_err = process.communicate()
    r = process.poll()
    if r == 2: # key is not available
        qprint(output)
        error("The GPG key seems not to be in the keyring. Please fix this and 
try again.")
        qprint("In the case potential malicious kernel code is not a problem,\n"
               "you can skip the verifying by using --no-gpg.")
        return -1
    if r < 0: # killed by signal
        return -1
    return 1

def verify(url, f, sign):
    if options["no-gpg"] or options["dry-run"] or not options["gpg-path"]:
        return 1

    need_extract=False

    sf = f + sign
    sign_url = url + sign
    if not os.path.isfile(sf) and not download(sign_url, sf):
        sf = os.path.splitext(f)[0] + sign
        sign_url = os.path.splitext(url)[0] + sign
        need_extract=True

    if not os.path.isfile(sf) and not download(sign_url, sf):
        error("signature download failed")
        error("removing files...")
        os.unlink(sf)
        return 0

    qprint("Verifying signature...")
    if need_extract:
        if f[-4:] == ".bz2":
                r = os.system("bzcat %s | %s --verify %s -" % 
(f,options["gpg-path"], sf))
        elif f[-3:] == ".gz":
                r = os.system("zcat %s | %s --verify %s -" % 
(f,options["gpg-path"], sf))
        elif f[-3:] == ".xz":
                r = os.system("xzcat %s | %s --verify %s -" % 
(f,options["gpg-path"], sf))
        else:
            error("Cannot check signature: Unknown compression algorithm")
            error("removing files...")
            os.unlink(f)
            os.unlink(sf)
            return 1
    else:
            r = os.system("%s --verify %s %s" % (options["gpg-path"], sf, f))

    if r:
        error("gpg returned %d" % r)
        error("removing files...")
        os.unlink(f)
        os.unlink(sf)
        return 0

    return 1

def trydownload(urls, f, sign):
    for url in urls:
        if sign:
            result=check_if_gpg_key_available(url, f, sign)
            if result < 0: # gpg key not available
                return None
            elif result==0: # download failed
                continue
        if os.path.isfile(f):
            if not sign or verify(url, f, sign):
                return f
        if url[-4:] == ".bz2":
            try_exts=['.xz', '.bz2', '.gz', '']
            for ext in try_exts:
                f2=f[:-4]+ext
                if os.path.isfile(f2):
                    if not sign or verify(url, f2, sign):
                        return f2
            for ext in try_exts:
                f2=f[:-4]+ext
                url2 = url[:-4] + ext
                if download(url2, f2):
                    if not sign or verify(url2, f2, sign):
                        return f2
        elif download(url, f):
            if not sign or verify(url, f, sign):
                return f
    return None

def get_patch(ver):
    """Return the path to patch for given ver, downloading if necessary"""
    f = patch_path(ver)
    if os.path.exists(f):
        return f

    if re.search(r'-mm1', ver) != None:
        if pre(find_ver('3-rc')) != pre(ver):
            error("-mm1 patchset can only be applied to the latest -rc 
release!")
            sys.exit(-1)

    if f[-4:] == ".bz2":
        f2 = f[:-4] + ".xz"
        if os.path.exists(f2):
            return f2
        f2 = f[:-4] + ".gz"
        if os.path.exists(f2):
            return f2
        f2 = f[:-4]
        if os.path.exists(f2):
            return f2

    urls = version_urls(ver)
    sign = find_info(ver)[3]
    if sign == 1: sign = ".sign"
    f = trydownload(urls, f, sign)
    if not f:
        error("patch download failed")
        sys.exit(-1)

    return f

def apply_mmotm(ver, reverse = 0):
    if reverse:
        err = os.system("quilt pop -a &> .patchdiag")
        if err:
            sys.stderr.write(open(".patchdiag").read())
        os.unlink(".patchdiag")
        os.system("rm -rf patches .pc Next")
    else:
        old = os.getcwd()
        os.mkdir("patches")
        os.chdir("patches")
        err = os.system("zcat %s | tar -xf -" % patch_path(ver))
        if err:
            error("Unpacking failed: ", err)
            os.chdir(old)
            os.system("rm -rf patches")
            sys.exit(-1)
 
        os.system("mv broken-out/*.patch ./")

        os.chdir(old)

        err = os.system("quilt push -a --leave-rejects &> .patchdiag")
        if err:
            sys.stderr.write(open(".patchdiag").read())
        os.unlink(".patchdiag")

    return err

def apply_patch(ver, reverse = 0):
    """Find the patch to upgrade from the predecessor of ver to ver and
    apply or reverse it."""
    p = get_patch(ver)
    r = ""
    if reverse:
        r = " -R"

    qprint("Applying %s%s" % (os.path.basename(p), r))
    if options["dry-run"] or options["only-dl"]:
        return ver

    if forkname(ver) == "mm":
        return apply_mmotm(ver, reverse)

    def cmd(patch, reverse, dry):
        base = "patch -l -p1%s" % reverse
        if dry:
            base += " --dry-run"

        if p[-4:] == ".bz2":
            pipe = "bzcat %s | %s" % (patch, base)
        elif p[-3:] == ".gz":
            pipe = "zcat %s | %s" % (patch, base)
        elif p[-3:] == ".xz":
            pipe = "xzcat %s | %s" % (patch, base)
        else:
            pipe = "%s < %s" % (base, patch)

        err = os.system(pipe + " > .patchdiag")
        if err:
            sys.stderr.write(open(".patchdiag").read())
        os.unlink(".patchdiag")
        return err

    err = cmd(p, r, 1)
    if err:
        if os.WIFSIGNALED(err):
            error("patch %s failed: killed with signal %d" % (p, 
os.WTERMSIG(err)))
        else:
            error("patch %s failed: return value %d" % (p, os.WEXITSTATUS(err)))
        sys.exit(-1)

    err = cmd(p, r, 0)
    if err:
        if os.WIFSIGNALED(err):
            error("patch %s failed while it was supposed to apply: signal: %d" 
% (p, os.WTERMSIG(err)))
        else:
            error("patch %s failed while it was supposed to apply: %d" % (p, 
os.WEXITSTATUS(err)))
        sys.exit(-1)

def untar(tarfile):
    old = os.getcwd()
    if os.path.exists("ketchup-tmp"):
        error("Please remove the ketchup-tmp directory by hand.")
        sys.exit(-1)
    os.mkdir("ketchup-tmp")
    try:
        os.chdir("ketchup-tmp")

        if tarfile[-4:] == ".bz2":
            err = os.system("tar -jxf %s" % tarfile)
        elif tarfile[-3:] == ".gz":
            err = os.system("tar -zxf %s" % tarfile)
        elif tarfile[-3:] == ".xz":
            err = os.system("xzcat %s | tar -xf -" % tarfile)
        elif tarfile[-4:] == ".tar":
            err = os.system("tar -xf %s" % tarfile)
        else:
            err="Unknown compression algorithm."

        if err:
            error("Unpacking failed: ", err)
            sys.exit(-1)

        ldir = glob.glob("linux*")[0]
        for f in os.listdir(ldir):
            os.rename(ldir + "/" + f, "../" + f)
    finally:
        os.chdir(old)
        shutil.rmtree("ketchup-tmp")

def install_nearest(ver):
    t = tree(ver)
    tarballs = glob.glob(archive + "/linux-%s.*.tar.xz" % t)
    list = []

    for f in tarballs:
        m = re.match(r'.*/linux-(.*).tar.xz$', f)
        v = m.group(1)
        d = abs(rev(v) - rev(ver))
        list.append((d, f, v))
    list.sort()

    # ugly hack, upstream dir is /v3.x (not v3)
    # FIXME: we assume that this will be the case for linux-4.0 and later 
releases
    # if not, we'll have to fix it again :)
    if t >= 3:
        t = "%s.x" % t

    if not list or (options["full-tarball"] and list[0][0]):
        f = "linux-%s.tar.xz" % ver
        url = "%s/v%s/%s" % (kernel_url, t, f)
        url_longterm = "%s/v%s/longterm/v%s/%s" % (kernel_url, t, revbase(ver), 
f)
        f = archive + "/" + f

        sign = find_info(ver)[3]
        if sign == 1: sign = ".sign"

        f = trydownload([url, url_longterm], f, sign)
        if not f:
            error("Tarball download failed")
            sys.exit(-1)

    else:
        f = list[0][1]
        ver = list[0][2]

    qprint("Unpacking %s" % os.path.basename(f))
    if options["dry-run"] or options["only-dl"]: return ver
    untar(f)

    return ver

def find_ver(ver):
    if ver in version_info.keys():
        v = version_info[ver]
        d = v[1]
        if not type(d) is type([]):
            d = [d]
        for n in d:
            r = v[0](os.path.dirname(n), v[2])
            if r:
                return r
    else:
        return ver

def transform(a, b):
    if a == b:
        qprint("Nothing to do!")
        return
    if not b:
            error("No version found.")
            sys.exit(-1)
    if not a:
        a = install_nearest(base(b))
    t = tree(a)
    # 2.4 -> >=2.6 and >=2.6 -> 2.4 are not supported
    if (t ==2.4 and tree(b) != 2.4) or (tree(b) == 2.4 and t != 2.4):
        error("Can't patch %s to %s" % (tree(a), tree(b)))
        sys.exit(-1)
    if fork(a):
        apply_patch(a, 1)
        a = prebase(a)
    if prebase(a) != prebase(b):
        if pre(a):
            apply_patch(a, 1)
            a = base(a)

        if post(a) and (post(a) != post(b) or rev(a) != rev(b)):
            apply_patch(prebase(a), 1)
        
        # handle the transition from 2.6.39 to 3.0
        ra, rb = rev(a), rev(b)
        if tree(a) == tree(b) and ra > rb:
            for r in range(ra, rb, -1):
                apply_patch("%s.%s" % (tree(a), r), -1)
        if tree(a) == tree(b) and ra < rb:
            for r in range(ra + 1, rb + 1):
                apply_patch("%s.%s" % (tree(b), r))
        if tree(a) > tree(b):
            for r in range(ra, -1, -1):
                apply_patch("%s.%s" % (tree(a), r), -1)
            if tree(a)==2.6:
                for r in range(39, rb, -1):
                    apply_patch("%s.%s" % (tree(b), r), -1)
            else:
                for r in range(19, rb, -1):
                    apply_patch("%s.%s" % (tree(b), r), -1)
        if tree(b) > tree(a):
            if tree(a)==2.6:
                for r in range(ra + 1, 40):
                    apply_patch("%s.%s" % (tree(a), r))
                if rb != 39:
                    for r in range(0, rb + 1, 1):
                        apply_patch("%s.%s" % (tree(b), r))
            else:
                for r in range(ra + 1, 20):
                    apply_patch("%s.%s" % (tree(a), r))
                if rb != 19:
                    for r in range(0, rb + 1, 1):
                        apply_patch("%s.%s" % (tree(b), r))
        a = revbase(b)

        if post(b) and post(a) != post(b):
            apply_patch(prebase(b), 0)
            a = base(b)

        if pre(b):
            apply_patch(prebase(b))
            a = prebase(b)

    if fork(b):
        a = apply_patch(b)

def rename_dir(v):
    """Rename the current directory to linux-v, where v is the function arg"""
    if rename_with_localversion:
        v += get_localversion()
    cwd = os.getcwd()
    basedir = os.path.dirname(cwd)
    newdir = os.path.join(basedir, rename_prefix + v)
    if newdir == cwd:
        return
    if os.access(newdir, os.F_OK):
        error("Cannot rename directory, destination exists: %s", newdir);
        return
    os.rename(cwd, newdir)
    qprint('Current directory renamed to %s' % newdir)


# latest lookup function, canonical urls, pattern for lookup function,
# signature flag, description
version_info = {
    '2.4': (latest_dir,
            "%(kernel_url)s" + "/v2.4" + "/patch-%(base)s.bz2",
            r'patch-(.*?).bz2',
            1, "2.4 kernel series"),
    '2.6': (latest_dir,
            ["%(kernel_url)s" + "/v2.6" + "/patch-%(prebase)s.bz2",
             "%(kernel_url)s" + 
"/v2.6/longterm/v%(revbase)s/patch-%(prebase)s.bz2"],
            r'linux-(.*?).tar.bz2',
            1, "2.6 kernel series"),
    '2.6-lt': (latest_dir_lt,
               ["%(kernel_url)s" + "/v2.6" + "/patch-%(prebase)s.bz2",
                "%(kernel_url)s" + 
"/v2.6/longterm/v%(revbase)s/patch-%(prebase)s.bz2"],
               r'patch-(.*?).bz2',
               1, "2.6 kernel series - update (only) to newer longterm stable 
releases (fourth number of 2.6 kernels)"),
    '2.6-rc': (latest_dir,
               "%(kernel_url)s" + "/v2.6" + "/testing/patch-%(prebase)s.bz2",
               r'linux-(.*?).tar.xz',
               1, "2.6 kernel series prereleases"),
    '2.6-rt': (latest_rt,
               ["%(kernel_url)s" + 
"/projects/rt/%(revbase)s/patch-%(full)s.bz2",
                "%(kernel_url)s" + 
"/projects/rt/%(revbase)s/older/patch-%(full)s.bz2"],
               r'patch-(2.6.*?).bz2',
               1, "Ingo Molnar's realtime-preempt kernel"),
    '2.6-pf': (latest_pf, 
               "http://pf.natalenko.name/sources"; + "/%(revbase)s" 
               + "/patch-%(full)s.bz2",
               r'patch-(.*?).bz2',
               0, "-pf kernel patchset"),
    '3-pf': (latest_pf, 
               "http://pf.natalenko.name/sources"; + "/%(revbase)s" 
               + "/patch-%(full)s.bz2",
               r'patch-(.*?).bz2',
               0, "-pf kernel patchset"),
    '4-pf': (latest_pf, 
               "http://pf.natalenko.name/sources"; + "/%(revbase)s" 
               + "/patch-%(full)s.bz2",
               r'patch-(.*?).bz2',
               0, "-pf kernel patchset"),
    '3-next': (latest_dir,
                 "%(kernel_url)s" + "/next" + "/patch-v%(prebase)s%(fork)s.xz",
                 r'patch-v(.*?).xz',
                 1, "3 linux-next tree"),
    '3-rc': (latest_dir,
             "%(kernel_url)s" + "/v3.x" + "/testing/patch-%(prebase)s.xz",
             r'patch-(.*?).xz',
             1, "3.x stable kernel series prereleases"),
    # current -next releases, for 4.x kernels
    '4-next': (latest_dir,
                 "%(kernel_url)s" + "/next" + "/patch-v%(prebase)s%(fork)s.xz",
                 r'patch-v(.*?).xz',
                 1, "4 linux-next tree"),
    '4-mm': (latest_mmotm, 
               "http://ozlabs.org/~akpm/mmotm/broken-out.tar.gz";,
               r'broken-out.tar.gz',
               0, "-mmotm quilt patchset"),
    '4-rc': (latest_dir,
             "%(kernel_url)s" + "/v4.x" + "/testing/patch-%(prebase)s.xz",
             r'patch-(.*?).xz',
             1, "current stable kernel series prereleases"),
    '3-rt': (latest_rt,
             ["%(kernel_url)s" + 
"/projects/rt/%(revbase)s/patch-%(full)s.patch.xz",
              "%(kernel_url)s" + 
"/projects/rt/%(revbase)s/older/patch-%(full)s.patch.xz",
             ],
             r'patch-(3(\.\d+)*-rt\d+).patch.xz', # TODO: add git-based releases
             1, "PREEMPT_RT real-time kernel (only based directly on stable and 
-rc kernels.)"),
    '3-ck': (latest_ck,
            "http://ck.kolivas.org/patches/3.0/"; +
            "/%(revbase)s/%(revbase)s%(fork)s/patch-%(revbase)s%(fork)s.xz",
            "", 0,
            "Con Kolivas' -ck patchset"),
    '4-rt': (latest_rt,
             ["%(kernel_url)s" + 
"/projects/rt/%(revbase)s/patch-%(full)s.patch.xz",
              "%(kernel_url)s" + 
"/projects/rt/%(revbase)s/older/patch-%(full)s.patch.xz",
             ],
             r'patch-([456](\.\d+)*-rt\d+).patch.xz', # TODO: add git-based 
releases
             1, "PREEMPT_RT real-time kernel (only based directly on stable and 
-rc kernels.)"),
    '3-lt': (latest_dir_lt,
               "%(kernel_url)s" + "/v3.x" + "/patch-%(prebase)s.xz",
               r'patch-(.*?).xz',
               1, "3.x kernel series - update (only) to newer longterm stable 
releases (third number of 3.x kernels)"),
    '4-lt': (latest_dir_lt,
               "%(kernel_url)s" + "/v4.x" + "/patch-%(prebase)s.xz",
               r'patch-(.*?).xz',
               1, "4.x kernel series - update (only) to newer longterm stable 
releases (third number of 4.x kernels)"),
    '3': (latest_dir,
          ["%(kernel_url)s" + "/v3.x" + "/patch-%(prebase)s.xz"],
          r'patch-(.*?).xz',
          1, "last stable kernel series"),
    '4': (latest_dir,
          ["%(kernel_url)s" + "/v4.x" + "/patch-%(prebase)s.xz"],
          r'patch-(.*?).xz',
          1, "current stable kernel series"),
    '5': (latest_dir,
          ["%(kernel_url)s" + "/v5.x" + "/patch-%(prebase)s.xz"],
          r'patch-(.*?).xz',
          1, "current stable kernel series"),

    }

# Override defaults with ~/.ketchuprc which is just a Python script
rcpath = os.path.expanduser('~/.ketchuprc')
if os.path.isfile(rcpath):
    try:
        execfile(rcpath)
    except Exception as e:
        sys.exit('Failed parsing %s\nError was: %s' % (rcpath, e))

# Add local trees
for k,v in local_trees.items():
    version_info[k] = v

# Environment variables override defaults and ketchuprc
kernel_url = os.environ.get("KETCHUP_URL", kernel_url)
archive = os.environ.get("KETCHUP_ARCH", archive)

# And finally command line overrides everything
if not os.path.exists(wget): wget = ""
if not os.path.exists(gpg): gpg = ""

options = {}
opts = [
    ('a', 'archive', archive, 'cache directory'),
    ('d', 'directory', '.', 'directory to update'),
    ('f', 'full-tarball', None, 'if unpacking a tarball, download the latest'),
    ('g', 'gpg-path', gpg, 'path for GnuPG'),
    ('G', 'no-gpg', None, 'disable GPG signature verification'),
    ('k', 'kernel-url', kernel_url, 'base url for kernel.org mirror'),
    ('l', 'list-trees', None, 'list supported trees'),
    ('m', 'show-makefile', None, 'output version in makefile <arg>'),
    ('n', 'dry-run', None, 'don\'t download or apply patches'),
    ('o', 'only-dl', None, 'don\'t apply patches'),
    ('p', 'show-previous', None, 'output version previous to <arg>'),
    ('q', 'quiet', None, 'reduce output'),
    ('r', 'rename-directory', None, 'rename updated directory to %s<v>'
     % rename_prefix),
    ('s', 'show-latest', None, 'output the latest version of <arg>'),
    ('u', 'show-url', None, 'output URL for <arg>'),
    ('w', 'wget', wget, 'command to use for wget'),
    ]

args = fancyopts(sys.argv[1:], opts, options,
                 'ketchup [options] [ver]')

archive = options["archive"]
kernel_url = options["kernel-url"]
if options["no-gpg"]: options["gpg-path"] = ''

# Process args

if not os.path.exists(options["directory"]):
    qprint("Creating target directory", options["directory"])
    os.mkdir(options["directory"])
os.chdir(options["directory"])

if os.path.isfile(".ketchuprc"):
    try:
        execfile(".ketchuprc")
    except Exception as e:
        sys.exit('Failed parsing .ketchuprc\nError was: %s' % (e))

if options["list-trees"]:
    l = version_info.keys()
    l.sort()
    for tree in l:
        if version_info[tree][3] == 0:
            lprint(tree, "(unsigned)")
        else:
            lprint(tree, "(signed)")
        lprint(" " + version_info[tree][4])
    sys.exit(0)

if options["show-makefile"] and len(args) < 2:
    if not args:
        lprint(get_ver("Makefile"))
    else:
        lprint(get_ver(args[0]))
    sys.exit(0)

if len(args) == 0 and default_tree:
    qprint("Using default tree \"%s\"" % (default_tree))
    args.append(default_tree)

if len(args) != 1:
    error("No version given on command line and no default in configuration")
    sys.exit(-1)

if options["show-latest"]:
    lprint(find_ver(args[0]))
    sys.exit(0)

if options["show-url"]:
    lprint(version_urls(find_ver(args[0]))[0])
    sys.exit(0)

if options["show-previous"]:
    v = find_ver(args[0])
    p = prebase(v)
    if p == v: p = base(v)
    if p == v:
        if rev(v) > 0: p = "%.1f.%s" % (tree(v), rev(v) -1)
        else: p = "unknown"
    lprint(p)
    sys.exit(0)

if not os.path.exists(options["archive"]):
    qprint("Creating cache directory", options["archive"])
    os.mkdir(options["archive"])

if precommand and os.system(precommand):
    sys.exit('Precommand "%s" failed!' % precommand)

try:
    a = get_ver('Makefile')
except:
    a = None

if not a and os.listdir("."):
    error("Can't find kernel version for non-empty directory")
    sys.exit(-1)

b = find_ver(args[0])
qprint("%s -> %s" % (a, b))
transform(a, b)
if options["rename-directory"] and not options["dry-run"] and not 
options["only-dl"] :
    rename_dir(b)

if postcommand and os.system(postcommand):
    sys.exit('Postcommand "%s" failed!' % postcommand)

Reply via email to