aboutsummaryrefslogtreecommitdiffstats
path: root/RepSys
diff options
context:
space:
mode:
Diffstat (limited to 'RepSys')
-rw-r--r--RepSys/ConfigParser.py398
-rw-r--r--RepSys/__init__.py9
-rw-r--r--RepSys/cgi/__init__.py0
-rw-r--r--RepSys/cgi/soapserver.py93
-rw-r--r--RepSys/cgi/submit.py119
-rw-r--r--RepSys/cgi/xmlrpcserver.py111
-rw-r--r--RepSys/cgiutil.py42
-rw-r--r--RepSys/command.py53
-rw-r--r--RepSys/commands/__init__.py0
-rw-r--r--RepSys/commands/authoremail.py34
-rw-r--r--RepSys/commands/changed.py35
-rw-r--r--RepSys/commands/co.py36
-rw-r--r--RepSys/commands/create.py30
-rw-r--r--RepSys/commands/editlog.py38
-rw-r--r--RepSys/commands/getspec.py31
-rw-r--r--RepSys/commands/getsrpm.py76
-rw-r--r--RepSys/commands/markrelease.py96
-rw-r--r--RepSys/commands/patchspec.py35
-rw-r--r--RepSys/commands/putsrpm.py61
-rw-r--r--RepSys/commands/rpmlog.py40
-rw-r--r--RepSys/commands/submit.py104
-rw-r--r--RepSys/log.py100
-rw-r--r--RepSys/pexpect.py1050
-rw-r--r--RepSys/rpm.py19
-rw-r--r--RepSys/rpmutil.py369
-rw-r--r--RepSys/svn.py349
-rw-r--r--RepSys/util.py54
27 files changed, 3382 insertions, 0 deletions
diff --git a/RepSys/ConfigParser.py b/RepSys/ConfigParser.py
new file mode 100644
index 0000000..0f219b9
--- /dev/null
+++ b/RepSys/ConfigParser.py
@@ -0,0 +1,398 @@
+"""
+This is a heavily hacked version of ConfigParser to keep the order in
+which options and sections are read, and allow multiple options with
+the same key.
+"""
+from __future__ import generators
+import string, types
+import re
+
+__all__ = ["NoSectionError","DuplicateSectionError","NoOptionError",
+ "InterpolationError","InterpolationDepthError","ParsingError",
+ "MissingSectionHeaderError","ConfigParser",
+ "MAX_INTERPOLATION_DEPTH"]
+
+DEFAULTSECT = "DEFAULT"
+
+MAX_INTERPOLATION_DEPTH = 10
+
+# exception classes
+class Error(Exception):
+ def __init__(self, msg=''):
+ self._msg = msg
+ Exception.__init__(self, msg)
+ def __repr__(self):
+ return self._msg
+ __str__ = __repr__
+
+class NoSectionError(Error):
+ def __init__(self, section):
+ Error.__init__(self, 'No section: %s' % section)
+ self.section = section
+
+class DuplicateSectionError(Error):
+ def __init__(self, section):
+ Error.__init__(self, "Section %s already exists" % section)
+ self.section = section
+
+class NoOptionError(Error):
+ def __init__(self, option, section):
+ Error.__init__(self, "No option `%s' in section: %s" %
+ (option, section))
+ self.option = option
+ self.section = section
+
+class InterpolationError(Error):
+ def __init__(self, reference, option, section, rawval):
+ Error.__init__(self,
+ "Bad value substitution:\n"
+ "\tsection: [%s]\n"
+ "\toption : %s\n"
+ "\tkey : %s\n"
+ "\trawval : %s\n"
+ % (section, option, reference, rawval))
+ self.reference = reference
+ self.option = option
+ self.section = section
+
+class InterpolationDepthError(Error):
+ def __init__(self, option, section, rawval):
+ Error.__init__(self,
+ "Value interpolation too deeply recursive:\n"
+ "\tsection: [%s]\n"
+ "\toption : %s\n"
+ "\trawval : %s\n"
+ % (section, option, rawval))
+ self.option = option
+ self.section = section
+
+class ParsingError(Error):
+ def __init__(self, filename):
+ Error.__init__(self, 'File contains parsing errors: %s' % filename)
+ self.filename = filename
+ self.errors = []
+
+ def append(self, lineno, line):
+ self.errors.append((lineno, line))
+ self._msg = self._msg + '\n\t[line %2d]: %s' % (lineno, line)
+
+class MissingSectionHeaderError(ParsingError):
+ def __init__(self, filename, lineno, line):
+ Error.__init__(
+ self,
+ 'File contains no section headers.\nfile: %s, line: %d\n%s' %
+ (filename, lineno, line))
+ self.filename = filename
+ self.lineno = lineno
+ self.line = line
+
+class ConfigParser:
+ def __init__(self, defaults=None):
+ # Options are stored in __sections_list like this:
+ # [(sectname, [(optname, optval), ...]), ...]
+ self.__sections_list = []
+ self.__sections_dict = {}
+ if defaults is None:
+ self.__defaults = {}
+ else:
+ self.__defaults = defaults
+
+ def defaults(self):
+ return self.__defaults
+
+ def sections(self):
+ return self.__sections_dict.keys()
+
+ def has_section(self, section):
+ return self.__sections_dict.has_key(section)
+
+ def options(self, section):
+ self.__sections_dict[section]
+ try:
+ opts = self.__sections_dict[section].keys()
+ except KeyError:
+ raise NoSectionError(section)
+ return self.__defaults.keys()+opts
+
+ def read(self, filenames):
+ if type(filenames) in types.StringTypes:
+ filenames = [filenames]
+ for filename in filenames:
+ try:
+ fp = open(filename)
+ except IOError:
+ continue
+ self.__read(fp, filename)
+ fp.close()
+
+ def readfp(self, fp, filename=None):
+ if filename is None:
+ try:
+ filename = fp.name
+ except AttributeError:
+ filename = '<???>'
+ self.__read(fp, filename)
+
+ def set(self, section, option, value):
+ if self.__sections_dict.has_key(section):
+ sectdict = self.__sections_dict[section]
+ sectlist = []
+ self.__sections_list.append((section, sectlist))
+ elif section == DEFAULTSECT:
+ sectdict = self.__defaults
+ sectlist = None
+ else:
+ sectdict = {}
+ self.__sections_dict[section] = sectdict
+ sectlist = []
+ self.__sections_list.append((section, sectlist))
+ xform = self.optionxform(option)
+ sectdict[xform] = value
+ if sectlist is not None:
+ sectlist.append([xform, value])
+
+ def get(self, section, option, raw=0, vars=None):
+ d = self.__defaults.copy()
+ try:
+ d.update(self.__sections_dict[section])
+ except KeyError:
+ if section != DEFAULTSECT:
+ raise NoSectionError(section)
+ if vars:
+ d.update(vars)
+ option = self.optionxform(option)
+ try:
+ rawval = d[option]
+ except KeyError:
+ raise NoOptionError(option, section)
+ if raw:
+ return rawval
+ return self.__interpolate(rawval, d)
+
+ def getall(self, section, option, raw=0, vars=None):
+ option = self.optionxform(option)
+ values = []
+ d = self.__defaults.copy()
+ if section != DEFAULTSECT:
+ for sectname, options in self.__sections_list:
+ if sectname == section:
+ for optname, value in options:
+ if optname == option:
+ values.append(value)
+ d[optname] = value
+ if raw:
+ return values
+ if vars:
+ d.update(vars)
+ for i in len(values):
+ values[i] = self.__interpolate(values[i], d)
+ return values
+
+ def walk(self, section, option=None, raw=0, vars=None):
+ # Build dictionary for interpolation
+ try:
+ d = self.__sections_dict[section].copy()
+ except KeyError:
+ if section == DEFAULTSECT:
+ d = {}
+ else:
+ raise NoSectionError(section)
+ d.update(self.__defaults)
+ if vars:
+ d.update(vars)
+
+ # Start walking
+ if option:
+ option = self.optionxform(option)
+ if section != DEFAULTSECT:
+ for sectname, options in self.__sections_list:
+ if sectname == section:
+ for optname, value in options:
+ if not option or optname == option:
+ if not raw:
+ value = self.__interpolate(value, d)
+ yield (optname, value)
+
+ def __interpolate(self, value, vars):
+ rawval = value
+ depth = 0
+ while depth < 10:
+ depth = depth + 1
+ if value.find("%(") >= 0:
+ try:
+ value = value % vars
+ except KeyError, key:
+ raise InterpolationError(key, option, section, rawval)
+ else:
+ break
+ if value.find("%(") >= 0:
+ raise InterpolationDepthError(option, section, rawval)
+ return value
+
+ def __get(self, section, conv, option):
+ return conv(self.get(section, option))
+
+ def getint(self, section, option):
+ return self.__get(section, string.atoi, option)
+
+ def getfloat(self, section, option):
+ return self.__get(section, string.atof, option)
+
+ def getboolean(self, section, option):
+ states = {'1': 1, 'yes': 1, 'true': 1, 'on': 1,
+ '0': 0, 'no': 0, 'false': 0, 'off': 0}
+ v = self.get(section, option)
+ if not states.has_key(v.lower()):
+ raise ValueError, 'Not a boolean: %s' % v
+ return states[v.lower()]
+
+ def optionxform(self, optionstr):
+ #return optionstr.lower()
+ return optionstr
+
+ def has_option(self, section, option):
+ """Check for the existence of a given option in a given section."""
+ if not section or section == "DEFAULT":
+ return self.__defaults.has_key(option)
+ elif not self.has_section(section):
+ return 0
+ else:
+ option = self.optionxform(option)
+ return self.__sections_dict[section].has_key(option)
+
+ SECTCRE = re.compile(r'\[(?P<header>[^]]+)\]')
+ OPTCRE = re.compile(r'(?P<option>\S+)\s*(?P<vi>[:=])\s*(?P<value>.*)$')
+
+ def __read(self, fp, fpname):
+ cursectdict = None # None, or a dictionary
+ optname = None
+ lineno = 0
+ e = None # None, or an exception
+ while 1:
+ line = fp.readline()
+ if not line:
+ break
+ lineno = lineno + 1
+ # comment or blank line?
+ if line.strip() == '' or line[0] in '#;':
+ continue
+ if line.split()[0].lower() == 'rem' \
+ and line[0] in "rR": # no leading whitespace
+ continue
+ # continuation line?
+ if line[0] in ' \t' and cursectdict is not None and optname:
+ value = line.strip()
+ if value:
+ k = self.optionxform(optname)
+ cursectdict[k] = "%s\n%s" % (cursectdict[k], value)
+ cursectlist[-1][1] = "%s\n%s" % (cursectlist[-1][1], value)
+ # a section header or option header?
+ else:
+ # is it a section header?
+ mo = self.SECTCRE.match(line)
+ if mo:
+ sectname = mo.group('header')
+ if self.__sections_dict.has_key(sectname):
+ cursectdict = self.__sections_dict[sectname]
+ cursectlist = []
+ self.__sections_list.append((sectname, cursectlist))
+ elif sectname == DEFAULTSECT:
+ cursectdict = self.__defaults
+ cursectlist = None
+ else:
+ cursectdict = {}
+ self.__sections_dict[sectname] = cursectdict
+ cursectlist = []
+ self.__sections_list.append((sectname, cursectlist))
+ # So sections can't start with a continuation line
+ optname = None
+ # no section header in the file?
+ elif cursectdict is None:
+ raise MissingSectionHeaderError(fpname, lineno, `line`)
+ # an option line?
+ else:
+ mo = self.OPTCRE.match(line)
+ if mo:
+ optname, vi, optval = mo.group('option', 'vi', 'value')
+ if vi in ('=', ':') and ';' in optval:
+ # ';' is a comment delimiter only if it follows
+ # a spacing character
+ pos = optval.find(';')
+ if pos and optval[pos-1] in string.whitespace:
+ optval = optval[:pos]
+ optval = optval.strip()
+ # allow empty values
+ if optval == '""':
+ optval = ''
+ xform = self.optionxform(optname)
+ cursectdict[xform] = optval
+ if cursectlist is not None:
+ cursectlist.append([xform, optval])
+ else:
+ # a non-fatal parsing error occurred. set up the
+ # exception but keep going. the exception will be
+ # raised at the end of the file and will contain a
+ # list of all bogus lines
+ if not e:
+ e = ParsingError(fpname)
+ e.append(lineno, `line`)
+ # if any parsing errors occurred, raise an exception
+ if e:
+ raise e
+
+# Here we wrap this hacked ConfigParser into something more useful
+# for us.
+
+import os
+
+class Config:
+ def __init__(self):
+ self._config = ConfigParser()
+ conffiles = []
+ conffiles.append("/etc/repsys.conf")
+ repsys_conf = os.environ.get("REPSYS_CONF")
+ if repsys_conf:
+ conffiles.append(repsys_conf)
+ conffiles.append(os.path.expanduser("~/.repsys/config"))
+ for file in conffiles:
+ if os.path.isfile(file):
+ self._config.read(file)
+
+ def sections(self):
+ try:
+ return self._config.sections()
+ except Error:
+ return []
+
+ def options(self, section):
+ try:
+ return self._config.options(section)
+ except Error:
+ return []
+
+ def set(self, section, option, value):
+ return self._config.set(section, option, value)
+
+ def walk(self, section):
+ return self._config.walk(section)
+
+ def get(self, section, option, default=None):
+ try:
+ return self._config.get(section, option)
+ except Error:
+ return default
+
+ def getint(self, section, option, default=None):
+ ret = self.get(section, option, default)
+ if type(ret) == type(""):
+ return int(ret)
+
+ def getbool(self, section, option, default=None):
+ ret = self.get(section, option, default)
+ states = {'1': 1, 'yes': 1, 'true': 1, 'on': 1,
+ '0': 0, 'no': 0, 'false': 0, 'off': 0}
+ if type(ret) == type("") and states.has_key(ret.lower()):
+ return states[ret.lower()]
+ return default
+
+# vim:ts=4:sw=4:et
diff --git a/RepSys/__init__.py b/RepSys/__init__.py
new file mode 100644
index 0000000..c371e7a
--- /dev/null
+++ b/RepSys/__init__.py
@@ -0,0 +1,9 @@
+#!/usr/bin/python
+
+import ConfigParser
+config = ConfigParser.Config()
+del ConfigParser
+
+class Error(Exception): pass
+
+# vim:et:ts=4:sw=4
diff --git a/RepSys/cgi/__init__.py b/RepSys/cgi/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/RepSys/cgi/__init__.py
diff --git a/RepSys/cgi/soapserver.py b/RepSys/cgi/soapserver.py
new file mode 100644
index 0000000..2f6b751
--- /dev/null
+++ b/RepSys/cgi/soapserver.py
@@ -0,0 +1,93 @@
+#!/usr/bin/python
+from RepSys import Error, config
+from RepSys.rpmutil import get_srpm
+from RepSys.cgiutil import CgiError, get_targets
+import sys
+import os
+
+try:
+ import NINZ.dispatch
+except ImportError:
+ NINZ = None
+
+class SoapIface:
+ def author_email(self, author):
+ return config.get("users", author)
+
+ def submit_package(self, packageurl, packagerev, targetname):
+ username = os.environ.get("REMOTE_USER")
+ packager = config.get("users", username)
+ if not packager:
+ raise CgiError, "your email was not found"
+ elif not packagerev:
+ raise CgiError, "no revision provided"
+ elif not targetname:
+ raise CgiError, "no target provided"
+ else:
+ targetname = targetname.lower()
+ for target in get_targets():
+ if target.name.lower() == targetname:
+ break
+ else:
+ raise CgiError, "target not found"
+ try:
+ tmp = int(packagerev)
+ except ValueError:
+ raise CgiError, "invalid revision provided"
+ for allowed in target.allowed:
+ if packageurl.startswith(allowed):
+ break
+ else:
+ raise CgiError, "%s is not allowed for this target" \
+ % packageurl
+ get_srpm(packageurl,
+ revision=packagerev,
+ targetdirs=target.target,
+ packager=packager,
+ revname=1,
+ svnlog=1,
+ scripts=target.scripts)
+ return 1
+
+ def submit_targets(self):
+ return [x.name for x in get_targets()]
+
+TEMPLATE = """\
+Content-type: text/html
+
+<html>
+<head>
+<title>Repository system SOAP server</title>
+</head>
+<body bgcolor="white">
+<br>
+<hr>
+<center>
+<b>%(message)s</b>
+</center>
+<hr>
+</body>
+</html>
+"""
+
+def show(msg="", error=0):
+ if error:
+ msg = '<font color="red">%s</font>' % msg
+ print TEMPLATE % {"message":msg}
+
+def main():
+ if not os.environ.has_key('REQUEST_METHOD'):
+ sys.stderr.write("error: this program is meant to be used as a cgi\n")
+ sys.exit(1)
+ if not NINZ:
+ show("NINZ is not properly installed in this system", error=1)
+ sys.exit(1)
+ username = os.environ.get("REMOTE_USER")
+ method = os.environ.get("REQUEST_METHOD")
+ if not username or method != "POST":
+ show("This is a SOAP interface!", error=1)
+ sys.exit(1)
+
+ NINZ.dispatch.AsCGI(modules=(SoapIface(),))
+
+# vim:et:ts=4:sw=4
diff --git a/RepSys/cgi/submit.py b/RepSys/cgi/submit.py
new file mode 100644
index 0000000..10f7cb2
--- /dev/null
+++ b/RepSys/cgi/submit.py
@@ -0,0 +1,119 @@
+#!/usr/bin/python
+from RepSys import Error, config
+from RepSys.rpmutil import get_srpm
+from RepSys.cgiutil import CgiError, get_targets
+import cgi
+import sys
+import os
+
+TEMPLATE = """\
+<html>
+<head>
+<title>Repository package submission system</title>
+</head>
+<body bgcolor="white">
+<table cellspacing=0 cellpadding=0 border=0 width="100%%">
+ <tr bgcolor="#020264"><td align="left" valign="middle"><img src="http://qa.mandriva.com/mandriva.png" hspace=0 border=0 alt=""></td></tr>
+</table>
+<br>
+<hr>
+<center>
+<b>%(message)s</b>
+<br><br>
+<form method="POST" action="">
+<table><tr><td valign="top">
+ Package URL:<br>
+ <input name="packageurl" size="60" value="svn+ssh://cvs.mandriva.com/svn/mdv/cooker/"><br>
+ <small>Ex. svn+ssh://cvs.mandriva.com/svn/mdv/cooker/pkgname</small><br>
+ </td><td valign="top">
+ Revision:<br>
+ <input name="packagerev" size="10" value=""><br>
+ </td></tr></table>
+ <br>
+ Package target:<br>
+ <select name="target" size=5>
+ %(targetoptions)s
+ </select><br>
+ <br>
+ <input type="submit" value="Submit package">
+</form>
+</center>
+<hr/>
+</body>
+</html>
+"""
+
+def get_targetoptions():
+ s = ""
+ selected = " selected"
+ for target in get_targets():
+ s += '<option value="%s"%s>%s</option>' \
+ % (target.name, selected, target.name)
+ selected = ""
+ return s
+
+def show(msg="", error=0):
+ if error:
+ msg = '<font color="red">%s</font>' % msg
+ print TEMPLATE % {"message":msg, "targetoptions":get_targetoptions()}
+
+def submit_packages(packager):
+ form = cgi.FieldStorage()
+ packageurl = form.getfirst("packageurl", "").strip()
+ packagerev = form.getfirst("packagerev", "").strip()
+ if not packageurl:
+ show()
+ elif not packagerev:
+ raise CgiError, "No revision provided!"
+ else:
+ targetname = form.getfirst("target")
+ if not targetname:
+ raise CgiError, "No target selected!"
+ for target in get_targets():
+ if target.name == targetname:
+ break
+ else:
+ raise CgiError, "Target not found!"
+ try:
+ tmp = int(packagerev)
+ except ValueError:
+ raise CgiError, "Invalid revision provided!"
+ for allowed in target.allowed:
+ if packageurl.startswith(allowed):
+ break
+ else:
+ raise CgiError, "%s is not allowed for this target!" % packageurl
+ get_srpm(packageurl,
+ revision=packagerev,
+ targetdirs=target.target,
+ packager=packager,
+ revname=1,
+ svnlog=1,
+ scripts=target.scripts)
+ show("Package submitted!")
+
+def main():
+ if not os.environ.has_key('REQUEST_METHOD'):
+ sys.stderr.write("error: this program is meant to be used as a cgi\n")
+ sys.exit(1)
+ print "Content-type: text/html\n\n"
+ try:
+ username = os.environ.get("REMOTE_USER")
+ method = os.environ.get("REQUEST_METHOD")
+ if not username or method != "POST":
+ show()
+ else:
+ useremail = config.get("users", username)
+ if not useremail:
+ raise CgiError, \
+ "Your email was not found. Contact the administrator!"
+ submit_packages(useremail)
+ except CgiError, e:
+ show(str(e), error=1)
+ except Error, e:
+ error = str(e)
+ show(error[0].upper()+error[1:], error=1)
+ except:
+ cgi.print_exception()
+
+# vim:et:ts=4:sw=4
diff --git a/RepSys/cgi/xmlrpcserver.py b/RepSys/cgi/xmlrpcserver.py
new file mode 100644
index 0000000..e0851d1
--- /dev/null
+++ b/RepSys/cgi/xmlrpcserver.py
@@ -0,0 +1,111 @@
+#!/usr/bin/python
+from RepSys import Error, config
+from RepSys.rpmutil import get_srpm
+from RepSys.cgiutil import CgiError, get_targets
+import sys
+import os
+
+import xmlrpclib, cgi
+
+class XmlRpcIface:
+ def author_email(self, author):
+ return config.get("users", author)
+
+ def submit_package(self, packageurl, packagerev, targetname):
+ username = os.environ.get("REMOTE_USER")
+ packager = config.get("users", username)
+ if not packager:
+ raise CgiError, "your email was not found"
+ elif not packagerev:
+ raise CgiError, "no revision provided"
+ elif not targetname:
+ raise CgiError, "no target provided"
+ else:
+ targetname = targetname.lower()
+ for target in get_targets():
+ if target.name.lower() == targetname:
+ break
+ else:
+ raise CgiError, "target not found"
+ try:
+ tmp = int(packagerev)
+ except ValueError:
+ raise CgiError, "invalid revision provided"
+ for allowed in target.allowed:
+ if packageurl.startswith(allowed):
+ break
+ else:
+ raise CgiError, "%s is not allowed for this target" \
+ % packageurl
+ get_srpm(packageurl,
+ revision=packagerev,
+ targetdirs=target.target,
+ packager=packager,
+ revname=1,
+ svnlog=1,
+ scripts=target.scripts)
+ return 1
+
+ def submit_targets(self):
+ return [x.name for x in get_targets()]
+
+TEMPLATE = """\
+Content-type: text/html
+
+<html>
+<head>
+<title>Repository system SOAP server</title>
+</head>
+<body bgcolor="white">
+<br>
+<hr>
+<center>
+<b>%(message)s</b>
+</center>
+<hr>
+</body>
+</html>
+"""
+
+def show(msg="", error=0):
+ if error:
+ msg = '<font color="red">%s</font>' % msg
+ print TEMPLATE % {"message":msg}
+
+def main():
+ if not os.environ.has_key('REQUEST_METHOD'):
+ sys.stderr.write("error: this program is meant to be used as a cgi\n")
+ sys.exit(1)
+ username = os.environ.get("REMOTE_USER")
+ method = os.environ.get("REQUEST_METHOD")
+ if not username or method != "POST":
+ show("This is a XMLRPC interface!", error=1)
+ sys.exit(1)
+
+ iface = XmlRpcIface()
+
+ response = ""
+ try:
+ form = cgi.FieldStorage()
+ parms, method = xmlrpclib.loads(form.value)
+ meth = getattr(iface, method)
+ response = (meth(*parms),)
+ except CgiError, e:
+ msg = str(e)
+ try:
+ msg = msg.decode("iso-8859-1")
+ except UnicodeError:
+ pass
+ response = xmlrpclib.Fault(1, msg)
+ except Exception, e:
+ msg = str(e)
+ try:
+ msg = msg.decode("iso-8859-1")
+ except UnicodeError:
+ pass
+ response = xmlrpclib.Fault(1, msg)
+
+ sys.stdout.write("Content-type: text/xml\n\n")
+ sys.stdout.write(xmlrpclib.dumps(response, methodresponse=1))
+
+# vim:et:ts=4:sw=4
diff --git a/RepSys/cgiutil.py b/RepSys/cgiutil.py
new file mode 100644
index 0000000..ebb65c9
--- /dev/null
+++ b/RepSys/cgiutil.py
@@ -0,0 +1,42 @@
+#!/usr/bin/python
+from RepSys import Error, config
+from RepSys.svn import SVN
+import time
+
+class CgiError(Error): pass
+
+class SubmitTarget:
+ def __init__(self):
+ self.name = ""
+ self.target = ""
+ self.allowed = []
+ self.scripts = []
+
+TARGETS = []
+
+def get_targets():
+ global TARGETS
+ if not TARGETS:
+ target = SubmitTarget()
+ targetoptions = {}
+ for option, value in config.walk("submit"):
+ if targetoptions.has_key(option):
+ TARGETS.append(target)
+ target = SubmitTarget()
+ targetoptions = {}
+ targetoptions[option] = 1
+ if option == "name":
+ target.name = value
+ elif option == "target":
+ target.target = value.split()
+ elif option == "allowed":
+ target.allowed = value.split()
+ elif option == "scripts":
+ target.scripts = value.split()
+ else:
+ raise Error, "unknown [submit] option %s" % option
+ if targetoptions:
+ TARGETS.append(target)
+ return TARGETS
+
+# vim:et:ts=4:sw=4
diff --git a/RepSys/command.py b/RepSys/command.py
new file mode 100644
index 0000000..8029e08
--- /dev/null
+++ b/RepSys/command.py
@@ -0,0 +1,53 @@
+#!/usr/bin/python
+from RepSys import Error, config
+import sys, os, urllib
+import optparse
+
+__all__ = ["OptionParser", "do_command", "default_parent"]
+
+class CapitalizeHelpFormatter(optparse.IndentedHelpFormatter):
+
+ def format_usage(self, usage):
+ return optparse.IndentedHelpFormatter \
+ .format_usage(self, usage).capitalize()
+
+ def format_heading(self, heading):
+ return optparse.IndentedHelpFormatter \
+ .format_heading(self, heading).capitalize()
+
+class OptionParser(optparse.OptionParser):
+
+ def __init__(self, usage=None, help=None, **kwargs):
+ if not "formatter" in kwargs:
+ kwargs["formatter"] = CapitalizeHelpFormatter()
+ optparse.OptionParser.__init__(self, usage, **kwargs)
+ self._overload_help = help
+
+ def format_help(self, formatter=None):
+ if self._overload_help:
+ return self._overload_help
+ else:
+ return optparse.OptionParser.format_help(self, formatter)
+
+ def error(self, msg):
+ raise Error, msg
+
+def do_command(parse_options_func, main_func):
+ try:
+ opt = parse_options_func()
+ main_func(**opt.__dict__)
+ except Error, e:
+ sys.stderr.write("error: %s\n" % str(e))
+ sys.exit(1)
+
+def default_parent(url):
+ if url.find("://") == -1:
+ default_parent = config.get("global", "default_parent")
+ if not default_parent:
+ raise Error, "received a relative url, " \
+ "but default_parent was not setup"
+ type, rest = urllib.splittype(default_parent)
+ url = type+':'+os.path.normpath(rest+'/'+url)
+ return url
+
+# vim:et:ts=4:sw=4
diff --git a/RepSys/commands/__init__.py b/RepSys/commands/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/RepSys/commands/__init__.py
diff --git a/RepSys/commands/authoremail.py b/RepSys/commands/authoremail.py
new file mode 100644
index 0000000..aee7b58
--- /dev/null
+++ b/RepSys/commands/authoremail.py
@@ -0,0 +1,34 @@
+#!/usr/bin/python
+from RepSys import Error, config
+from RepSys.command import *
+import sys
+import getopt
+
+HELP = """\
+Usage: repsys authoremail [OPTIONS] AUTHOR
+
+Options:
+ -h Show this message
+
+Examples:
+ repsys authoremail john
+"""
+
+def parse_options():
+ parser = OptionParser(help=HELP)
+ opts, args = parser.parse_args()
+ if len(args) != 1:
+ raise Error, "invalid arguments"
+ opts.author = args[0]
+ return opts
+
+def print_author_email(author):
+ email = config.get("users", author)
+ if not email:
+ raise Error, "author not found"
+ print email
+
+def main():
+ do_command(parse_options, print_author_email)
+
+# vim:et:ts=4:sw=4
diff --git a/RepSys/commands/changed.py b/RepSys/commands/changed.py
new file mode 100644
index 0000000..c99f3ae
--- /dev/null
+++ b/RepSys/commands/changed.py
@@ -0,0 +1,35 @@
+#!/usr/bin/python
+from RepSys import Error
+from RepSys.command import *
+from RepSys.rpmutil import check_changed
+import getopt
+import sys
+
+HELP = """\
+Usage: repsys changed [OPTIONS] URL
+
+Options:
+ -a Check all packages in given URL
+ -s Show differences
+ -h Show this message
+
+Examples:
+ repsys changed http://repos/svn/cnc/snapshot/foo
+ repsys changed -a http://repos/svn/cnc/snapshot
+"""
+
+def parse_options():
+ parser = OptionParser(help=HELP)
+ parser.add_option("-a", dest="all", action="store_true")
+ parser.add_option("-s", dest="show", action="store_true")
+ opts, args = parser.parse_args()
+ if len(args) != 1:
+ raise Error, "invalid arguments"
+ opts.url = default_parent(args[0])
+ opts.verbose = 1 # Unconfigurable
+ return opts
+
+def main():
+ do_command(parse_options, check_changed)
+
+# vim:et:ts=4:sw=4
diff --git a/RepSys/commands/co.py b/RepSys/commands/co.py
new file mode 100644
index 0000000..0c9d2dc
--- /dev/null
+++ b/RepSys/commands/co.py
@@ -0,0 +1,36 @@
+#!/usr/bin/python
+from RepSys import Error
+from RepSys.command import *
+from RepSys.rpmutil import checkout
+import getopt
+import sys
+
+HELP = """\
+Usage: repsys co [OPTIONS] URL [LOCALPATH]
+
+Options:
+ -r REV Revision to checkout
+ -h Show this message
+
+Examples:
+ repsys co http://repos/svn/cnc/snapshot/foo
+ repsys co http://repos/svn/cnc/snapshot/foo foo-pkg
+"""
+
+def parse_options():
+ parser = OptionParser(help=HELP)
+ parser.add_option("-r", dest="revision")
+ opts, args = parser.parse_args()
+ if len(args) not in (1, 2):
+ raise Error, "invalid arguments"
+ opts.url = default_parent(args[0])
+ if len(args) == 2:
+ opts.path = args[1]
+ else:
+ opts.path = None
+ return opts
+
+def main():
+ do_command(parse_options, checkout)
+
+# vim:et:ts=4:sw=4
diff --git a/RepSys/commands/create.py b/RepSys/commands/create.py
new file mode 100644
index 0000000..56af1ef
--- /dev/null
+++ b/RepSys/commands/create.py
@@ -0,0 +1,30 @@
+#!/usr/bin/python
+from RepSys import Error
+from RepSys.command import *
+from RepSys.rpmutil import create_package
+import getopt
+import sys
+
+HELP = """\
+Usage: repsys create [OPTIONS] URL
+
+Options:
+ -h Show this message
+
+Examples:
+ repsys create http://repos/svn/cnc/snapshot/newpkg
+"""
+
+def parse_options():
+ parser = OptionParser(help=HELP)
+ opts, args = parser.parse_args()
+ if len(args) != 1:
+ raise Error, "invalid arguments"
+ opts.pkgdirurl = default_parent(args[0])
+ opts.verbose = 1 # Unconfigurable
+ return opts
+
+def main():
+ do_command(parse_options, create_package)
+
+# vim:et:ts=4:sw=4
diff --git a/RepSys/commands/editlog.py b/RepSys/commands/editlog.py
new file mode 100644
index 0000000..1962ed8
--- /dev/null
+++ b/RepSys/commands/editlog.py
@@ -0,0 +1,38 @@
+#!/usr/bin/python
+from RepSys import Error
+from RepSys.command import *
+from RepSys.svn import SVN
+import re
+
+HELP = """\
+Usage: repsys editlog [OPTIONS] [URL] REVISION
+
+Options:
+ -h Show this message
+
+Examples:
+ repsys editlog 14800
+ repsys editlog https://repos/svn/cnc/snapshot 14800
+"""
+
+def parse_options():
+ parser = OptionParser(help=HELP)
+ opts, args = parser.parse_args()
+ if len(args) == 2:
+ pkgdirurl, revision = args
+ elif len(args) == 1:
+ pkgdirurl, revision = "", args[0]
+ else:
+ raise Error, "invalid arguments"
+ opts.pkgdirurl = default_parent(pkgdirurl)
+ opts.revision = re.compile(r".*?(\d+).*").sub(r"\1", revision)
+ return opts
+
+def editlog(pkgdirurl, revision):
+ svn = SVN()
+ svn.propedit("svn:log", pkgdirurl, rev=revision)
+
+def main():
+ do_command(parse_options, editlog)
+
+# vim:et:ts=4:sw=4
diff --git a/RepSys/commands/getspec.py b/RepSys/commands/getspec.py
new file mode 100644
index 0000000..1079a81
--- /dev/null
+++ b/RepSys/commands/getspec.py
@@ -0,0 +1,31 @@
+#!/usr/bin/python
+from RepSys import Error
+from RepSys.command import *
+from RepSys.rpmutil import get_spec
+import getopt
+import sys
+
+HELP = """\
+Usage: repsys getspec [OPTIONS] REPPKGURL
+
+Options:
+ -t DIR Use DIR as target for spec file (default is ".")
+ -h Show this message
+
+Examples:
+ repsys getspec http://repos/svn/cnc/snapshot/foo
+"""
+
+def parse_options():
+ parser = OptionParser(help=HELP)
+ parser.add_option("-t", dest="targetdir", default=".")
+ opts, args = parser.parse_args()
+ if len(args) != 1:
+ raise Error, "invalid arguments"
+ opts.pkgdirurl = default_parent(args[0])
+ return opts
+
+def main():
+ do_command(parse_options, get_spec)
+
+# vim:et:ts=4:sw=4
diff --git a/RepSys/commands/getsrpm.py b/RepSys/commands/getsrpm.py
new file mode 100644
index 0000000..f2def54
--- /dev/null
+++ b/RepSys/commands/getsrpm.py
@@ -0,0 +1,76 @@
+#!/usr/bin/python
+#
+# This program will extract given version/revision of the named package
+# from the Conectiva Linux repository system.
+#
+from RepSys import Error, config
+from RepSys.command import *
+from RepSys.rpmutil import get_srpm
+import tempfile
+import shutil
+import getopt
+import glob
+import sys
+import os
+
+HELP = """\
+Usage: repsys getsrpm [OPTIONS] REPPKGURL
+
+Options:
+ -c Use files in current/ directory (default)
+ -p Use files in pristine/ directory
+ -v VER Use files from the version specified by VER (e.g. 2.2.1-2cl)
+ -r REV Use files from current directory, in revision REV (e.g. 1001)
+ -t DIR Put SRPM file in directory DIR when done (default is ".")
+ -P USER Define the RPM packager inforamtion to USER
+ -s FILE Run script with "FILE TOPDIR SPECFILE" command
+ -n Rename the package to include the revision number
+ -l Use subversion log to build rpm %changelog
+ -h Show this message
+
+Examples:
+ repsys getsrpm http://foo.bar/svn/cnc/snapshot/python
+ repsys getsrpm -p http://foo.bar/svn/cnc/releases/8cl/python
+ repsys getsrpm -r 1001 file:///svn/cnc/snapshot/python
+"""
+
+def mode_callback(option, opt, val, parser, mode):
+ opts = parser.values
+ opts.mode = mode
+ if mode == "version":
+ try:
+ opts.version, opts.release = val.split("-", 1)
+ except ValueError:
+ raise Error, "wrong version, use something like 2.2-1cl"
+ elif mode == "revision":
+ opts.revision = val
+
+def parse_options():
+ parser = OptionParser(help=HELP)
+ parser.defaults["mode"] = "current"
+ parser.defaults["version"] = None
+ parser.defaults["release"] = None
+ parser.defaults["revision"] = None
+ parser.add_option("-c", action="callback", callback=mode_callback,
+ callback_kwargs={"mode": "current"})
+ parser.add_option("-p", action="callback", callback=mode_callback,
+ callback_kwargs={"mode": "pristine"})
+ parser.add_option("-r", action="callback", callback=mode_callback,
+ callback_kwargs={"mode": "revision"})
+ parser.add_option("-v", action="callback", callback=mode_callback,
+ callback_kwargs={"mode": "version"})
+ parser.add_option("-t", dest="targetdirs", action="append", default=[])
+ parser.add_option("-s", dest="scripts", action="append", default=[])
+ parser.add_option("-P", dest="packager", default="")
+ parser.add_option("-n", dest="revname", action="store_true")
+ parser.add_option("-l", dest="svnlog", action="store_true")
+ opts, args = parser.parse_args()
+ if len(args) != 1:
+ raise Error, "invalid arguments"
+ opts.pkgdirurl = default_parent(args[0])
+ return opts
+
+def main():
+ do_command(parse_options, get_srpm)
+
+# vim:et:ts=4:sw=4
diff --git a/RepSys/commands/markrelease.py b/RepSys/commands/markrelease.py
new file mode 100644
index 0000000..054fff0
--- /dev/null
+++ b/RepSys/commands/markrelease.py
@@ -0,0 +1,96 @@
+#!/usr/bin/python
+#
+# This program will append a release to the Conectiva Linux package
+# repository system. It's meant to be a startup system to include
+# pre-packaged SRPMS in the repository, thus, you should not commit
+# packages over an ongoing package structure (with changes in current/
+# directory and etc). Also, notice that packages must be included in
+# cronological order.
+#
+from RepSys import Error
+from RepSys.command import *
+from RepSys.rpm import SRPM
+from RepSys.rpmutil import mark_release
+from RepSys.util import get_auth
+import getopt
+import sys
+import os
+
+HELP = """\
+*** WARNING --- You probably SHOULD NOT use this program! --- WARNING ***
+
+Usage: repsys markrelease [OPTIONS] REPPKGURL
+
+Options:
+ -f FILE Try to extract information from given file
+ -r REV Revision which will be used to make the release copy tag
+ -v VER Version which will be used to make the release copy tag
+ -n Append package name to provided URL
+ -h Show this message
+
+Examples:
+ repsys markrelease -r 68 -v 1.0-1 file://svn/cnc/snapshot/foo
+ repsys markrelease -f @68:foo-1.0-1.src.rpm file://svn/cnc/snapshot/foo
+ repsys markrelease -r 68 -f foo-1.0.src.rpm file://svn/cnc/snapshot/foo
+"""
+
+def version_callback(option, opt, val, parser):
+ opts = parser.values
+ try:
+ opts.version, opts.release = val.split("-", 1)
+ except ValueError:
+ raise Error, "wrong version, use something like 1:2.2-1cl"
+
+def parse_options():
+ parser = OptionParser(help=HELP)
+ parser.defaults["version"] = None
+ parser.defaults["release"] = None
+ parser.add_option("-v", action="callback", callback=version_callback)
+ parser.add_option("-r", dest="revision")
+ parser.add_option("-f", dest="filename")
+ parser.add_option("-n", dest="appendname", action="store_true")
+ opts, args = parser.parse_args()
+
+ if len(args) != 1:
+ raise Error, "invalid arguments"
+
+ opts.pkgdirurl = default_parent(args[0])
+
+ filename = opts.filename
+ appendname = opts.appendname
+ del opts.filename, opts.appendname
+
+ if filename:
+ if not os.path.isfile(filename):
+ raise Error, "file not found: "+filename
+ if not opts.revision:
+ basename = os.path.basename(filename)
+ end = basename.find(":")
+ if basename[0] != "@" or end == -1:
+ raise Error, "couldn't guess revision from filename"
+ opts.revision = basename[1:end]
+ srpm = None
+ if not opts.version:
+ srpm = SRPM(filename)
+ if srpm.epoch:
+ opts.version = "%s:%s" % (srpm.epoch, srpm.version)
+ else:
+ opts.version = srpm.version
+ opts.release = srpm.release
+ if appendname:
+ if not srpm:
+ srpm = SRPM(filename)
+ opts.pkgdirurl = "/".join([opts.pkgdirurl, srpm.name])
+ elif appendname:
+ raise Error, "option -n requires option -f"
+ elif not opts.revision:
+ raise Error, "no revision provided"
+ elif not opts.version:
+ raise Error, "no version provided"
+ get_auth()
+ return opts
+
+def main():
+ do_command(parse_options, mark_release)
+
+# vim:et:ts=4:sw=4
diff --git a/RepSys/commands/patchspec.py b/RepSys/commands/patchspec.py
new file mode 100644
index 0000000..155ff4f
--- /dev/null
+++ b/RepSys/commands/patchspec.py
@@ -0,0 +1,35 @@
+#!/usr/bin/python
+#
+# This program will try to patch a spec file from a given package url.
+#
+from RepSys import Error
+from RepSys.rpmutil import patch_spec
+from RepSys.command import *
+import getopt
+import sys
+
+HELP = """\
+Usage: repsys patchspec [OPTIONS] REPPKGURL PATCHFILE
+
+Options:
+ -l LOG Use LOG as log message
+ -h Show this message
+
+Examples:
+ repsys patchspec http://repos/svn/cnc/snapshot/foo
+"""
+
+def parse_options():
+ parser = OptionParser(help=HELP)
+ parser.add_option("-l", dest="log", default="")
+ opts, args = parser.parse_args()
+ if len(args) != 2:
+ raise Error, "invalid arguments"
+ opts.pkgdirurl = default_parent(args[0])
+ opts.patchfile = args[1]
+ return opts
+
+def main():
+ do_command(parse_options, patch_spec)
+
+# vim:et:ts=4:sw=4
diff --git a/RepSys/commands/putsrpm.py b/RepSys/commands/putsrpm.py
new file mode 100644
index 0000000..21ad234
--- /dev/null
+++ b/RepSys/commands/putsrpm.py
@@ -0,0 +1,61 @@
+#!/usr/bin/python
+#
+# This program will append a release to the Conectiva Linux package
+# repository system. It's meant to be a startup system to include
+# pre-packaged SRPMS in the repository, thus, you should not commit
+# packages over an ongoing package structure (with changes in current/
+# directory and etc). Also, notice that packages must be included in
+# cronological order.
+#
+from RepSys import Error
+from RepSys.command import *
+from RepSys.rpmutil import put_srpm
+import getopt
+import sys, os
+
+HELP = """\
+*** WARNING --- You probably SHOULD NOT use this program! --- WARNING ***
+
+Usage: repsys putsrpm [OPTIONS] REPPKGURL
+
+Options:
+ -n Append package name to provided URL
+ -l LOG Use log when commiting changes
+ -h Show this message
+
+Examples:
+ repsys putsrpm file://svn/cnc/snapshot/foo /cnc/d/SRPMS/foo-1.0.src.rpm
+"""
+
+def parse_options():
+ parser = OptionParser(help=HELP)
+ parser.add_option("-l", dest="log", default="")
+ parser.add_option("-n", dest="appendname", action="store_true")
+ opts, args = parser.parse_args()
+ if len(args) != 2:
+ raise Error, "invalid arguments"
+ opts.pkgdirurl = default_parent(args[0])
+ opts.srpmfile = args[1]
+ return opts
+
+def put_srpm_cmd(pkgdirurl, srpmfile, appendname=0, log=""):
+ if os.path.isdir(srpmfile):
+ dir = srpmfile
+ for entry in os.listdir(dir):
+ if entry[-8:] == ".src.rpm":
+ sys.stderr.write("Putting %s... " % entry)
+ sys.stderr.flush()
+ entrypath = os.path.join(dir, entry)
+ try:
+ put_srpm(pkgdirurl, entrypath, appendname, log)
+ sys.stderr.write("done\n")
+ except Error, e:
+ sys.stderr.write("error: %s\n" % str(e))
+ else:
+ put_srpm(pkgdirurl, srpmfile, appendname, log)
+
+
+def main():
+ do_command(parse_options, put_srpm_cmd)
+
+# vim:et:ts=4:sw=4
diff --git a/RepSys/commands/rpmlog.py b/RepSys/commands/rpmlog.py
new file mode 100644
index 0000000..24eddfe
--- /dev/null
+++ b/RepSys/commands/rpmlog.py
@@ -0,0 +1,40 @@
+#!/usr/bin/python
+#
+# This program will convert the output of "svn log" to be suitable
+# for usage in an rpm %changelog session.
+#
+from RepSys import Error
+from RepSys.command import *
+from RepSys.log import svn2rpm
+import getopt
+import sys
+
+HELP = """\
+Usage: repsys rpmlog [OPTIONS] REPPKGDIRURL
+
+Options:
+ -r REV Collect logs from given revision to revision 0
+ -n NUM Output only last NUM entries
+ -h Show this message
+
+Examples:
+ repsys rpmlog https://repos/snapshot/python
+"""
+
+def parse_options():
+ parser = OptionParser(help=HELP)
+ parser.add_option("-r", dest="revision")
+ parser.add_option("-n", dest="size", type="int")
+ opts, args = parser.parse_args()
+ if len(args) != 1:
+ raise Error, "invalid arguments"
+ opts.pkgdirurl = default_parent(args[0])
+ return opts
+
+def rpmlog(pkgdirurl, revision, size):
+ sys.stdout.write(svn2rpm(pkgdirurl, revision, size))
+
+def main():
+ do_command(parse_options, rpmlog)
+
+# vim:sw=4:ts=4:et
diff --git a/RepSys/commands/submit.py b/RepSys/commands/submit.py
new file mode 100644
index 0000000..ff442c1
--- /dev/null
+++ b/RepSys/commands/submit.py
@@ -0,0 +1,104 @@
+#!/usr/bin/python
+from RepSys import Error, config
+from RepSys.command import *
+from RepSys.rpmutil import get_spec, get_submit_info
+from RepSys.util import get_auth
+import urllib
+import getopt
+import sys
+import re
+
+#try:
+# import NINZ.client
+#except ImportError:
+# NINZ = None
+
+import xmlrpclib
+
+HELP = """\
+Usage: repsys submit [OPTIONS] [URL [REVISION]]
+
+Options:
+ -t TARGET Submit given package URL to given target
+ -l Just list available targets
+ -h Show this message
+
+Examples:
+ repsys submit
+ repsys submit foo 14800
+ repsys submit https://repos/svn/cnc/snapshot/foo 14800
+ repsys submit -l https://repos
+"""
+
+def parse_options():
+ parser = OptionParser(help=HELP)
+ parser.defaults["revision"] = ""
+ parser.add_option("-t", dest="target", default="Snapshot")
+ parser.add_option("-l", dest="list", action="store_true")
+ opts, args = parser.parse_args()
+ if not args:
+ name, rev = get_submit_info(".")
+ try:
+ yn = raw_input("Submit '%s', revision %d (y/N)? " % (name, rev))
+ except KeyboardInterrupt:
+ yn = "n"
+ if yn.lower() in ("y", "yes"):
+ args = name, str(rev)
+ else:
+ print "Cancelled."
+ sys.exit(1)
+ elif len(args) > 2:
+ raise Error, "invalid arguments"
+ opts.pkgdirurl = default_parent(args[0])
+ if len(args) == 2:
+ opts.revision = re.compile(r".*?(\d+).*").sub(r"\1", args[1])
+ elif not opts.list:
+ raise Error, "provide -l or a revision number"
+ return opts
+
+def submit(pkgdirurl, revision, target, list=0):
+ #if not NINZ:
+ # raise Error, "you must have NINZ installed to use this command"
+ type, rest = urllib.splittype(pkgdirurl)
+ host, path = urllib.splithost(rest)
+ user, host = urllib.splituser(host)
+ host, port = urllib.splitport(host)
+ if type != "https":
+ raise Error, "you must use https:// urls"
+ if user:
+ user, passwd = urllib.splitpasswd(user)
+ if passwd:
+ raise Error, "do not use a password in your command line"
+ user, passwd = get_auth(username=user)
+ #soap = NINZ.client.Binding(host=host,
+ # url="https://%s/scripts/cnc/soap" % host,
+ # ssl=1,
+ # auth=(NINZ.client.AUTH.httpbasic,
+ # user, passwd))
+ if port:
+ port = ":"+port
+ else:
+ port = ""
+ iface = xmlrpclib.ServerProxy("https://%s:%s@%s%s/scripts/cnc/xmlrpc"
+ % (user, passwd, host, port))
+ try:
+ if list:
+ targets = iface.submit_targets()
+ if not targets:
+ raise Error, "no targets available"
+ sys.stdout.writelines(['"%s"\n' % x for x in targets])
+ else:
+ iface.submit_package(pkgdirurl, revision, target)
+ print "Package submitted!"
+ #except NINZ.client.SoapError, e:
+ except xmlrpclib.ProtocolError, e:
+ raise Error, "remote error: "+str(e.errmsg)
+ except xmlrpclib.Fault, e:
+ raise Error, "remote error: "+str(e.faultString)
+ except xmlrpclib.Error, e:
+ raise Error, "remote error: "+str(e)
+
+def main():
+ do_command(parse_options, submit)
+
+# vim:et:ts=4:sw=4
diff --git a/RepSys/log.py b/RepSys/log.py
new file mode 100644
index 0000000..fa007fa
--- /dev/null
+++ b/RepSys/log.py
@@ -0,0 +1,100 @@
+#!/usr/bin/python
+from RepSys import Error, config
+from RepSys.svn import SVN
+import tempfile
+import shutil
+import time
+import os
+
+def svn2rpm(pkgdirurl, rev=None, size=None):
+ concat = config.get("log", "concat", "").split()
+ svn = SVN()
+ log = svn.log(os.path.join(pkgdirurl, "current"), start=rev)
+ if (size is not None):
+ log = log[:size]
+ rpmlog = []
+ lastauthor = None
+ for logentry in log:
+ entryheader = []
+ if lastauthor != logentry.author or \
+ not (logentry.author in concat or "*" in concat):
+ entryheader.append(time.strftime("* %a %b %d %Y ", logentry.date))
+ entryheader.append(config.get("users", logentry.author,
+ logentry.author))
+ entryheader.append("\n")
+ entryheader.append(time.strftime("+ %Y-%m-%d %H:%M:%S",
+ logentry.date))
+ entryheader.append(" (%d)" % logentry.revision)
+ if lastauthor:
+ rpmlog.append("")
+ lastauthor = logentry.author
+ entrylines = []
+ first = 1
+ for line in logentry.lines:
+ if line:
+ line = line.replace("%", "%%")
+ if first:
+ first = 0
+ if entryheader:
+ rpmlog.append("".join(entryheader))
+ line = line.lstrip()
+ if line[0] != "-":
+ nextline = "- " + line
+ else:
+ nextline = line
+ elif line[0] != " " and line[0] != "-":
+ nextline = " " + line
+ else:
+ nextline = line
+ if nextline not in entrylines:
+ rpmlog.append(nextline)
+ entrylines.append(nextline)
+ return "\n".join(rpmlog)+"\n"
+
+def specfile_svn2rpm(pkgdirurl, specfile, rev=None, size=None):
+ file = open(specfile)
+ lines = file.readlines()
+ file.close()
+ newlines = []
+ found = 0
+
+ # Strip old changelogs
+ for line in lines:
+ if line.startswith("%changelog"):
+ found = 1
+ elif not found:
+ newlines.append(line)
+ elif line.startswith("%"):
+ found = 0
+ newlines.append(line)
+
+ # Create new changelog
+ newlines.append("\n\n%changelog\n")
+ newlines.append(svn2rpm(pkgdirurl, rev, size))
+
+ # Merge old changelog, if available
+ oldurl = config.get("log", "oldurl")
+ if oldurl:
+ svn = SVN()
+ tmpdir = tempfile.mktemp()
+ try:
+ pkgname = os.path.basename(pkgdirurl)
+ pkgoldurl = os.path.join(oldurl, pkgname)
+ if svn.ls(pkgoldurl, noerror=1):
+ svn.checkout(pkgoldurl, tmpdir, rev=rev)
+ logfile = os.path.join(tmpdir, "log")
+ if os.path.isfile(logfile):
+ file = open(logfile)
+ newlines.append("\n")
+ newlines.append(file.read())
+ file.close()
+ finally:
+ if os.path.isdir(tmpdir):
+ shutil.rmtree(tmpdir)
+
+ # Write new specfile
+ file = open(specfile, "w")
+ file.writelines(newlines)
+ file.close()
+
+# vim:et:ts=4:sw=4
diff --git a/RepSys/pexpect.py b/RepSys/pexpect.py
new file mode 100644
index 0000000..2460052
--- /dev/null
+++ b/RepSys/pexpect.py
@@ -0,0 +1,1050 @@
+"""
+Pexpect is a Python module for spawning child applications;
+controlling them; and responding to expected patterns in their output.
+Pexpect can be used for automating interactive applications such as
+ssh, ftp, passwd, telnet, etc. It can be used to a automate setup scripts
+for duplicating software package installations on different servers.
+It can be used for automated software testing. Pexpect is in the spirit of
+Don Libes' Expect, but Pexpect is pure Python. Other Expect-like
+modules for Python require TCL and Expect or require C extensions to
+be compiled. Pexpect does not use C, Expect, or TCL extensions. It
+should work on any platform that supports the standard Python pty
+module. The Pexpect interface focuses on ease of use so that simple
+tasks are easy.
+
+Pexpect is Open Source, Free, and all Good that stuff.
+License: Python Software Foundation License
+ http://www.opensource.org/licenses/PythonSoftFoundation.html
+
+Noah Spurrier
+
+$Revision$
+$Date$
+"""
+
+
+try:
+ import os, sys
+ import select
+ import string
+ import re
+ import struct
+ import resource
+ from types import *
+ import pty
+ import tty
+ import termios
+ import fcntl
+except ImportError, e:
+ raise ImportError, str(e) + """
+A critical module was not found. Probably this OS does not support it.
+Currently pexpect is intended for UNIX operating systems."""
+
+
+
+__version__ = '0.99'
+__revision__ = '$Revision$'
+__all__ = ['ExceptionPexpect', 'EOF', 'TIMEOUT', 'spawn', 'run',
+ '__version__', '__revision__']
+
+
+
+# Exception classes used by this module.
+class ExceptionPexpect(Exception):
+ """Base class for all exceptions raised by this module."""
+ def __init__(self, value):
+ self.value = value
+ def __str__(self):
+ return `self.value`
+class EOF(ExceptionPexpect):
+ """Raised when EOF is read from a child."""
+class TIMEOUT(ExceptionPexpect):
+ """Raised when a read time exceeds the timeout."""
+##class MAXBUFFER(ExceptionPexpect):
+## """Raised when a scan buffer fills before matching an expected pattern."""
+
+
+
+def run (command, args=[], timeout=30):
+ """This runs a command; waits for it to finish; then returns
+ all output as a string. This is a utility interface around
+ the spawn class.
+ """
+ child = spawn(command, args, timeout)
+ child.expect (EOF)
+ return child.before
+
+class spawn:
+ """This is the main class interface for Pexpect. Use this class to
+ start and control child applications.
+ """
+
+ def __init__(self, command, args=[], timeout=30):
+ """This is the constructor. The command parameter may be a string
+ that includes a command and any arguments to the command. For example:
+ p = pexpect.spawn ('/usr/bin/ftp')
+ p = pexpect.spawn ('/usr/bin/ssh user@example.com')
+ p = pexpect.spawn ('ls -latr /tmp')
+ You may also construct it with a list of arguments like so:
+ p = pexpect.spawn ('/usr/bin/ftp', [])
+ p = pexpect.spawn ('/usr/bin/ssh', ['user@example.com'])
+ p = pexpect.spawn ('ls', ['-latr', '/tmp'])
+ After this the child application will be created and
+ will be ready to talk to. For normal use, see expect() and
+ send() and sendline().
+
+ If the command parameter is an integer AND a valid file descriptor
+ then spawn will talk to the file descriptor instead. This can be
+ used to act expect features to any file descriptor. For example:
+ fd = os.open ('somefile.txt', os.O_RDONLY)
+ s = pexpect.spawn (fd)
+ The original creator of the file descriptor is responsible
+ for closing it. Spawn will not try to close it and spawn will
+ raise an exception if you try to call spawn.close().
+ """
+
+ self.STDIN_FILENO = sys.stdin.fileno()
+ self.STDOUT_FILENO = sys.stdout.fileno()
+ self.STDERR_FILENO = sys.stderr.fileno()
+
+ self.stdin = sys.stdin
+ self.stdout = sys.stdout
+ self.stderr = sys.stderr
+
+ self.timeout = timeout
+ self.child_fd = -1 # initially closed
+ self.__child_fd_owner = None
+ self.exitstatus = None
+ self.pid = None
+ self.log_file = None
+ self.before = None
+ self.after = None
+ self.match = None
+ self.softspace = 0 # File-like object.
+ self.name = '' # File-like object.
+ self.flag_eof = 0
+
+ # NEW -- to support buffering -- the ability to read more than one
+ # byte from a TTY at a time. See setmaxread() method.
+ self.buffer = ''
+ self.maxread = 1 # Maximum to read at a time
+ ### IMPLEMENT THIS FEATURE!!!
+ # anything before maxsearchsize point is preserved, but not searched.
+ #self.maxsearchsize = 1000
+
+ # If command is an int type then it must represent an open file descriptor.
+ if type (command) == type(0):
+ try: # Command is an int, so now check if it is a file descriptor.
+ os.fstat(command)
+ except OSError:
+ raise ExceptionPexpect, 'Command is an int type, yet is not a valid file descriptor.'
+ self.pid = -1
+ self.child_fd = command
+ self.__child_fd_owner = 0 # Sets who is reponsible for the child_fd
+ self.args = None
+ self.command = None
+ self.name = '<file descriptor>'
+ return
+
+ if type (args) != type([]):
+ raise TypeError, 'The second argument, args, must be a list.'
+
+ if args == []:
+ self.args = _split_command_line(command)
+ self.command = self.args[0]
+ else:
+ self.args = args
+ self.args.insert (0, command)
+ self.command = command
+ self.name = '<' + reduce(lambda x, y: x+' '+y, self.args) + '>'
+
+ self.__spawn()
+
+ def __del__(self):
+ """This makes sure that no system resources are left open.
+ Python only garbage collects Python objects. OS file descriptors
+ are not Python objects, so they must be handled explicitly.
+ If the child file descriptor was opened outside of this class
+ (passed to the constructor) then this does not close it.
+ """
+ try: # CNC:2003-09-23
+ if self.__child_fd_owner:
+ self.close()
+ except OSError:
+ pass
+
+ def __spawn(self):
+ """This starts the given command in a child process. This does
+ all the fork/exec type of stuff for a pty. This is called by
+ __init__. The args parameter is a list, command is a string.
+ """
+ # The pid and child_fd of this object get set by this method.
+ # Note that it is difficult for this method to fail.
+ # You cannot detect if the child process cannot start.
+ # So the only way you can tell if the child process started
+ # or not is to try to read from the file descriptor. If you get
+ # EOF immediately then it means that the child is already dead.
+ # That may not necessarily be bad, because you may haved spawned a child
+ # that performs some task; creates no stdout output; and then dies.
+ # It is a fuzzy edge case. Any child process that you are likely to
+ # want to interact with Pexpect would probably not fall into this
+ # category.
+ # FYI, This essentially does a fork/exec operation.
+
+ assert self.pid == None, 'The pid member is not None.'
+ assert self.command != None, 'The command member is None.'
+
+ if _which(self.command) == None:
+ raise ExceptionPexpect ('The command was not found or was not executable: %s.' % self.command)
+
+ try:
+ self.pid, self.child_fd = pty.fork()
+ except OSError, e:
+ raise ExceptionPexpect('Pexpect: pty.fork() failed: ' + str(e))
+
+ if self.pid == 0: # Child
+ try: # Some platforms do not like setwinsize (Cygwin).
+ self.child_fd = sys.stdout.fileno()
+ self.setwinsize(24, 80)
+ except:
+ pass
+ # Do not allow child to inherit open file descriptors from parent.
+ max_fd = resource.getrlimit(resource.RLIMIT_NOFILE)[0]
+ for i in range (3, max_fd):
+ try:
+ os.close (i)
+ except OSError:
+ pass
+
+ os.environ["LANG"] = "C"
+ os.execvp(self.command, self.args)
+
+ # Parent
+ self.__child_fd_owner = 1
+
+ def fileno (self): # File-like object.
+ """This returns the file descriptor of the pty for the child."""
+ return self.child_fd
+
+ def close (self, wait=1): # File-like object.
+ """ This closes the connection with the child application.
+ It makes no attempt to actually kill the child or wait for its status.
+ If the file descriptor was set by passing a file descriptor
+ to the constructor then this method raises an exception.
+ Note that calling close() more than once is valid.
+ This emulates standard Python behavior with files.
+ If wait is set to True then close will wait
+ for the exit status of the process. Doing a wait is a blocking call,
+ but this usually takes almost no time at all. Generally,
+ you don't have to worry about this. If you are
+ creating lots of children then you usually want to call wait.
+ Only set wait to false if you know the child will
+ continue to run after closing the controlling TTY.
+ Otherwise you will end up with defunct (zombie) processes.
+ """
+ if self.child_fd != -1:
+ if not self.__child_fd_owner:
+ raise ExceptionPexpect ('This file descriptor cannot be closed because it was not created by spawn. The original creator is responsible for closing it.')
+ self.flush()
+ os.close (self.child_fd)
+ if wait:
+ os.waitpid (self.pid, 0)
+ self.child_fd = -1
+ self.__child_fd_owner = None
+
+ def flush (self): # File-like object.
+ """This does nothing. It is here to support the interface for a File-like object.
+ """
+ pass
+
+ def isatty (self): # File-like object.
+ """This returns 1 if the file descriptor is open and
+ connected to a tty(-like) device, else 0.
+ """
+ return os.isatty(self.child_fd)
+
+ def setecho (self, on):
+ """This sets the terminal echo mode on or off."""
+ new = termios.tcgetattr(self.child_fd)
+ if on:
+ new[3] = new[3] | termios.ECHO # lflags
+ else:
+ new[3] = new[3] & ~termios.ECHO # lflags
+ termios.tcsetattr(self.child_fd, termios.TCSADRAIN, new)
+
+ def setlog (self, fileobject):
+ """This sets logging output to go to the given fileobject.
+ Set fileobject to None to stop logging.
+ Example:
+ child = pexpect.spawn('some_command')
+ fout = file('mylog.txt','w')
+ child.setlog (fout)
+ ...
+ """
+ self.log_file = fileobject
+
+ def setmaxread (self, maxread):
+ """This sets the maximum number of bytes to read from a TTY at one time.
+ This is used to change the read buffer size. When a pexpect.spawn
+ object is created the default maxread is 1 (unbuffered).
+ Set this value higher to turn on buffer. This should help performance
+ in cases where large amounts of output are read back from the child.
+ """
+ self.maxread = maxread
+
+ def read_nonblocking (self, size = 1, timeout = None):
+ """
+ This reads at most size characters from the child application.
+ It includes a timeout. If the read does not complete within the
+ timeout period then a TIMEOUT exception is raised.
+ If the end of file is read then an EOF exception will be raised.
+ If a log file was set using setlog() then all data will
+ also be written to the log file.
+
+ Notice that if this method is called with timeout=None
+ then it actually may block.
+
+ This is a non-blocking wrapper around os.read().
+ It uses select.select() to implement a timeout.
+ """
+
+ if self.child_fd == -1:
+ raise ValueError ('I/O operation on closed file')
+
+ # Note that some systems like Solaris don't seem to ever give
+ # an EOF when the child dies. In fact, you can still try to read
+ # from the child_fd -- it will block forever or until TIMEOUT.
+ # For this case, I test isalive() before doing any reading.
+ # If isalive() is false, then I pretend that this is the same as EOF.
+ if not self.isalive():
+ r, w, e = select.select([self.child_fd], [], [], 0)
+ if not r:
+ self.flag_eof = 1
+ raise EOF ('End Of File (EOF) in read(). Braindead platform.')
+
+ r, w, e = select.select([self.child_fd], [], [], timeout)
+ if not r:
+ raise TIMEOUT('Timeout exceeded in read().')
+
+ if self.child_fd in r:
+ try:
+ s = os.read(self.child_fd, size)
+ except OSError, e:
+ self.flag_eof = 1
+ raise EOF('End Of File (EOF) in read(). Exception style platform.')
+ if s == '':
+ self.flag_eof = 1
+ raise EOF('End Of File (EOF) in read(). Empty string style platform.')
+
+ if self.log_file != None:
+ self.log_file.write (s)
+ self.log_file.flush()
+
+ return s
+
+ raise ExceptionPexpect('Reached an unexpected state in read().')
+
+ def read (self, size = -1): # File-like object.
+ """This reads at most size bytes from the file
+ (less if the read hits EOF before obtaining size bytes).
+ If the size argument is negative or omitted,
+ read all data until EOF is reached.
+ The bytes are returned as a string object.
+ An empty string is returned when EOF is encountered immediately.
+ """
+ if size == 0:
+ return ''
+ if size < 0:
+ self.expect (EOF)
+ return self.before
+
+ # I could have done this more directly by not using expect(), but
+ # I deliberately decided to couple read() to expect() so that
+ # I would catch any bugs early and ensure consistant behavior.
+ # It's a little less efficient, but there is less for me to
+ # worry about if I have to later modify read() or expect().
+ cre = re.compile('.{%d}' % size, re.DOTALL)
+ index = self.expect ([cre, EOF])
+ if index == 0:
+ return self.after ### self.before should be ''. Should I assert this?
+ return self.before
+
+ def readline (self, size = -1): # File-like object.
+ """This reads and returns one entire line. A trailing newline is kept in
+ the string, but may be absent when a file ends with an incomplete line.
+ Note: This readline() looks for a \r\n pair even on UNIX because this is
+ what the pseudo tty device returns. So contrary to what you may be used to
+ you will get a newline as \r\n.
+ An empty string is returned when EOF is hit immediately.
+ Currently, the size agument is mostly ignored, so this behavior is not
+ standard for a file-like object.
+ """
+ if size == 0:
+ return ''
+ index = self.expect (['\r\n', EOF])
+ if index == 0:
+ return self.before + '\r\n'
+ else:
+ return self.before
+
+ def __iter__ (self):
+ """This is to support interators over a file-like object.
+ """
+ return self
+ def next (self):
+ """This is to support iterators over a file-like object.
+ """
+ result = self.readline()
+ if result == "":
+ raise StopIteration
+ return result
+
+ def readlines (self, sizehint = -1): # File-like object.
+ """This reads until EOF using readline() and returns a list containing
+ the lines thus read. The optional sizehint argument is ignored.
+ """
+ lines = []
+ while 1:
+ line = self.readline()
+ if not line:
+ break
+ lines.append(line)
+ return lines
+
+ def write(self, str): # File-like object.
+ """This is similar to send() except that there is no return value.
+ """
+ self.send (str)
+
+ def writelines (self, sequence): # File-like object.
+ """This calls write() for each element in the sequence.
+ The sequence can be any iterable object producing strings,
+ typically a list of strings. This does not add line separators
+ There is no return value.
+ """
+ for str in sequence:
+ self.write (str)
+
+ def send(self, str):
+ """This sends a string to the child process.
+ This returns the number of bytes written.
+ """
+ return os.write(self.child_fd, str)
+
+ def sendline(self, str=''):
+ """This is like send(), but it adds a line feed (os.linesep).
+ This returns the number of bytes written.
+ """
+ n = self.send(str)
+ n = n + self.send (os.linesep)
+ return n
+
+ def sendeof(self):
+ """This sends an EOF to the child.
+ This sends a character which causes the pending parent output
+ buffer to be sent to the waiting child program without
+ waiting for end-of-line. If it is the first character of the
+ line, the read() in the user program returns 0, which
+ signifies end-of-file. This means to work as expected
+ a sendeof() has to be called at the begining of a line.
+ This method does not send a newline. It is the responsibility
+ of the caller to ensure the eof is sent at the beginning of a line.
+ """
+ ### Hmmm... how do I send an EOF?
+ ###C if ((m = write(pty, *buf, p - *buf)) < 0)
+ ###C return (errno == EWOULDBLOCK) ? n : -1;
+ fd = sys.stdin.fileno()
+ old = termios.tcgetattr(fd) # remember current state
+ new = termios.tcgetattr(fd)
+ new[3] = new[3] | termios.ICANON # lflags
+ # use try/finally to ensure state gets restored
+ try:
+ # EOF is recognized when ICANON is set, so make sure it is set.
+ termios.tcsetattr(fd, termios.TCSADRAIN, new)
+ os.write (self.child_fd, '%c' % termios.CEOF)
+ finally:
+ termios.tcsetattr(fd, termios.TCSADRAIN, old) # restore state
+
+ def eof (self):
+ """This returns 1 if the EOF exception was raised at some point.
+ """
+ return self.flag_eof
+
+ def isalive(self):
+ """This tests if the child process is running or not.
+ This returns 1 if the child process appears to be running or 0 if not.
+ This also sets the exitstatus attribute.
+ It can take literally SECONDS for Solaris to return the right status.
+ This is the most wiggly part of Pexpect, but I think I've almost got
+ it nailed down.
+ """
+ # I can't use signals. Signals on UNIX suck and they
+ # mess up Python pipes (setting SIGCHLD to SIGIGNORE).
+
+ # If this class was created from an existing file descriptor then
+ # I just check to see if the file descriptor is still valid.
+ if self.pid == -1 and not self.__child_fd_owner:
+ try:
+ os.fstat(self.child_fd)
+ return 1
+ except:
+ return 0
+
+ try:
+ pid, status = os.waitpid(self.pid, os.WNOHANG)
+ except OSError:
+ return 0
+
+ # I have to do this twice for Solaris.
+ # I can't even believe that I figured this out...
+ if pid == 0 and status == 0:
+ try:
+ pid, status = os.waitpid(self.pid, os.WNOHANG)
+ #print 'Solaris sucks'
+ except OSError: # This is crufty. When does this happen?
+ return 0
+ # If pid and status is still 0 after two calls to waitpid() then
+ # the process really is alive. This seems to work on all platforms.
+ if pid == 0 and status == 0:
+ return 1
+
+ # I do not OR this together because I want hooks for debugging.
+ if os.WIFEXITED (status):
+ self.exitstatus = os.WEXITSTATUS(status)
+ return 0
+ elif os.WIFSTOPPED (status):
+ return 0
+ elif os.WIFSIGNALED (status):
+ return 0
+ else:
+ return 0 # Can I ever get here?
+
+ def kill(self, sig):
+ """This sends the given signal to the child application.
+ In keeping with UNIX tradition it has a misleading name.
+ It does not necessarily kill the child unless
+ you send the right signal.
+ """
+ # Same as os.kill, but the pid is given for you.
+ if self.isalive():
+ os.kill(self.pid, sig)
+
+ def compile_pattern_list(self, patterns):
+ """This compiles a pattern-string or a list of pattern-strings.
+ Patterns must be a StringType, EOF, TIMEOUT, SRE_Pattern, or
+ a list of those.
+
+ This is used by expect() when calling expect_list().
+ Thus expect() is nothing more than::
+ cpl = self.compile_pattern_list(pl)
+ return self.expect_list(clp, timeout)
+
+ If you are using expect() within a loop it may be more
+ efficient to compile the patterns first and then call expect_list().
+ This avoid calls in a loop to compile_pattern_list():
+ cpl = self.compile_pattern_list(my_pattern)
+ while some_condition:
+ ...
+ i = self.expect_list(clp, timeout)
+ ...
+ """
+ if type(patterns) is not ListType:
+ patterns = [patterns]
+
+ compiled_pattern_list = []
+ for p in patterns:
+ if type(p) is StringType:
+ compiled_pattern_list.append(re.compile(p, re.DOTALL))
+ elif p is EOF:
+ compiled_pattern_list.append(EOF)
+ elif p is TIMEOUT:
+ compiled_pattern_list.append(TIMEOUT)
+ elif type(p) is type(re.compile('')):
+ compiled_pattern_list.append(p)
+ else:
+ raise TypeError, 'Argument must be one of StringType, EOF, TIMEOUT, SRE_Pattern, or a list of those type. %s' % str(type(p))
+
+ return compiled_pattern_list
+
+ def expect(self, pattern, timeout = -1):
+ """This seeks through the stream until a pattern is matched.
+ The pattern is overloaded and may take several types including a list.
+ The pattern can be a StringType, EOF, a compiled re, or
+ a list of those types. Strings will be compiled to re types.
+ This returns the index into the pattern list. If the pattern was
+ not a list this returns index 0 on a successful match.
+ This may raise exceptions for EOF or TIMEOUT.
+ To avoid the EOF or TIMEOUT exceptions add EOF or TIMEOUT to
+ the pattern list.
+
+ After a match is found the instance attributes
+ 'before', 'after' and 'match' will be set.
+ You can see all the data read before the match in 'before'.
+ You can see the data that was matched in 'after'.
+ The re.MatchObject used in the re match will be in 'match'.
+ If an error occured then 'before' will be set to all the
+ data read so far and 'after' and 'match' will be None.
+
+ If timeout is -1 then timeout will be set to the self.timeout value.
+
+ Note: A list entry may be EOF or TIMEOUT instead of a string.
+ This will catch these exceptions and return the index
+ of the list entry instead of raising the exception.
+ The attribute 'after' will be set to the exception type.
+ The attribute 'match' will be None.
+ This allows you to write code like this:
+ index = p.expect (['good', 'bad', pexpect.EOF, pexpect.TIMEOUT])
+ if index == 0:
+ do_something()
+ elif index == 1:
+ do_something_else()
+ elif index == 2:
+ do_some_other_thing()
+ elif index == 3:
+ do_something_completely_different()
+ instead of code like this:
+ try:
+ index = p.expect (['good', 'bad'])
+ if index == 0:
+ do_something()
+ elif index == 1:
+ do_something_else()
+ except EOF:
+ do_some_other_thing()
+ except TIMEOUT:
+ do_something_completely_different()
+ These two forms are equivalent. It all depends on what you want.
+ You can also just expect the EOF if you are waiting for all output
+ of a child to finish. For example:
+ p = pexpect.spawn('/bin/ls')
+ p.expect (pexpect.EOF)
+ print p.before
+ """
+ compiled_pattern_list = self.compile_pattern_list(pattern)
+ return self.expect_list(compiled_pattern_list, timeout)
+
+ def expect_exact (self, pattern_list, timeout = -1):
+ """This is similar to expect() except that it takes
+ list of plain strings instead of regular expressions.
+ The idea is that this should be much faster. It could also be
+ useful when you don't want to have to worry about escaping
+ regular expression characters that you want to match.
+ You may also pass just a string without a list and the single
+ string will be converted to a list.
+ If timeout is -1 then timeout will be set to the self.timeout value.
+ """
+ ### This is dumb. It shares most of the code with expect_list.
+ ### The only different is the comparison method and that
+ ### self.match is always None after calling this.
+ if timeout == -1:
+ timeout = self.timeout
+
+ if type(pattern_list) is StringType:
+ pattern_list = [pattern_list]
+
+ try:
+ #ED# incoming = ''
+ incoming = self.buffer
+ while 1: # Keep reading until exception or return.
+ #ED# c = self.read_nonblocking (1, timeout)
+ #ED# incoming = incoming + c
+
+ # Sequence through the list of patterns and look for a match.
+ index = -1
+ for str_target in pattern_list:
+ index = index + 1
+ if str_target is EOF or str_target is TIMEOUT:
+ continue # The Exception patterns are handled differently.
+ match_index = incoming.find (str_target)
+ if match_index >= 0:
+ self.before = incoming [ : match_index]
+ self.after = incoming [match_index : ]
+ self.buffer = incoming [match_index + len(str_target):]
+ self.match = None
+ return index
+ c = self.read_nonblocking (self.maxread, timeout)
+ incoming = incoming + c
+
+ except EOF:
+ if EOF in pattern_list:
+ self.before = incoming
+ self.after = EOF
+ self.buffer = ''
+ return pattern_list.index(EOF)
+ else:
+ raise
+ except TIMEOUT:
+ if TIMEOUT in pattern_list:
+ self.before = incoming
+ self.after = TIMEOUT
+ self.buffer = ''
+ return pattern_list.index(TIMEOUT)
+ else:
+ raise
+ except Exception:
+ self.before = incoming
+ self.after = None
+ self.match = None
+ self.buffer = ''
+ raise
+
+ def expect_list(self, pattern_list, timeout = -1):
+ """
+ This takes a list of compiled regular expressions and returns
+ the index into the pattern_list that matched the child's output.
+ This is called by expect(). It is similar to the expect() method
+ except that expect_list() is not overloaded. You must not pass
+ anything except a list of compiled regular expressions.
+ If timeout is -1 then timeout will be set to the self.timeout value.
+ """
+
+ if timeout == -1:
+ timeout = self.timeout
+
+ try:
+ #ED# incoming = ''
+ incoming = self.buffer
+ while 1: # Keep reading until exception or return.
+ #ED# c = self.read_nonblocking (1, timeout)
+ #ED# incoming = incoming + c
+
+ # Sequence through the list of patterns and look for a match.
+ index = -1
+ for cre in pattern_list:
+ index = index + 1
+ if cre is EOF or cre is TIMEOUT:
+ continue # The patterns for PexpectExceptions are handled differently.
+ match = cre.search(incoming)
+ if match is not None:
+ self.before = incoming[ : match.start()]
+ self.after = incoming[match.start() : ]
+ self.match = match
+ self.buffer = incoming[match.end() : ]
+ return index
+ # Read more data
+ c = self.read_nonblocking (self.maxread, timeout)
+ incoming = incoming + c
+
+ except EOF:
+ if EOF in pattern_list:
+ self.before = incoming
+ self.after = EOF
+ self.buffer = ''
+ return pattern_list.index(EOF)
+ else:
+ raise
+ except TIMEOUT:
+ if TIMEOUT in pattern_list:
+ self.before = incoming
+ self.after = TIMEOUT
+ self.buffer = ''
+ return pattern_list.index(TIMEOUT)
+ else:
+ raise
+ except Exception:
+ self.before = incoming
+ self.after = None
+ self.match = None
+ self.buffer = ''
+ raise
+
+ def getwinsize(self):
+ """
+ This returns the window size of the child tty.
+ The return value is a tuple of (rows, cols).
+ """
+
+ s = struct.pack('HHHH', 0, 0, 0, 0)
+ x = fcntl.ioctl(self.fileno(), termios.TIOCGWINSZ, s)
+ return struct.unpack('HHHH', x)[0:2]
+
+ def setwinsize(self, r, c):
+ """
+ This sets the windowsize of the child tty.
+ This will cause a SIGWINCH signal to be sent to the child.
+ This does not change the physical window size.
+ It changes the size reported to TTY-aware applications like
+ vi or curses -- applications that respond to the SIGWINCH signal.
+ """
+ # Check for buggy platforms. Some Python versions on some platforms
+ # (notably OSF1 Alpha and RedHat 7.1) truncate the value for
+ # termios.TIOCSWINSZ. It is not clear why this happens.
+ # These platforms don't seem to handle the signed int very well;
+ # yet other platforms like OpenBSD have a large negative value for
+ # TIOCSWINSZ and they don't have a truncate problem.
+ # Newer versions of Linux have totally different values for TIOCSWINSZ.
+ #
+ # Note that this fix is a hack.
+ TIOCSWINSZ = termios.TIOCSWINSZ
+ if TIOCSWINSZ == 2148037735L: # L is not required in Python >= 2.2.
+ TIOCSWINSZ = -2146929561 # Same bits, but with sign.
+
+ # Note, assume ws_xpixel and ws_ypixel are zero.
+ s = struct.pack('HHHH', r, c, 0, 0)
+ fcntl.ioctl(self.fileno(), TIOCSWINSZ, s)
+
+ def interact(self, escape_character = chr(29)):
+ """This gives control of the child process to the interactive user
+ (the human at the keyboard).
+ Keystrokes are sent to the child process, and the stdout and stderr
+ output of the child process is printed.
+ When the user types the escape_character this method will stop.
+ The default for escape_character is ^] (ASCII 29).
+ This simply echos the child stdout and child stderr to the real
+ stdout and it echos the real stdin to the child stdin.
+ """
+ # Flush the buffer.
+ self.stdout.write (self.buffer)
+ self.buffer = ''
+ self.stdout.flush()
+ mode = tty.tcgetattr(self.STDIN_FILENO)
+ tty.setraw(self.STDIN_FILENO)
+ try:
+ self.__interact_copy(escape_character)
+ finally:
+ tty.tcsetattr(self.STDIN_FILENO, tty.TCSAFLUSH, mode)
+
+ def __interact_writen(self, fd, data):
+ """This is used by the interact() method.
+ """
+ while data != '' and self.isalive():
+ n = os.write(fd, data)
+ data = data[n:]
+ def __interact_read(self, fd):
+ """This is used by the interact() method.
+ """
+ return os.read(fd, 1000)
+ def __interact_copy(self, escape_character = None):
+ """This is used by the interact() method.
+ """
+ while self.isalive():
+ r, w, e = select.select([self.child_fd, self.STDIN_FILENO], [], [])
+ if self.child_fd in r:
+ data = self.__interact_read(self.child_fd)
+ os.write(self.STDOUT_FILENO, data)
+ if self.STDIN_FILENO in r:
+ data = self.__interact_read(self.STDIN_FILENO)
+ self.__interact_writen(self.child_fd, data)
+ if escape_character in data:
+ break
+
+##############################################################################
+# End of Spawn
+##############################################################################
+
+def _which (filename):
+ """This takes a given filename; tries to find it in the
+ environment path; then checks if it is executable.
+ """
+
+ # Special case where filename already contains a path.
+ if os.path.dirname(filename) != '':
+ if os.access (filename, os.X_OK):
+ return filename
+
+ if not os.environ.has_key('PATH') or os.environ['PATH'] == '':
+ p = os.defpath
+ else:
+ p = os.environ['PATH']
+
+ # Oddly enough this was the one line that made Pexpect
+ # incompatible with Python 1.5.2.
+ #pathlist = p.split (os.pathsep)
+ pathlist = string.split (p, os.pathsep)
+
+ for path in pathlist:
+ f = os.path.join(path, filename)
+ if os.access(f, os.X_OK):
+ return f
+ return None
+
+def _split_command_line(command_line):
+ """This splits a command line into a list of arguments.
+ It splits arguments on spaces, but handles
+ embedded quotes, doublequotes, and escaped characters.
+ It's impossible to do this with a regular expression, so
+ I wrote a little state machine to parse the command line.
+ """
+ arg_list = []
+ arg = ''
+ state_quote = 0
+ state_doublequote = 0
+ state_esc = 0
+ for c in command_line:
+ if c == '\\': # Escape the next character
+ state_esc = 1
+ elif c == r"'": # Handle single quote
+ if state_esc:
+ state_esc = 0
+ elif not state_quote:
+ state_quote = 1
+ else:
+ state_quote = 0
+ elif c == r'"': # Handle double quote
+ if state_esc:
+ state_esc = 0
+ elif not state_doublequote:
+ state_doublequote = 1
+ else:
+ state_doublequote = 0
+
+ # Add arg to arg_list unless in some other state.
+ elif c == ' 'and not state_quote and not state_doublequote and not state_esc:
+ arg_list.append(arg)
+ arg = ''
+ else:
+ arg = arg + c
+ if c != '\\'and state_esc: # escape mode lasts for one character.
+ state_esc = 0
+
+ # Handle last argument.
+ if arg != '':
+ arg_list.append(arg)
+ return arg_list
+
+import shlex
+def _split_command_line(command_line):
+ return shlex.split(command_line)
+
+
+####################
+#
+# NOTES
+#
+####################
+
+## def send_human(self, text, delay_min = 0, delay_max = 1):
+## pass
+## def spawn2(self, command, args):
+## """return pid, fd_stdio, fd_stderr
+## """
+## pass
+
+
+# Reason for double fork:
+# http://www.erlenstar.demon.co.uk/unix/faq_2.html#SEC15
+# Reason for ptys:
+# http://www.erlenstar.demon.co.uk/unix/faq_4.html#SEC52
+
+# Nonblocking on Win32?
+# Reasearch this as a way to maybe make pipe work for Win32.
+# http://groups.google.com/groups?q=setraw+tty&hl=en&selm=uvgpvisvk.fsf%40roundpoint.com&rnum=7
+#
+# if istty:
+# if os.name=='posix':
+# import tty
+# tty.setraw(sys.stdin.fileno())
+# elif os.name=='nt':
+# import win32file, win32con
+# hstdin = win32file._get_osfhandle(sys.stdin.fileno())
+# modes = (win32file.GetConsoleMode(hstdin)
+# & ~(win32con.ENABLE_LINE_INPUT
+# |win32con.ENABLE_ECHO_INPUT))
+# win32file.SetConsoleMode(hstdin, modes)
+
+# Basic documentation:
+# Explain use of lists of patterns and return index.
+# Explain exceptions for non-handled special cases like EOF
+
+# Test bad fork
+# Test ENOENT. In other words, no more TTY devices.
+
+#GLOBAL_SIGCHLD_RECEIVED = 0
+#def childdied (signum, frame):
+# print 'Signal handler called with signal', signum
+# frame.f_globals['pexpect'].GLOBAL_SIGCHLD_RECEIVED = 1
+# print str(frame.f_globals['pexpect'].GLOBAL_SIGCHLD_RECEIVED)
+# GLOBAL_SIGCHLD_RECEIVED = 1
+
+### Weird bug. If you read too fast after doing a sendline()
+# Sometimes you will read the data back that you just sent even if
+# the child did not echo the data. This is particularly a problem if
+# you send a password.
+
+# This was previously used to implement a look-ahead in reads.
+# if the lookahead failed then Pexpect would "push-back" the data
+# that was read. The idea was to allow read() to read blocks of data.
+# What I do now is just read one character at a time and then try a
+# match. This is not as efficient, but it works well enough for the
+# output of most applications and it makes program logic much simpler.
+##class PushbackReader:
+## """This class is a wrapper around os.read. It adds the features of buffering
+## to allow push-back of data and to provide a timeout on a read.
+## """
+## def __init__(self, file_descriptor):
+## self.fd = file_descriptor
+## self.buffer = ''
+##
+## def read(self, n, timeout = None):
+## """This does a read restricted by a timeout and
+## it includes any cached data from previous calls.
+## This is a non-blocking wrapper around os.read.
+## it uses select.select to supply a timeout.
+## Note that if this is called with timeout=None (the default)
+## then this actually MAY block.
+## """
+## # The read() call is a problem.
+## # Some platforms return an empty string '' at EOF.
+## # Whereas other platforms raise an Input/output exception.
+##
+## avail = len(self.buffer)
+## if n > avail:
+## result = self.buffer
+## n = n-avail
+## else:
+## result = self.buffer[: n]
+## self.buffer = self.buffer[n:]
+##
+## r, w, e = select.select([self.fd], [], [], timeout)
+## if not r:
+## self.flag_timeout = 1
+## raise TIMEOUT('Read exceeded time: %d'%timeout)
+##
+## if self.fd in r:
+## try:
+## s = os.read(self.fd, n)
+## except OSError, e:
+## self.flag_eof = 1
+## raise EOF('Read reached End Of File (EOF). Exception platform.')
+## if s == '':
+## self.flag_eof = 1
+## raise EOF('Read reached End Of File (EOF). Empty string platform.')
+## return s
+##
+## self.flag_error = 1
+## raise ExceptionPexpect('PushbackReader.read() reached an unexpected state.'+
+## ' There is a logic error in the Pexpect source code.')
+##
+## def pushback(self, data):
+## self.buffer = piece+self.buffer
+
+
+#def _setwinsize(r, c):
+# """This sets the windowsize of the tty for stdout.
+# This does not change the physical window size.
+# It changes the size reported to TTY-aware applications like
+# vi or curses -- applications that respond to the SIGWINCH signal.
+# This is used by __spawn to set the tty window size of the child.
+# """
+# # Check for buggy platforms. Some Pythons on some platforms
+# # (notably OSF1 Alpha and RedHat 7.1) truncate the value for
+# # termios.TIOCSWINSZ. It is not clear why this happens.
+# # These platforms don't seem to handle the signed int very well;
+# # yet other platforms like OpenBSD have a large negative value for
+# # TIOCSWINSZ and they don't truncate.
+# # Newer versions of Linux have totally different values for TIOCSWINSZ.
+# #
+# # Note that this fix is a hack.
+# TIOCSWINSZ = termios.TIOCSWINSZ
+# if TIOCSWINSZ == 2148037735L: # L is not required in Python 2.2.
+# TIOCSWINSZ = -2146929561 # Same number in binary, but with sign.
+#
+# # Assume ws_xpixel and ws_ypixel are zero.
+# s = struct.pack("HHHH", r, c, 0, 0)
+# fcntl.ioctl(sys.stdout.fileno(), TIOCSWINSZ, s)
+#
+#def _getwinsize():
+# s = struct.pack("HHHH", 0, 0, 0, 0)
+# x = fcntl.ioctl(sys.stdout.fileno(), termios.TIOCGWINSZ, s)
+# return struct.unpack("HHHH", x)
+
diff --git a/RepSys/rpm.py b/RepSys/rpm.py
new file mode 100644
index 0000000..d448c5f
--- /dev/null
+++ b/RepSys/rpm.py
@@ -0,0 +1,19 @@
+#!/usr/bin/python
+from RepSys.util import execcmd
+
+class SRPM:
+ def __init__(self, filename):
+ self.filename = filename
+ self._getinfo()
+
+ def _getinfo(self):
+ cmdstr = "rpm -qp --qf '%%{name} %%{epoch} %%{release} %%{version}' %s"
+ status, output = execcmd(cmdstr % self.filename)
+ self.name, self.epoch, self.release, self.version = output.split()
+ if self.epoch == "(none)":
+ self.epoch = None
+
+ def unpack(self, topdir):
+ execcmd("rpm -i --define '_topdir %s' %s" % (topdir, self.filename))
+
+# vim:et:ts=4:sw=4
diff --git a/RepSys/rpmutil.py b/RepSys/rpmutil.py
new file mode 100644
index 0000000..3df9818
--- /dev/null
+++ b/RepSys/rpmutil.py
@@ -0,0 +1,369 @@
+#!/usr/bin/python
+from RepSys import Error, config
+from RepSys.svn import SVN
+from RepSys.rpm import SRPM
+from RepSys.log import specfile_svn2rpm
+from RepSys.util import execcmd
+import tempfile
+import shutil
+import glob
+import sys
+import os
+
+def get_spec(pkgdirurl, targetdir="."):
+ svn = SVN()
+ tmpdir = tempfile.mktemp()
+ try:
+ geturl = "/".join([pkgdirurl, "current", "SPECS"])
+ svn.checkout("'%s'" % geturl, tmpdir)
+ speclist = glob.glob(os.path.join(tmpdir, "*.spec"))
+ if not speclist:
+ raise Error, "no spec files found"
+ spec = speclist[0]
+ shutil.copy(spec, targetdir)
+ finally:
+ if os.path.isdir(tmpdir):
+ shutil.rmtree(tmpdir)
+
+def get_srpm(pkgdirurl,
+ mode = "current",
+ targetdirs = None,
+ version = None,
+ release = None,
+ revision = None,
+ packager = "",
+ revname = 0,
+ svnlog = 0,
+ scripts = []):
+ svn = SVN()
+ tmpdir = tempfile.mktemp()
+ try:
+ if mode == "version":
+ geturl = os.path.join(pkgdirurl, "versions",
+ version, release)
+ elif mode == "pristine":
+ geturl = os.path.join(pkgdirurl, "pristine")
+ elif mode == "current" or mode == "revision":
+ geturl = os.path.join(pkgdirurl, "current")
+ else:
+ raise Error, "unsupported get_srpm mode: %s" % mode
+ svn.checkout(geturl, tmpdir, rev=revision)
+ srpmsdir = os.path.join(tmpdir, "SRPMS")
+ os.mkdir(srpmsdir)
+ specsdir = os.path.join(tmpdir, "SPECS")
+ speclist = glob.glob(os.path.join(specsdir, "*.spec"))
+ if not speclist:
+ raise Error, "no spec files found"
+ spec = speclist[0]
+ if svnlog:
+ specfile_svn2rpm(pkgdirurl, spec, revision)
+ revision = svn.revision(tmpdir)
+ for script in scripts:
+ status, output = execcmd(script, tmpdir, spec, str(revision),
+ noerror=1)
+ if status != 0:
+ raise Error, "script %s failed" % script
+ if packager:
+ packager = " --define 'packager %s'" % packager
+ execcmd("rpm -bs --nodeps --define '_topdir %s'%s %s" %
+ (tmpdir, packager, spec))
+ if revname:
+ srpm = glob.glob(os.path.join(srpmsdir, "*.src.rpm"))[0]
+ srpmbase = os.path.basename(srpm)
+ os.rename(srpm, "%s/@%i:%s" % (srpmsdir, revision, srpmbase))
+ srpm = glob.glob(os.path.join(srpmsdir, "*.src.rpm"))[0]
+ if not targetdirs:
+ targetdirs = (".",)
+ for targetdir in targetdirs:
+ execcmd("cp -f", srpm, targetdir)
+ os.unlink(srpm)
+ finally:
+ if os.path.isdir(tmpdir):
+ shutil.rmtree(tmpdir)
+
+def patch_spec(pkgdirurl, patchfile, log=""):
+ svn = SVN()
+ tmpdir = tempfile.mktemp()
+ try:
+ geturl = "/".join([pkgdirurl, "current", "SPECS"])
+ svn.checkout(geturl, tmpdir)
+ speclist = glob.glob(os.path.join(tmpdir, "*.spec"))
+ if not speclist:
+ raise Error, "no spec files found"
+ spec = speclist[0]
+ status, output = execcmd("patch", spec, patchfile)
+ if status != 0:
+ raise Error, "can't apply patch:\n%s\n" % output
+ else:
+ svn.commit(tmpdir, log="")
+ finally:
+ if os.path.isdir(tmpdir):
+ shutil.rmtree(tmpdir)
+
+def put_srpm(pkgdirurl, srpmfile, appendname=0, log=""):
+ srpm = SRPM(srpmfile)
+ if appendname:
+ pkgdirurl = "/".join([pkgdirurl, srpm.name])
+ svn = SVN()
+ tmpdir = tempfile.mktemp()
+ try:
+ if srpm.epoch:
+ version = "%s:%s" % (srpm.epoch, srpm.version)
+ else:
+ version = srpm.version
+ versionurl = "/".join([pkgdirurl, "releases", version])
+ releaseurl = "/".join([versionurl, srpm.release])
+ ret = svn.mkdir(pkgdirurl, noerror=1, log="Created package directory")
+ if ret:
+ svn.checkout(pkgdirurl, tmpdir)
+ svn.mkdir(os.path.join(tmpdir, "releases"))
+ svn.mkdir(os.path.join(tmpdir, "releases", version))
+ svn.mkdir(os.path.join(tmpdir, "current"))
+ svn.mkdir(os.path.join(tmpdir, "current", "SPECS"))
+ svn.mkdir(os.path.join(tmpdir, "current", "SOURCES"))
+ #svn.commit(tmpdir,log="Created package structure.")
+ version_exists = 1
+ currentdir = os.path.join(tmpdir, "current")
+ else:
+ if svn.ls(releaseurl, noerror=1):
+ raise Error, "release already exists"
+ svn.checkout("/".join([pkgdirurl, "current"]), tmpdir)
+ svn.mkdir(versionurl, noerror=1,
+ log="Created directory for version %s." % version)
+ currentdir = tmpdir
+
+ specsdir = os.path.join(currentdir, "SPECS")
+ sourcesdir = os.path.join(currentdir, "SOURCES")
+
+ unpackdir = tempfile.mktemp()
+ os.mkdir(unpackdir)
+ try:
+ srpm.unpack(unpackdir)
+
+ uspecsdir = os.path.join(unpackdir, "SPECS")
+ usourcesdir = os.path.join(unpackdir, "SOURCES")
+
+ uspecsentries = os.listdir(uspecsdir)
+ usourcesentries = os.listdir(usourcesdir)
+ specsentries = os.listdir(specsdir)
+ sourcesentries = os.listdir(sourcesdir)
+
+ # Remove old entries
+ for entry in [x for x in specsentries
+ if x not in uspecsentries]:
+ if entry == ".svn":
+ continue
+ entrypath = os.path.join(specsdir, entry)
+ os.unlink(entrypath)
+ svn.remove(entrypath)
+ for entry in [x for x in sourcesentries
+ if x not in usourcesentries]:
+ if entry == ".svn":
+ continue
+ entrypath = os.path.join(sourcesdir, entry)
+ os.unlink(entrypath)
+ svn.remove(entrypath)
+
+ # Copy all files
+ execcmd("cp -rf", uspecsdir, currentdir)
+ execcmd("cp -rf", usourcesdir, currentdir)
+
+ # Add new entries
+ for entry in [x for x in uspecsentries
+ if x not in specsentries]:
+ entrypath = os.path.join(specsdir, entry)
+ svn.add(entrypath)
+ for entry in [x for x in usourcesentries
+ if x not in sourcesentries]:
+ entrypath = os.path.join(sourcesdir, entry)
+ svn.add(entrypath)
+ finally:
+ if os.path.isdir(unpackdir):
+ shutil.rmtree(unpackdir)
+
+ svn.commit(tmpdir, log=log)
+ finally:
+ if os.path.isdir(tmpdir):
+ shutil.rmtree(tmpdir)
+
+ # Do revision and pristine tag copies
+ pristineurl = os.path.join(pkgdirurl, "pristine")
+ svn.remove(pristineurl, noerror=1,
+ log="Removing previous pristine/ directory.")
+ currenturl = os.path.join(pkgdirurl, "current")
+ svn.copy(currenturl, pristineurl,
+ log="Copying release %s-%s to pristine/ directory." %
+ (version, srpm.release))
+ svn.copy(currenturl, releaseurl,
+ log="Copying release %s-%s to releases/ directory." %
+ (version, srpm.release))
+
+def create_package(pkgdirurl, log="", verbose=0):
+ svn = SVN()
+ tmpdir = tempfile.mktemp()
+ try:
+ basename = os.path.basename(pkgdirurl)
+ if verbose:
+ print "Creating package directory...",
+ sys.stdout.flush()
+ ret = svn.mkdir(pkgdirurl,
+ log="Created package directory for '%s'." % basename)
+ if verbose:
+ print "done"
+ print "Checking it out...",
+ svn.checkout(pkgdirurl, tmpdir)
+ if verbose:
+ print "done"
+ print "Creating package structure...",
+ svn.mkdir(os.path.join(tmpdir, "current"))
+ svn.mkdir(os.path.join(tmpdir, "current", "SPECS"))
+ svn.mkdir(os.path.join(tmpdir, "current", "SOURCES"))
+ if verbose:
+ print "done"
+ print "Committing...",
+ svn.commit(tmpdir,
+ log="Created package structure for '%s'." % basename)
+ print "done"
+ finally:
+ if os.path.isdir(tmpdir):
+ shutil.rmtree(tmpdir)
+
+def mark_release(pkgdirurl, version, release, revision):
+ svn = SVN()
+ releasesurl = "/".join([pkgdirurl, "releases"])
+ versionurl = "/".join([releasesurl, version])
+ releaseurl = "/".join([versionurl, release])
+ if svn.ls(releaseurl, noerror=1):
+ raise cncrep.Error, "release already exists"
+ svn.mkdir(releasesurl, noerror=1,
+ log="Created releases directory.")
+ svn.mkdir(versionurl, noerror=1,
+ log="Created directory for version %s." % version)
+ pristineurl = os.path.join(pkgdirurl, "pristine")
+ svn.remove(pristineurl, noerror=1,
+ log="Removing previous pristine/ directory.")
+ currenturl = os.path.join(pkgdirurl, "current")
+ svn.copy(currenturl, pristineurl,
+ log="Copying release %s-%s to pristine/ directory." %
+ (version, release))
+ svn.copy(currenturl, releaseurl, rev=revision,
+ log="Copying release %s-%s to releases/ directory." %
+ (version, release))
+
+def check_changed(url, all=0, show=0, verbose=0):
+ svn = SVN()
+ if all:
+ baseurl = url
+ packages = []
+ if verbose:
+ print "Getting list of packages...",
+ sys.stdout.flush()
+ packages = [x[:-1] for x in svn.ls(baseurl)]
+ if verbose:
+ print "done"
+ if not packages:
+ raise Error, "couldn't get list of packages"
+ else:
+ baseurl, basename = os.path.split(url)
+ packages = [basename]
+ clean = []
+ changed = []
+ nopristine = []
+ nocurrent = []
+ for package in packages:
+ pkgdirurl = os.path.join(baseurl, package)
+ current = os.path.join(pkgdirurl, "current")
+ pristine = os.path.join(pkgdirurl, "pristine")
+ if verbose:
+ print "Checking package %s..." % package,
+ sys.stdout.flush()
+ if not svn.ls(current, noerror=1):
+ if verbose:
+ print "NO CURRENT"
+ nocurrent.append(package)
+ elif not svn.ls(pristine, noerror=1):
+ if verbose:
+ print "NO PRISTINE"
+ nopristine.append(package)
+ else:
+ diff = svn.diff(pristine, current)
+ if diff:
+ changed.append(package)
+ if verbose:
+ print "CHANGED"
+ if show:
+ print diff
+ else:
+ if verbose:
+ print "clean"
+ clean.append(package)
+ if verbose:
+ if not packages:
+ print "No packages found!"
+ elif all:
+ print "Total clean packages: %s" % len(clean)
+ print "Total CHANGED packages: %d" % len(changed)
+ print "Total NO CURRENT packages: %s" % len(nocurrent)
+ print "Total NO PRISTINE packages: %s" % len(nopristine)
+ return {"clean": clean,
+ "changed": changed,
+ "nocurrent": nocurrent,
+ "nopristine": nopristine}
+
+def checkout(url, path=None, revision=None):
+ svn = SVN()
+ current = os.path.join(url, "current")
+ if path is None:
+ _, path = os.path.split(url)
+ svn.checkout(current, path, rev=revision, show=1)
+
+def get_submit_info(path):
+ path = os.path.abspath(path)
+
+ # First, look for SPECS and SOURCES directories.
+ found = False
+ while path != "/":
+ if os.path.isdir(path):
+ specsdir = os.path.join(path, "SPECS")
+ sourcesdir = os.path.join(path, "SOURCES")
+ if os.path.isdir(specsdir) and os.path.isdir(sourcesdir):
+ found = True
+ break
+ path = os.path.dirname(path)
+ if not found:
+ raise Error, "SPECS and/or SOURCES directories not found"
+
+ # Then, check if this is really a subversion directory.
+ if not os.path.isdir(os.path.join(path, ".svn")):
+ raise Error, "subversion directory not found"
+
+ svn = SVN()
+
+ # Now, extract the package name.
+ for line in svn.info(path):
+ if line.startswith("URL: "):
+ url = line.split()[1]
+ toks = url.split("/")
+ if len(toks) < 2 or toks[-1] != "current":
+ raise Error, "unexpected URL received from 'svn info'"
+ name = toks[-2]
+ break
+ else:
+ raise Error, "URL tag not found in 'svn info' output"
+
+ # Finally, guess revision.
+ max = -1
+ files = []
+ files.extend(glob.glob("%s/*" % specsdir))
+ files.extend(glob.glob("%s/*" % sourcesdir))
+ for line in svn.info(" ".join(files)):
+ if line.startswith("Revision: "):
+ rev = int(line.split()[1])
+ if rev > max:
+ max = rev
+ if max == -1:
+ raise Error, "revision tag not found in 'svn info' output"
+
+ return name, max
+
+# vim:et:ts=4:sw=4
diff --git a/RepSys/svn.py b/RepSys/svn.py
new file mode 100644
index 0000000..6a42e6d
--- /dev/null
+++ b/RepSys/svn.py
@@ -0,0 +1,349 @@
+from RepSys import Error, config, pexpect
+from RepSys.util import execcmd
+import sys
+import re
+import time
+
+__all__ = ["SVN", "SVNLook", "SVNLogEntry"]
+
+class SVNLogEntry:
+ def __init__(self, revision, author, date):
+ self.revision = revision
+ self.author = author
+ self.date = date
+ self.lines = []
+
+ def __cmp__(self, other):
+ return cmp(self.date, other.date)
+
+class SVN:
+ def __init__(self, username=None, password=None):
+ if not username:
+ username = config.get("auth", "username")
+ if not password:
+ password = config.get("auth", "password")
+
+ self.username = username
+ self.password = password
+
+ def _execsvn(self, *args, **kwargs):
+ cmdstr = "svn "+" ".join(args)
+ if kwargs.get("local"):
+ return execcmd(cmdstr, **kwargs)
+ show = kwargs.get("show")
+ noerror = kwargs.get("noerror")
+ p = pexpect.spawn(cmdstr, timeout=1)
+ p.setmaxread(1024)
+ p.setecho(False)
+ outlist = []
+ while True:
+ i = p.expect_exact([pexpect.EOF, pexpect.TIMEOUT,
+ "username:", "password:",
+ "(p)ermanently?",
+ "Authorization failed"])
+ if i == 0:
+ if show and p.before:
+ print p.before,
+ outlist.append(p.before)
+ break
+ elif i == 1:
+ if show and p.before:
+ print p.before,
+ outlist.append(p.before)
+ elif i == 2:
+ p.sendline(self.username)
+ outlist = []
+ elif i == 3:
+ p.sendline(self.password)
+ outlist = []
+ elif i == 4:
+ p.sendline("p")
+ outlist = []
+ elif i == 5:
+ if not noerror:
+ raise Error, "authorization failed"
+ else:
+ break
+ while p.isalive():
+ try:
+ time.sleep(1)
+ except (pexpect.TIMEOUT, pexpect.EOF):
+ # Continue until the child dies
+ pass
+ status, output = p.exitstatus, "".join(outlist).strip()
+ if status != 0 and not kwargs.get("noerror"):
+ sys.stderr.write(cmdstr)
+ sys.stderr.write("\n")
+ sys.stderr.write(output)
+ sys.stderr.write("\n")
+ raise Error, "command failed: "+cmdstr
+ return status, output
+
+ def _execsvn_success(self, *args, **kwargs):
+ status, output = self._execsvn(*args, **kwargs)
+ return status == 0
+
+ def _add_log(self, cmd_args, received_kwargs, optional=0):
+ if (not optional or
+ received_kwargs.has_key("log") or
+ received_kwargs.has_key("logfile")):
+ ret = received_kwargs.get("log")
+ if ret is not None:
+ cmd_args.append("-m '%s'" % ret)
+ ret = received_kwargs.get("logfile")
+ if ret is not None:
+ cmd_args.append("-F '%s'" % ret)
+
+ def _add_revision(self, cmd_args, received_kwargs, optional=0):
+ if not optional or received_kwargs.has_key("rev"):
+ ret = received_kwargs.get("rev")
+ if isinstance(ret, basestring):
+ try:
+ ret = int(ret)
+ except ValueError:
+ raise Error, "invalid revision provided"
+ if ret:
+ cmd_args.append("-r %d" % ret)
+
+ def add(self, path, **kwargs):
+ cmd = ["add", path]
+ return self._execsvn_success(noauth=1, *cmd, **kwargs)
+
+ def copy(self, pathfrom, pathto, **kwargs):
+ cmd = ["copy", pathfrom, pathto]
+ self._add_revision(cmd, kwargs, optional=1)
+ self._add_log(cmd, kwargs)
+ return self._execsvn_success(*cmd, **kwargs)
+
+ def remove(self, path, force=0, **kwargs):
+ cmd = ["remove", path]
+ self._add_log(cmd, kwargs)
+ if force:
+ cmd.append("--force")
+ return self._execsvn_success(*cmd, **kwargs)
+
+ def mkdir(self, path, **kwargs):
+ cmd = ["mkdir", path]
+ self._add_log(cmd, kwargs)
+ return self._execsvn_success(*cmd, **kwargs)
+
+ def commit(self, path, **kwargs):
+ cmd = ["commit", path]
+ self._add_log(cmd, kwargs)
+ return self._execsvn_success(*cmd, **kwargs)
+
+ def checkout(self, url, targetpath, **kwargs):
+ cmd = ["checkout", "'%s'" % url, targetpath]
+ self._add_revision(cmd, kwargs, optional=1)
+ return self._execsvn_success(*cmd, **kwargs)
+
+ def propset(self, propname, value, targets, **kwargs):
+ cmd = ["propset", propname, "'%s'" % value, targets]
+ return self._execsvn_success(*cmd, **kwargs)
+
+ def propedit(self, propname, target, **kwargs):
+ cmd = ["propedit", propname, target]
+ if kwargs.get("rev"):
+ cmd.append("--revprop")
+ self._add_revision(cmd, kwargs)
+ return self._execsvn_success(local=True, show=True, *cmd, **kwargs)
+
+ def revision(self, path, **kwargs):
+ cmd = ["info", path]
+ status, output = self._execsvn(local=True, *cmd, **kwargs)
+ if status == 0:
+ for line in output.splitlines():
+ if line.startswith("Revision: "):
+ return int(line.split()[1])
+ return None
+
+ def info(self, path, **kwargs):
+ cmd = ["info", path]
+ status, output = self._execsvn(local=True, *cmd, **kwargs)
+ if status == 0:
+ return output.splitlines()
+ return None
+
+ def ls(self, path, **kwargs):
+ cmd = ["ls", path]
+ status, output = self._execsvn(*cmd, **kwargs)
+ if status == 0:
+ return output.split()
+ return None
+
+ def status(self, path, **kwargs):
+ cmd = ["status", path]
+ if kwargs.get("verbose"):
+ cmd.append("-v")
+ status, output = self._execsvn(*cmd, **kwargs)
+ if status == 0:
+ return [x.split() for x in output.splitlines()]
+ return None
+
+ def cleanup(self, path, **kwargs):
+ cmd = ["cleanup", path]
+ return self._execsvn_success(*cmd, **kwargs)
+
+ def revert(self, path, **kwargs):
+ cmd = ["revert", path]
+ status, output = self._execsvn(*cmd, **kwargs)
+ if status == 0:
+ return [x.split() for x in output.split()]
+ return None
+
+ def update(self, path, **kwargs):
+ cmd = ["update", path]
+ self._add_revision(cmd, kwargs, optional=1)
+ status, output = self._execsvn(*cmd, **kwargs)
+ if status == 0:
+ return [x.split() for x in output.split()]
+ return None
+
+ def merge(self, url1, url2=None, rev1=None, rev2=None, path=None, **kwargs):
+ cmd = ["merge"]
+ if rev1 and rev2 and not url2:
+ cmd.append("-r")
+ cmd.append("%s:%s" % (rev1, rev2))
+ cmd.append(url1)
+ else:
+ if not url2:
+ raise ValueError, \
+ "url2 needed if two revisions are not provided"
+ if rev1:
+ cmd.append("%s@%s" % (url1, rev1))
+ else:
+ cmd.append(url1)
+ if rev2:
+ cmd.append("%s@%s" % (url2, rev2))
+ else:
+ cmd.append(url2)
+ if path:
+ cmd.append(path)
+ status, output = self._execsvn(*cmd, **kwargs)
+ if status == 0:
+ return [x.split() for x in output.split()]
+ return None
+
+ def diff(self, pathurl1, pathurl2=None, **kwargs):
+ cmd = ["diff", pathurl1]
+ self._add_revision(cmd, kwargs, optional=1)
+ if pathurl2:
+ cmd.append(pathurl2)
+ status, output = self._execsvn(*cmd, **kwargs)
+ if status == 0:
+ return output
+ return None
+
+ def cat(self, url, **kwargs):
+ cmd = ["cat", url]
+ self._add_revision(cmd, kwargs, optional=1)
+ status, output = self._execsvn(*cmd, **kwargs)
+ if status == 0:
+ return output
+ return None
+
+ def log(self, url, start=None, end=0, **kwargs):
+ cmd = ["log", url]
+ if start is not None:
+ if type(start) is not type(0):
+ try:
+ start = int(start)
+ except (ValueError, TypeError):
+ raise Error, "invalid log start revision provided"
+ if type(end) is not type(0):
+ try:
+ end = int(end)
+ except (ValueError, TypeError):
+ raise Error, "invalid log end revision provided"
+ cmd.append("-r %d:%d" % (start, end))
+ status, output = self._execsvn(*cmd, **kwargs)
+ if status != 0:
+ return None
+
+ revheader = re.compile("^r(?P<revision>[0-9]+) \| (?P<author>.+?) \| (?P<date>.+?) \| (?P<lines>[0-9]+) (?:line|lines)")
+ logseparator = "-"*72
+ linesleft = 0
+ entry = None
+ log = []
+ emptyline = 0
+ for line in output.splitlines():
+ if emptyline:
+ emptyline = 0
+ continue
+ line = line.rstrip()
+ if linesleft == 0:
+ if line != logseparator:
+ m = revheader.match(line)
+ if m:
+ linesleft = int(m.group("lines"))
+ timestr = " ".join(m.group("date").split()[:2])
+ timetuple = time.strptime(timestr,
+ "%Y-%m-%d %H:%M:%S")
+ entry = SVNLogEntry(int(m.group("revision")),
+ m.group("author"), timetuple)
+ log.append(entry)
+ emptyline = 1
+ else:
+ entry.lines.append(line)
+ linesleft -= 1
+ log.sort()
+ log.reverse()
+ return log
+
+class SVNLook:
+ def __init__(self, repospath, txn=None, rev=None):
+ self.repospath = repospath
+ self.txn = txn
+ self.rev = rev
+
+ def _execsvnlook(self, cmd, *args, **kwargs):
+ execcmd_args = ["svnlook", cmd, self.repospath]
+ self._add_txnrev(execcmd_args, kwargs)
+ execcmd_args += args
+ execcmd_kwargs = {}
+ keywords = ["show", "noerror"]
+ for key in keywords:
+ if kwargs.has_key(key):
+ execcmd_kwargs[key] = kwargs[key]
+ return execcmd(*execcmd_args, **execcmd_kwargs)
+
+ def _add_txnrev(self, cmd_args, received_kwargs):
+ if received_kwargs.has_key("txn"):
+ txn = received_kwargs.get("txn")
+ if txn is not None:
+ cmd_args += ["-t", txn]
+ elif self.txn is not None:
+ cmd_args += ["-t", self.txn]
+ if received_kwargs.has_key("rev"):
+ rev = received_kwargs.get("rev")
+ if rev is not None:
+ cmd_args += ["-r", rev]
+ elif self.rev is not None:
+ cmd_args += ["-r", self.rev]
+
+ def changed(self, **kwargs):
+ status, output = self._execsvnlook("changed", **kwargs)
+ if status != 0:
+ return None
+ changes = []
+ for line in output.splitlines():
+ line = line.rstrip()
+ if not line:
+ continue
+ entry = [None, None, None]
+ changedata, changeprop, path = None, None, None
+ if line[0] != "_":
+ changedata = line[0]
+ if line[1] != " ":
+ changeprop = line[1]
+ path = line[4:]
+ changes.append((changedata, changeprop, path))
+ return changes
+
+ def author(self, **kwargs):
+ status, output = self._execsvnlook("author", **kwargs)
+ if status != 0:
+ return None
+ return output.strip()
+
+# vim:et:ts=4:sw=4
diff --git a/RepSys/util.py b/RepSys/util.py
new file mode 100644
index 0000000..0337bc1
--- /dev/null
+++ b/RepSys/util.py
@@ -0,0 +1,54 @@
+#!/usr/bin/python
+from RepSys import Error, config
+import getpass
+import sys, os
+#import commands
+
+# Our own version of commands' getstatusoutput(). We have a commands
+# module directory, so we can't import Python's standard module
+def commands_getstatusoutput(cmd):
+ """Return (status, output) of executing cmd in a shell."""
+ import os
+ pipe = os.popen('{ ' + cmd + '; } 2>&1', 'r')
+ text = pipe.read()
+ sts = pipe.close()
+ if sts is None: sts = 0
+ if text[-1:] == '\n': text = text[:-1]
+ return sts, text
+
+def execcmd(*cmd, **kwargs):
+ cmdstr = " ".join(cmd)
+ if kwargs.get("show"):
+ status = os.system(cmdstr)
+ output = ""
+ else:
+ status, output = commands_getstatusoutput("LANG=C "+cmdstr)
+ if status != 0 and not kwargs.get("noerror"):
+ raise Error, "command failed: %s\n%s\n" % (cmdstr, output)
+ if config.getbool("global", "verbose", 0):
+ print cmdstr
+ sys.stdout.write(output)
+ return status, output
+
+def get_auth(username=None, password=None):
+ set_username = 1
+ set_password = 1
+ if not username:
+ username = config.get("auth", "username")
+ if not username:
+ username = raw_input("username: ")
+ else:
+ set_username = 0
+ if not password:
+ password = config.get("auth", "password")
+ if not password:
+ password = getpass.getpass("password: ")
+ else:
+ set_password = 0
+ if set_username:
+ config.set("auth", "username", username)
+ if set_password:
+ config.set("auth", "password", password)
+ return username, password
+
+# vim:et:ts=4:sw=4