linux/debian/bin/apply.py

436 lines
13 KiB
Python
Executable File

#!/usr/bin/env python2.4
import os, os.path, re, sys
from warnings import warn
_default_home = "@home@"
_default_revisions = "@revisions@"
_default_source = "@source@"
class series(list):
def __init__(self, name, home, reverse = False):
self.name = name
self.reverse = reverse
filename = os.path.join(home, 'series', name)
if not os.path.exists(filename):
raise RuntimeError, "Can't find series file for %s" % name
f = file(filename)
for line in f.readlines():
line = line.strip()
if len(line) == 0 or line[0] == '#':
continue
items = line.split(' ')
if len(items) != 2:
raise RuntimeError, "Line '%s' in file %s malformed." % (line, filename)
else:
operation, patch = items
if operation in ('+', '-'):
patchfile = os.path.join(home, patch)
for suffix, type in (('', 0), ('.bz2', 1), ('.gz', 2)):
if os.path.exists(patchfile + suffix):
patchinfo = patchfile + suffix, type
break
else:
raise RuntimeError, "Can't find patch %s for series %s" % (patchfile, name)
elif operation in ('X',):
backup = patch + ".bak"
if not os.path.exists(patch) and not os.path.exists(backup):
raise RuntimeError, "Can't find neither original nor backup file %s for series %s" % (patch, name)
patchinfo = patch, backup
else:
raise RuntimeError, 'Undefined operation "%s" in series %s' % (operation, name)
self.append((operation, patch, patchinfo))
def __repr__(self):
return '<%s object for %s>' % (self.__class__.__name__, self.name)
def apply(self):
if self.reverse:
for operation, patch, patchinfo in self[::-1]:
if operation == '.':
print """\
(.) IGNORED %s\
""" % patch
elif operation == '+':
self.patch_deapply(patch, patchinfo)
elif operation == '-':
self.patch_apply(patch, patchinfo)
elif operation == 'X':
os.rename(patchinfo[1], patchinfo[0])
print """\
(X) RESTORED %s\
""" % patch
print "--> %s fully unapplied." % self.name
else:
for operation, patch, patchinfo in self:
if operation == '.':
print """\
(.) IGNORED %s\
""" % patch
elif operation == '+':
self.patch_apply(patch, patchinfo)
elif operation == '-':
self.patch_deapply(patch, patchinfo)
elif operation == 'X':
os.rename(patchinfo[0], patchinfo[1])
print """\
(X) REMOVED %s\
""" % patch
print "--> %s fully applied." % self.name
def patch_apply(self, patch, patchinfo):
ret = self.patch_call(patchinfo, '--fuzz=1')
if ret == 0:
print """\
(+) OK %s\
""" % patch
else:
print """\
(+) FAIL %s\
""" % patch
raise SystemExit, 1
def patch_call(self, patchinfo, patchargs):
patchfile, type = patchinfo
cmdline = []
if type == 0:
cmdline.append('cat')
elif type == 1:
cmdline.append('bzcat')
elif type == 2:
cmdline.append('zcat')
cmdline.append(patchfile + ' | patch -p1 -f -s -t --no-backup-if-mismatch')
cmdline.append(patchargs)
return os.spawnv(os.P_WAIT, '/bin/sh', ['sh', '-c', ' '.join(cmdline)])
def patch_deapply(self, patch, patchinfo):
ret = self.patch_call(patchinfo, '-R')
if ret == 0:
print """\
(-) OK %s\
""" % patch
else:
print """\
(-) FAIL %s\
""" % patch
raise SystemExit, 1
@staticmethod
def read_all(s, home, reverse = False):
ret = []
for name in s:
filename = os.path.join(home, 'series', name)
if not os.path.exists(filename):
warn("Can't find series file for %s" % name)
else:
i = series(name, home, reverse)
ret.append(i)
return ret
class series_extra(series):
def __init__(self, name, home, extra, reverse = False):
self.extra = extra
self.extra_used = ()
self.name = name
self.reverse = reverse
filename = os.path.join(home, 'series', name + '-extra')
if not os.path.exists(filename):
raise RuntimeError, "Can't find series file for %s" % name
f = file(filename)
for line in f.readlines():
line = line.strip()
if len(line) == 0 or line[0] == '#':
continue
items = line.split(' ')
operation, patch = items[:2]
if operation in ('+', '-'):
patchfile = os.path.join(home, patch)
for suffix, type in (('', 0), ('.bz2', 1), ('.gz', 2)):
if os.path.exists(patchfile + suffix):
patchinfo = patchfile + suffix, type
break
else:
raise RuntimeError, "Can't find patch %s for series %s" % (patchfile, name)
else:
raise RuntimeError, 'Undefined operation "%s" in series %s' % (operation, name)
extra = {}
for s in items[2:]:
s = tuple(s.split('_'))
if len(s) > 3:
raise RuntimeError, "parse error"
if len(s) == 3:
raise RuntimeError, "Patch per flavour is not supported currently"
extra[s] = True
if not self._check_extra(extra):
operation = '.'
self.append((operation, patch, patchinfo))
def _check_extra(self, extra):
for i in (1, 2, 3):
t = self.extra[:i]
if extra.has_key(t):
if i > len(self.extra_used):
self.extra_used = t
return True
for i in (2, 3):
t = ('*',) + self.extra[1:i]
if extra.has_key(t):
if i > len(self.extra_used):
self.extra_used = t
return True
return False
@staticmethod
def read_all(s, home, extra, reverse = False):
ret = []
for name in s:
filename = os.path.join(home, 'series', name + '-extra')
if not os.path.exists(filename):
warn("Can't find series file for %s" % name)
else:
i = series_extra(name, home, extra, reverse)
ret.append(i)
return ret
class version(object):
__slots__ = "upstream", "revision"
def __init__(self, string = None):
if string is not None:
self.upstream, self.revision = self.parse(string)
def __str__(self):
return "%s-%s" % (self.upstream, self.revision)
_re = r"""
^
(
(?:
\d+\.\d+\.\d+\+
)?
\d+\.\d+\.\d+
(?:
-.+?
)?
)
-
([^-]+)
$
"""
def parse(self, version):
match = re.match(self._re, version, re.X)
if match is None:
raise ValueError
return match.groups()
class version_file(object):
_file = 'version.Debian'
extra = ()
in_progress = False
def __init__(self, ver = None, overwrite = False):
if overwrite:
self._read(ver)
elif os.path.exists(self._file):
s = file(self._file).readline().strip()
self._read(s)
elif ver:
warn('No %s file, assuming pristine Linux %s' % (self._file, ver.upstream))
self.version = version()
self.version.upstream = ver.upstream
self.version.revision = '0'
else:
raise RuntimeError, "Not possible to determine version"
def __str__(self):
if self.in_progress:
return "unstable"
if self.extra:
return "%s %s" % (self.version, '_'.join(self.extra))
return str(self.version)
def _read(self, s):
list = s.split(' ')
if len(list) > 2:
raise RuntimeError, "Can't parse %s" % self._file
try:
self.version = version(list[0])
except ValueError:
raise RuntimeError, 'Can\'t read version in %s: "%s"' % (self._file, list[0])
if len(list) == 2:
self.extra = tuple(list[1].split('_'))
def _write(self):
if os.path.lexists(self._file):
os.unlink(self._file)
file(self._file, 'w').write('%s\n' % self)
def begin(self):
self.in_progress = True
self._write()
def commit(self, version = None, extra = None):
self.in_progress = False
if version is not None:
self.version = version
if extra is not None:
self.extra = extra
self._write()
def main():
options, args = parse_options()
if len(args) > 1:
print "Too much arguments"
return
home = options.home
revisions = ['0'] + options.revisions.split()
source = version(options.source)
if len(args) == 1:
target = version(args[0])
else:
target = source
if options.current is not None:
vfile = version_file(options.current, True)
else:
vfile = version_file(source)
current = vfile.version
current_extra = vfile.extra
target_extra = []
if options.arch: target_extra.append(options.arch)
if options.subarch: target_extra.append(options.subarch)
if options.flavour: target_extra.append(options.flavour)
target_extra = tuple(target_extra)
if current.revision not in revisions:
raise RuntimeError, "Current revision is not in our list of revisions"
if target.revision not in revisions:
raise RuntimeError, "Target revision is not in our list of revisions"
if current.revision == target.revision and current_extra == target_extra:
print "Nothing to do"
return
current_index = revisions.index(current.revision)
target_index = revisions.index(target.revision)
if current_extra:
consider = revisions[current_index:0:-1]
s = series_extra.read_all(consider, home, current_extra, reverse = True)
vfile.begin()
for i in s:
i.apply()
vfile.commit(current, ())
if current_index < target_index:
consider = revisions[current_index + 1:target_index + 1]
s = series.read_all(consider, home)
vfile.begin()
for i in s:
i.apply()
vfile.commit(target, ())
elif current_index > target_index:
consider = revisions[current_index:target_index:-1]
s = series.read_all(consider, home, reverse = True)
vfile.begin()
for i in s:
i.apply()
vfile.commit(target, ())
if target_extra:
consider = revisions[1:target_index + 1]
s = series_extra.read_all(consider, home, target_extra)
real_extra = ()
for i in s:
t = i.extra_used
if len(t) > len(real_extra):
real_extra = t
vfile.begin()
for i in s:
i.apply()
vfile.commit(target, real_extra)
def parse_options():
from optparse import OptionParser
parser = OptionParser(
usage = "%prog [OPTION]... [TARGET]",
)
parser.add_option(
'-a', '--arch',
dest = 'arch',
help = "arch",
)
parser.add_option(
'-f', '--flavour',
dest = 'flavour',
help = "flavour",
)
parser.add_option(
'-s', '--subarch',
dest = 'subarch',
help = "subarch",
)
parser.add_option(
'-C', '--overwrite-current',
dest = 'current',
help = "overwrite current",
)
parser.add_option(
'-H', '--overwrite-home',
default = _default_home, dest = 'home',
help = "overwrite home [default: %default]",
)
parser.add_option(
'-R', '--overwrite-revisions',
default = _default_revisions, dest = 'revisions',
help = "overwrite revisions [default: %default]",
)
parser.add_option(
'-S', '--overwrite-source',
default = _default_source, dest = 'source',
help = "overwrite source [default: %default]",
)
options, args = parser.parse_args()
if options.arch is None and options.subarch is not None:
raise RuntimeError('You specified a subarch without an arch, this is not really working')
if options.subarch is None and options.flavour is not None:
raise RuntimeError('You specified a flavour without a subarch, this is not really working')
return options, args
if __name__ == '__main__':
def showwarning(message, category, filename, lineno):
sys.stderr.write("Warning: %s\n" % message)
import warnings
warnings.showwarning = showwarning
try:
main()
except RuntimeError, e:
sys.stderr.write("Error: %s\n" % e)
raise SystemExit, 1