diff options
Diffstat (limited to 'MgaRepo')
-rw-r--r-- | MgaRepo/VCS.py | 468 | ||||
-rw-r--r-- | MgaRepo/binrepo.py | 4 | ||||
-rw-r--r-- | MgaRepo/commands/clone.py | 51 | ||||
-rw-r--r-- | MgaRepo/commands/log.py | 12 | ||||
-rw-r--r-- | MgaRepo/commands/maintdb.py | 3 | ||||
-rw-r--r-- | MgaRepo/commands/submit.py | 10 | ||||
-rw-r--r-- | MgaRepo/git.py | 57 | ||||
-rw-r--r-- | MgaRepo/rpmutil.py | 66 | ||||
-rw-r--r-- | MgaRepo/simplerpm.py | 12 | ||||
-rw-r--r-- | MgaRepo/svn.py | 451 | ||||
-rw-r--r-- | MgaRepo/util.py | 127 |
11 files changed, 722 insertions, 539 deletions
diff --git a/MgaRepo/VCS.py b/MgaRepo/VCS.py new file mode 100644 index 0000000..30d9279 --- /dev/null +++ b/MgaRepo/VCS.py @@ -0,0 +1,468 @@ +from MgaRepo import Error, SilentError, config +from MgaRepo.util import execcmd, get_auth +import sys +import os +import re +import time + +class VCSLogEntry(object): + def __init__(self, revision, author, date): + self.revision = revision + self.author = author + self.date = date + self.changed = [] + self.lines = [] + + def __lt__(self, other): + return (self.date < other.date) + + def __eq__(self,other): + return (self.date == other.date) + +class VCS(object): + def __init__(self): + self.vcs_name = None + self.vcs_command = None + self.vcs_wrapper = "mga-ssh" + self.vcs_supports = {'clone' : False} + self.env_defaults = None + + def _execVcs(self, *args, **kwargs): + localcmds = ("add", "revert", "cleanup", "mv") + kwargs["collecterr"] = False + if kwargs.get("show"): + if not kwargs.get("local"): + kwargs["collecterr"] = True + else: + if args[0] not in localcmds: + args = list(args) + args.append("--non-interactive") + else: + if args[0] == "mv": + kwargs["collecterr"] = False + else: + kwargs["collecterr"] = True + kwargs["cleanerr"] = True + if kwargs.get("xml"): + args.append("--xml") + cmdargs = [self.vcs_command] + cmdargs.extend(args) + try: + if args[0] in ('info', 'checkout','log'): + kwargs['info'] = True + else: + kwargs['info'] = False + return execcmd(cmdargs, **kwargs) + except Error as e: + msg = None + if e.args: + if "Permission denied" in e.args: + msg = ("It seems ssh-agent or ForwardAgent are not setup " + "or your username is wrong. See " + "https://wiki.mageia.org/en/Packagers_ssh" + " for more information.") + elif "authorization failed" in e.args: + msg = ("Note that mgarepo does not support any HTTP " + "authenticated access.") + if kwargs.get("show") and \ + not config.getbool("global", "verbose", 0): + # svn has already dumped error messages, we don't need to + # do it too + if msg: + sys.stderr.write("\n") + sys.stderr.write(msg) + sys.stderr.write("\n") + raise SilentError + elif msg: + raise Error("%s\n%s" % (e, msg)) + raise + + def _set_env(self): + wrapper = "mgarepo-ssh" + repsys = config.get("global", "mgarepo-cmd") + if repsys: + dir = os.path.dirname(repsys) + path = os.path.join(dir, wrapper) + if os.path.exists(path): + wrapper = path + defaults = {"SVN_SSH": wrapper} + os.environ.update(defaults) + raw = config.get("global", "svn-env") + if raw: + for line in raw.split("\n"): + env = line.strip() + if not env: + continue + try: + name, value = env.split("=", 1) + except ValueError: + sys.stderr.write("invalid svn environment line: %r\n" % env) + continue + os.environ[name] = value + + def _execVcs_success(self, *args, **kwargs): + status, output = self._execVcs(*args, **kwargs) + return status == 0 + + def _add_log(self, cmd_args, received_kwargs, optional=0): + if (not optional or + "log" in received_kwargs or + "logfile" in received_kwargs): + ret = received_kwargs.get("log") + if ret is not None: + cmd_args.extend(("-m", ret)) + ret = received_kwargs.get("logfile") + if ret is not None: + cmd_args.extend(("-F", ret)) + + def _add_revision(self, cmd_args, received_kwargs, optional=0): + if not optional or "rev" in received_kwargs: + ret = received_kwargs.get("rev") + if isinstance(ret, str): + if not ret.startswith("{"): # if not a datespec + try: + ret = int(ret) + except ValueError: + raise Error("invalid revision provided") + if ret: + cmd_args.extend(("-r", str(ret))) + + def add(self, path, **kwargs): + cmd = ["add", path + '@' if '@' in path else path] + return self._execVcs_success(noauth=1, *cmd, **kwargs) + + def copy(self, pathfrom, pathto, **kwargs): + cmd = ["copy", pathfrom + '@' if '@' in pathfrom else pathfrom, pathto + '@' if '@' in pathto else pathto] + self._add_revision(cmd, kwargs, optional=1) + self._add_log(cmd, kwargs) + return self._execVcs_success(*cmd, **kwargs) + + def remove(self, path, force=0, **kwargs): + cmd = ["remove", path + '@' if '@' in path else path] + self._add_log(cmd, kwargs) + if force: + cmd.append("--force") + return self._execVcs_success(*cmd, **kwargs) + + def mkdir(self, path, **kwargs): + cmd = ["mkdir", path + '@' if '@' in path else path] + if kwargs.get("parents"): + cmd.append("--parents") + self._add_log(cmd, kwargs) + return self._execVcs_success(*cmd, **kwargs) + + def _execVcs_commit(self, *cmd, **kwargs): + status, output = self._execVcs(*cmd, **kwargs) + match = re.search("Committed revision (?P<rev>\\d+)\\.$", output) + if match: + rawrev = match.group("rev") + return int(rawrev) + + def commit(self, path, **kwargs): + cmd = ["commit", path + '@' if '@' in path else path] + if kwargs.get("nonrecursive"): + cmd.append("-N") + self._add_log(cmd, kwargs) + return self._execVcs_commit(*cmd, **kwargs) + + def import_(self, path, url, **kwargs): + cmd = ["import", path, url] + self._add_log(cmd, kwargs) + return self._execVcs_commit(*cmd, **kwargs) + + def export(self, url, targetpath, **kwargs): + cmd = ["export", url, targetpath] + self._add_revision(cmd, kwargs, optional=1) + return self._execVcs_success(*cmd, **kwargs) + + def checkout(self, url, targetpath, **kwargs): + cmd = ["checkout", url, targetpath] + self._add_revision(cmd, kwargs, optional=1) + return self._execVcs_success(*cmd, **kwargs) + + def clone(self, url, targetpath, **kwargs): + if self.vcs_supports['clone']: + cmd = ["clone", url, targetpath] + return self._execVcs_success(*cmd, **kwargs) + else: + raise Error("%s doesn't support 'clone'" % self.vcs_name) + + def propget(self, propname, targets, **kwargs): + cmd = ["propget", propname, targets] + if kwargs.get("revprop"): + cmd.append("--revprop") + self._add_revision(cmd, kwargs) + status, output = self._execVcs(local=True, *cmd, **kwargs) + return output + + def propset(self, propname, value, targets, **kwargs): + cmd = ["propset", propname, value, targets] + return self._execVcs_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._execVcs_success(local=True, show=True, *cmd, **kwargs) + + def revision(self, path, **kwargs): + cmd = ["info", path + '@' if '@' in path else path] + status, output = self._execVcs(local=True, *cmd, **kwargs) + if status == 0: + for line in output.splitlines(): + if line.startswith("Last Changed Rev: "): + return int(line.split()[3]) + return None + + def info(self, path, **kwargs): + cmd = ["info", path + '@' if '@' in path else path] + status, output = self._execVcs(local=True, noerror=True, *cmd, **kwargs) + if (("Not a versioned resource" not in output) and ("svn: warning: W155010" not in output)): + return output.splitlines() + return None + + def info2(self, *args, **kwargs): + lines = self.info(*args, **kwargs) + if lines is None: + return None + pairs = [[w.strip() for w in line.split(":", 1)] for line in lines] + info = {} + for pair in pairs: + if pair != ['']: + info[pair[0]]=pair[1] + return info + + def ls(self, path, **kwargs): + cmd = ["ls", path + '@' if '@' in path else path] + status, output = self._execVcs(*cmd, **kwargs) + if status == 0: + return output.split() + return None + + def status(self, path, **kwargs): + cmd = ["status", path + '@' if '@' in path else path] + if kwargs.get("verbose"): + cmd.append("-v") + if kwargs.get("noignore"): + cmd.append("--no-ignore") + if kwargs.get("quiet"): + cmd.append("--quiet") + status, output = self._execVcs(*cmd, **kwargs) + if status == 0: + return [(x[0], x[8:]) for x in output.splitlines()] + return None + + def cleanup(self, path, **kwargs): + cmd = ["cleanup", path + '@' if '@' in path else path] + return self._execVcs_success(*cmd, **kwargs) + + def revert(self, path, **kwargs): + cmd = ["revert", path + '@' if '@' in path else path] + status, output = self._execVcs(*cmd, **kwargs) + if status == 0: + return [x.split() for x in output.split()] + return None + + def switch(self, url, oldurl=None, path=None, relocate=False, **kwargs): + cmd = ["switch"] + if relocate: + if oldurl is None: + raise Error("You must supply the old URL when "\ + "relocating working copies") + cmd.append("--relocate") + cmd.append(oldurl) + cmd.append(url) + if path is not None: + cmd.append(path) + return self._execVcs_success(*cmd, **kwargs) + + def update(self, path, **kwargs): + cmd = ["update", path + '@' if '@' in path else path] + self._add_revision(cmd, kwargs, optional=1) + status, output = self._execVcs(*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._execVcs(*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._execVcs(*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._execVcs(*cmd, **kwargs) + if status == 0: + return output + return None + + def log(self, url, start=None, end=0, limit=None, **kwargs): + cmd = ["log", "-v", url] + if start is not None or end != 0: + if start is not None and 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") + start = start or "HEAD" + cmd.extend(("-r", "%s:%s" % (start, end))) + if limit is not None: + try: + limit = int(limit) + except (ValueError, TypeError): + raise Error("invalid limit number provided") + cmd.extend(("--limit", str(limit))) + status, output = self._execVcs(*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)$") + changedpat = re.compile(r"^\s+(?P<action>[^\s]+) (?P<path>[^\s]+)(?: \([^\s]+ (?P<from_path>[^:]+)(?:\:(?P<from_rev>[0-9]+))?\))?$") + logseparator = "-"*72 + linesleft = 0 + entry = None + log = [] + appendchanged = 0 + changedheader = 0 + for line in output.splitlines(): + line = line.rstrip() + if changedheader: + appendchanged = 1 + changedheader = 0 + elif appendchanged: + if not line: + appendchanged = 0 + continue + m = changedpat.match(line) + if m: + changed = m.groupdict().copy() + from_rev = changed.get("from_rev") + if from_rev is not None: + try: + changed["from_rev"] = int(from_rev) + except (ValueError, TypeError): + raise Error("invalid revision number in % log" % self.vcs_name) + entry.changed.append(changed) + elif 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 = VCSLogEntry(int(m.group("revision")), + m.group("author"), timetuple) + log.append(entry) + changedheader = 1 + else: + entry.lines.append(line) + linesleft -= 1 + log.sort() + log.reverse() + return log + + def mv(self, path, dest, message=None, **kwargs): + cmd = ["mv", path, dest, ] + if message: + cmd.extend(("-m", str(message))) + else: + kwargs['show'] = True + self._add_log(cmd, kwargs) + return self._execVcs_success(*cmd, **kwargs) + +class VCSLook(object): + def __init__(self, repospath, txn=None, rev=None): + self.repospath = repospath + self.txn = txn + self.rev = rev + self.execcmd = None + + def _execVcslook(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 key in kwargs: + execcmd_kwargs[key] = kwargs[key] + return execcmd(*execcmd_args, **execcmd_kwargs) + + def _add_txnrev(self, cmd_args, received_kwargs): + if "txn" in received_kwargs: + txn = received_kwargs.get("txn") + if txn is not None: + cmd_args.extend(("-t", txn)) + elif self.txn is not None: + cmd_args.extend(("-t", self.txn)) + if "rev" in received_kwargs: + rev = received_kwargs.get("rev") + if rev is not None: + cmd_args.exten(("-r", str(rev))) + elif self.rev is not None: + cmd_args.extend(("-r", str(self.rev))) + + def changed(self, **kwargs): + status, output = self._execVcslook("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._execVcslook("author", **kwargs) + if status != 0: + return None + return output.strip() + +# vim:et:ts=4:sw=4 diff --git a/MgaRepo/binrepo.py b/MgaRepo/binrepo.py index 89679b0..2d08300 100644 --- a/MgaRepo/binrepo.py +++ b/MgaRepo/binrepo.py @@ -102,12 +102,12 @@ def upload_binary(topdir, filename): return host = config.get("binrepo", "upload_host") upload_bin_helper = get_helper("upload-bin") - command = "ssh %s %s %s" % (host, upload_bin_helper, filename) + command = ["ssh", host, upload_bin_helper, filename] try: filein = open(filepath, 'r') except Error as e: raise Error("Could not open file %s\n" % filepath) - status, output = execcmd(command, show=True, geterr=True, stdin=filein) + status, output = execcmd(command, show=True, collecterr=True, stdin=filein) def import_binaries(topdir, pkgname): """Import all binaries from a given package checkout diff --git a/MgaRepo/commands/clone.py b/MgaRepo/commands/clone.py new file mode 100644 index 0000000..589ec75 --- /dev/null +++ b/MgaRepo/commands/clone.py @@ -0,0 +1,51 @@ +#!/usr/bin/python +from MgaRepo import Error +from MgaRepo.command import * +from MgaRepo.rpmutil import clone +import getopt +import sys + +HELP = """\ +Usage: repsys co [OPTIONS] URL [LOCALPATH] + +Checkout the package source from the Mandriva repository. + +If the 'mirror' option is enabled, the package is obtained from the mirror +repository. + +You can specify the distro branch to checkout from by using distro/pkgname. + +Options: + -d The distribution branch to checkout from + -b The package branch + -M Do not use the mirror (use the main repository) + -h Show this message + +Examples: + repsys clone pkgname + repsys clone -d 2009.0 pkgname + repsys clone 2009.0/pkgame + repsys clone http://repos/svn/cnc/snapshot/foo + repsys clone http://repos/svn/cnc/snapshot/foo foo-pkg +""" + +def parse_options(): + parser = OptionParser(help=HELP) + parser.add_option("--distribution", "-d", dest="distro", default=None) + parser.add_option("--branch", "-b", dest="branch", default=None) + opts, args = parser.parse_args() + if len(args) not in (1, 2): + raise Error("invalid arguments") + # here we don't use package_url in order to notify the user we are + # using the mirror + opts.pkgdirurl = args[0] + if len(args) == 2: + opts.path = args[1] + else: + opts.path = None + return opts + +def main(): + do_command(parse_options, clone) + +# vim:et:ts=4:sw=4 diff --git a/MgaRepo/commands/log.py b/MgaRepo/commands/log.py index 2181125..330a96a 100644 --- a/MgaRepo/commands/log.py +++ b/MgaRepo/commands/log.py @@ -6,6 +6,8 @@ from MgaRepo.rpmutil import sync from MgaRepo.util import execcmd import sys import os +import subprocess +import shlex HELP = """\ Usage: mgarepo log [OPTIONS] [PACKAGE] @@ -57,9 +59,13 @@ def svn_log(pkgdirurl, verbose=False, limit=None, revision=None, releases=None): args.append("-r") args.append(revision) if os.isatty(sys.stdin.fileno()): - args.append("| less") - rawcmd = " ".join(args) - execcmd(rawcmd, show=True) + pager = shlex.split(os.environ.get("PAGER", "less")) + p = subprocess.Popen(args, stdout=subprocess.PIPE) + p2 = subprocess.Popen(pager, stdin=p.stdout) + p2.wait() + p.wait() + else: + execcmd(args, show=True) def main(): do_command(parse_options, svn_log) diff --git a/MgaRepo/commands/maintdb.py b/MgaRepo/commands/maintdb.py index 9a97be8..2aa2a4c 100644 --- a/MgaRepo/commands/maintdb.py +++ b/MgaRepo/commands/maintdb.py @@ -33,8 +33,7 @@ def parse_options(): def maintdb(maintdb_args): host = config.get("maintdb", "host", "maintdb.mageia.org") maintdb_helper = get_helper("maintdb") - cmd_args = ' '.join(maintdb_args) - command = "ssh %s %s %s" % (host, maintdb_helper, cmd_args) + command = ["ssh", host, maintdb_helper] + maintdb_args execcmd(command, show=True) sys.exit(0) diff --git a/MgaRepo/commands/submit.py b/MgaRepo/commands/submit.py index 9f05dca..d36290f 100644 --- a/MgaRepo/commands/submit.py +++ b/MgaRepo/commands/submit.py @@ -160,9 +160,10 @@ def list_targets(option, opt, val, parser): raise Error("no submit host defined in mgarepo.conf") createsrpm = get_helper("create-srpm") #TODO make it configurable - command = "ssh %s %s --list" % (host, createsrpm) - execcmd(command, show=True) - sys.exit(0) + args = ["ssh", host, createsrpm, "--list"] + execcmd(args, show=true) + sys.exit(0) # it is invoked via optparse callback, thus we need to + # force ending the script def submit(urls, target, define=[], submithost=None, atonce=False, sid=None): if submithost is None: @@ -197,8 +198,7 @@ def submit(urls, target, define=[], submithost=None, atonce=False, sid=None): else: cmdsargs.extend((baseargs + [url]) for url in urls) for cmdargs in cmdsargs: - command = subprocess.list2cmdline(cmdargs) - status, output = execcmd(command) + status, output = execcmd(cmdargs) if status == 0: print("Package submitted!") else: diff --git a/MgaRepo/git.py b/MgaRepo/git.py new file mode 100644 index 0000000..a424a08 --- /dev/null +++ b/MgaRepo/git.py @@ -0,0 +1,57 @@ +from MgaRepo import Error, config +from MgaRepo.util import execcmd +from MgaRepo.VCS import * +from os.path import basename, dirname +from os import chdir, getcwd +import sys +import re +import time +from xml.etree import ElementTree +import subprocess + +class GITLogEntry(VCSLogEntry): + def __init__(self, revision, author, date): + VCSLogEntry.__init__(self, revision, author, data) + +class GIT(VCS): + def __init__(self): + VCS.__init__(self) + self.vcs_name = "git" + self.vcs_command = config.get("global", "git-command", "git") + self.vcs_supports['clone'] = True + self.env_defaults = {"GIT_SSH": self.vcs_wrapper} + + def clone(self, url, targetpath, **kwargs): + if url.split(':')[0].find("svn") < 0: + return VCS.clone(self, url, targetpath, **kwargs) + else: + # To speed things up on huge repositories, we'll just grab all the + # revision numbers for this specific directory and grab these only + # in stead of having to go through each and every revision... + retval, result = execcmd("svn log --stop-on-copy --xml %s" % url) + if retval: + return retval + parser = ElementTree.XMLParser() + result = "".join(result.split("\n")) + parser.feed(result) + log = parser.close() + logentries = log.getiterator("logentry") + revisions = [] + topurl = dirname(url) + trunk = basename(url) + tags = "releases" + execcmd("git svn init %s --trunk=%s --tags=%s %s" % (topurl, trunk, tags, targetpath), show=True) + chdir(targetpath) + for entry in logentries: + revisions.append(entry.attrib["revision"]) + while revisions: + execcmd("git svn fetch -r%d" % int(revisions.pop()), show=True) + + cmd = ["svn", "rebase"] + return self._execVcs_success(*cmd, **kwargs) + +class SVNLook(VCSLook): + def __init__(self, repospath, txn=None, rev=None): + VCSLook.__init__(self, repospath, txn, rev) + +# vim:et:ts=4:sw=4 diff --git a/MgaRepo/rpmutil.py b/MgaRepo/rpmutil.py index 8c00da9..850bd4d 100644 --- a/MgaRepo/rpmutil.py +++ b/MgaRepo/rpmutil.py @@ -1,9 +1,10 @@ #!/usr/bin/python3 from MgaRepo import Error, config from MgaRepo import mirror, layout, log, binrepo +from MgaRepo.git import GIT from MgaRepo.svn import SVN from MgaRepo.simplerpm import SRPM -from MgaRepo.util import execcmd +from MgaRepo.util import execcmd, CommandError from MgaRepo.command import default_parent import rpm import urllib.parse @@ -20,7 +21,7 @@ def get_spec(pkgdirurl, targetdir=".", submit=False): try: geturl = layout.checkout_url(pkgdirurl, append_path="SPECS") mirror.info(geturl) - svn.export("'%s'" % geturl, tmpdir) + svn.export(geturl, tmpdir) speclist = glob.glob(os.path.join(tmpdir, "*.spec")) if not speclist: raise Error("no spec files found") @@ -33,11 +34,6 @@ def get_spec(pkgdirurl, targetdir=".", submit=False): if os.path.isdir(tmpdir): shutil.rmtree(tmpdir) -def rpm_macros_defs(macros): - defs = ("--define \"%s %s\"" % macro for macro in macros) - args = " ".join(defs) - return args - #FIXME move it to another module def rev_touched_url(url, rev): svn = SVN() @@ -72,13 +68,17 @@ def get_srpm(pkgdirurl, strict = False): svn = SVN() tmpdir = tempfile.mktemp() - topdir = "--define '_topdir %s'" % tmpdir - builddir = "--define '_builddir %s/%s'" % (tmpdir, "BUILD") - rpmdir = "--define '_rpmdir %s/%s'" % (tmpdir, "RPMS") - sourcedir = "--define '_sourcedir %s/%s'" % (tmpdir, "SOURCES") - specdir = "--define '_specdir %s/%s'" % (tmpdir, "SPECS") - srcrpmdir = "--define '_srcrpmdir %s/%s'" % (tmpdir, "SRPMS") - patchdir = "--define '_patchdir %s/%s'" % (tmpdir, "SOURCES") + topdir = "_topdir %s" % tmpdir + builddir = "_builddir %s/%s" % (tmpdir, "BUILD") + rpmdir = "_rpmdir %s/%s" % (tmpdir, "RPMS") + sourcedir = "_sourcedir %s/%s" % (tmpdir, "SOURCES") + specdir = "_specdir %s/%s" % (tmpdir, "SPECS") + srcrpmdir = "_srcrpmdir %s/%s" % (tmpdir, "SRPMS") + patchdir = "_patchdir %s/%s" % (tmpdir, "SOURCES") + temppath = "_tmppath %s" % (tmpdir) + + rpmdefs = [("--define", expr) for expr in (topdir, builddir, rpmdir, + sourcedir, specdir, srcrpmdir, patchdir, temppath)] try: if mode == "version": @@ -107,7 +107,7 @@ def get_srpm(pkgdirurl, if config.getbool("srpm", "run-prep", False): makefile = os.path.join(tmpdir, "Makefile") if os.path.exists(makefile): - execcmd("make", "-C", tmpdir, "srpm-prep") + execcmd(("make", "-C", tmpdir, "srpm-prep")) if not speclist: raise Error("no spec files found") spec = speclist[0] @@ -124,11 +124,22 @@ def get_srpm(pkgdirurl, if packager: packager = " --define 'packager %s'" % packager - defs = rpm_macros_defs(macros) sourcecmd = config.get("helper", "rpmbuild", "rpmbuild") - execcmd("%s -bs --nodeps %s %s %s %s %s %s %s %s %s %s" % - (sourcecmd, topdir, builddir, rpmdir, sourcedir, specdir, - srcrpmdir, patchdir, packager, spec, defs)) + args = [sourcecmd, "-bs", "--nodeps"] + for pair in rpmdefs: + args.extend(pair) + for pair in macros: + args.extend(("--define", "%s %s" % pair)) + args.append(spec) + try: + execcmd(args) + except CommandError as e: + if config.getbool("global", "verbose"): + cmdline = e.cmdline + "\n" + else: + cmdline = "" + raise Error("error while creating the source RPM " + "(with %s):\n%s%s" % (sourcecmd, cmdline, e.output)) # copy the generated SRPMs to their target locations targetsrpms = [] @@ -170,7 +181,7 @@ def patch_spec(pkgdirurl, patchfile, log=""): if not speclist: raise Error("no spec files found") spec = speclist[0] - status, output = execcmd("patch", spec, patchfile) + status, output = execcmd(["patch", spec, patchfile]) if status != 0: raise Error("can't apply patch:\n%s\n" % output) else: @@ -250,9 +261,9 @@ def put_srpm(srpmfile, markrelease=False, striplog=True, branch=None, svn.remove(entrypath) # Copy all files - execcmd("cp -rlf", uspecsdir, currentdir) + execcmd(["cp", "-rf", uspecsdir, currentdir]) if os.path.isdir(usourcesdir): - execcmd("cp -rlf", usourcesdir, currentdir) + execcmd(["cp", "-rlf", usourcesdir, currentdir]) # Add new entries for entry in [x for x in uspecsentries @@ -481,6 +492,17 @@ def checkout(pkgdirurl, path=None, revision=None, branch=None, distro=None, back if not spec: binrepo.download_binaries(path) +def clone(pkgdirurl, path=None, branch=None, + distro=None): + o_pkgdirurl = pkgdirurl + pkgdirurl = layout.package_url(o_pkgdirurl, distro=distro) + current = layout.checkout_url(pkgdirurl, branch=branch) + if path is None: + path = layout.package_name(pkgdirurl) + mirror.info(current) + git = GIT() + git.clone(current, path, show=1) + def getpkgtopdir(basedir=None): #FIXME this implementation doesn't work well with relative path names, # which is something we need in order to have a friendlier output diff --git a/MgaRepo/simplerpm.py b/MgaRepo/simplerpm.py index a06e694..3d50176 100644 --- a/MgaRepo/simplerpm.py +++ b/MgaRepo/simplerpm.py @@ -7,15 +7,17 @@ class SRPM: self._getinfo() def _getinfo(self): - cmdstr = "rpm -qp --nosignature --qf '%%{name} %%{epoch} %%{release} %%{version}' %s" - status, output = execcmd(cmdstr % self.filename) + args = ["rpm", "-qp", "--qf", "%{name} %{epoch} %{release} %{version}", + self.filename] + status, output = execcmd(args) self.name, self.epoch, self.release, self.version = output.split() if self.epoch == "(none)": self.epoch = None def unpack(self, topdir): - execcmd(("rpm -i --nodeps --define '_sourcedir %s/SOURCES' " + - "--define '_specdir %s/SPECS' --define '_patchdir %s/SOURCES' %s") - % (topdir, topdir, topdir, self.filename)) + args ["rpm", "-i", "--nodeps", "--define", "_sourcedir", topdir+"/SOURCES", + "--define", "_specdir", topdir + "/SPECS", "--define", "_patchdir", + topdir+"/SOURCES", self.filename] + execcmd(args) # vim:et:ts=4:sw=4 diff --git a/MgaRepo/svn.py b/MgaRepo/svn.py index 74f47a3..3a74954 100644 --- a/MgaRepo/svn.py +++ b/MgaRepo/svn.py @@ -1,5 +1,5 @@ -from MgaRepo import Error, SilentError, config -from MgaRepo.util import execcmd, get_auth +from MgaRepo.util import execcmd +from MgaRepo.VCS import * import sys import os import re @@ -7,445 +7,20 @@ import time __all__ = ["SVN", "SVNLook", "SVNLogEntry"] -class SVNLogEntry: +class SVNLogEntry(VCSLogEntry): def __init__(self, revision, author, date): - self.revision = revision - self.author = author - self.date = date - self.changed = [] - self.lines = [] + VCSLogEntry.__init__(self, revision, author, data) - def __lt__(self, other): - return (self.date < other.date) - - def __eq__(self,other): - return (self.date == other.date) +class SVN(VCS): + def __init__(self): + VCS.__init__(self) + self.vcs_name = "svn" + self.vcs_command = config.get("global", "svn-command", "svn") + self.env_defaults = {"SVN_SSH": self.vcs_wrapper} -class SVN: - def _execsvn(self, *args, **kwargs): - localcmds = ("add", "revert", "cleanup", "mv") - if not kwargs.get("show") and args[0] not in localcmds: - args = list(args) - args.append("--non-interactive") - else: - if args[0] == "mv": - kwargs["geterr"] = False - else: - kwargs["geterr"] = True - kwargs["cleanerr"] = True - if kwargs.get("xml"): - args.append("--xml") - self._set_env() - svn_command = config.get("global", "svn-command", "svn") - cmdstr = svn_command + " " + " ".join(args) - try: - if args[0] in ('info', 'checkout','log'): - kwargs['info'] = True - else: - kwargs['info'] = False - return execcmd(cmdstr, **kwargs) - except Error as e: - msg = None - if e.args: - if "Permission denied" in e.args: - msg = ("It seems ssh-agent or ForwardAgent are not setup " - "or your username is wrong. See " - "https://wiki.mageia.org/en/Packagers_ssh" - " for more information.") - elif "authorization failed" in e.args: - msg = ("Note that mgarepo does not support any HTTP " - "authenticated access.") - if kwargs.get("show") and \ - not config.getbool("global", "verbose", 0): - # svn has already dumped error messages, we don't need to - # do it too - if msg: - sys.stderr.write("\n") - sys.stderr.write(msg) - sys.stderr.write("\n") - raise SilentError - elif msg: - raise Error("%s\n%s" % (e, msg)) - raise - - def _set_env(self): - wrapper = "mgarepo-ssh" - repsys = config.get("global", "mgarepo-cmd") - if repsys: - dir = os.path.dirname(repsys) - path = os.path.join(dir, wrapper) - if os.path.exists(path): - wrapper = path - defaults = {"SVN_SSH": wrapper} - os.environ.update(defaults) - raw = config.get("global", "svn-env") - if raw: - for line in raw.split("\n"): - env = line.strip() - if not env: - continue - try: - name, value = env.split("=", 1) - except ValueError: - sys.stderr.write("invalid svn environment line: %r\n" % env) - continue - os.environ[name] = value - - 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 - "log" in received_kwargs or - "logfile" in received_kwargs): - 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 "rev" in received_kwargs: - ret = received_kwargs.get("rev") - if isinstance(ret, str): - if not ret.startswith("{"): # if not a datespec - try: - ret = int(ret) - except ValueError: - raise Error("invalid revision provided") - if ret: - cmd_args.append("-r '%s'" % ret) - - def add(self, path, **kwargs): - cmd = ["add", path + '@' if '@' in path else path] - return self._execsvn_success(noauth=1, *cmd, **kwargs) - - def copy(self, pathfrom, pathto, **kwargs): - cmd = ["copy", pathfrom + '@' if '@' in pathfrom else pathfrom, pathto + '@' if '@' in pathto else 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 + '@' if '@' in path else 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 + '@' if '@' in path else path] - if kwargs.get("parents"): - cmd.append("--parents") - self._add_log(cmd, kwargs) - return self._execsvn_success(*cmd, **kwargs) - - def _execsvn_commit(self, *cmd, **kwargs): - status, output = self._execsvn(*cmd, **kwargs) - match = re.search("Committed revision (?P<rev>\\d+)\\.$", output) - if match: - rawrev = match.group("rev") - return int(rawrev) - - def commit(self, path, **kwargs): - cmd = ["commit", path + '@' if '@' in path else path] - if kwargs.get("nonrecursive"): - cmd.append("-N") - self._add_log(cmd, kwargs) - return self._execsvn_commit(*cmd, **kwargs) - - def import_(self, path, url, **kwargs): - cmd = ["import", "'%s'" % path, "'%s'" % url] - self._add_log(cmd, kwargs) - return self._execsvn_commit(*cmd, **kwargs) - - def export(self, url, targetpath, **kwargs): - cmd = ["export", "'%s'" % url, targetpath] - self._add_revision(cmd, kwargs, optional=1) - 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 propget(self, propname, targets, **kwargs): - cmd = ["propget", propname, targets] - if kwargs.get("revprop"): - cmd.append("--revprop") - self._add_revision(cmd, kwargs) - status, output = self._execsvn(local=True, *cmd, **kwargs) - return output - - 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 + '@' if '@' in path else path] - status, output = self._execsvn(local=True, *cmd, **kwargs) - if status == 0: - for line in output.splitlines(): - if line.startswith("Last Changed Rev: "): - return int(line.split()[3]) - return None - - def info(self, path, **kwargs): - cmd = ["info", path + '@' if '@' in path else path] - status, output = self._execsvn(local=True, noerror=True, *cmd, **kwargs) - if (("Not a versioned resource" not in output) and ("svn: warning: W155010" not in output)): - return output.splitlines() - return None - - def info2(self, *args, **kwargs): - lines = self.info(*args, **kwargs) - if lines is None: - return None - pairs = [[w.strip() for w in line.split(":", 1)] for line in lines] - info = {} - for pair in pairs: - if pair != ['']: - info[pair[0]]=pair[1] - return info - - def ls(self, path, **kwargs): - cmd = ["ls", path + '@' if '@' in path else path] - status, output = self._execsvn(*cmd, **kwargs) - if status == 0: - return output.split() - return None - - def status(self, path, **kwargs): - cmd = ["status", path + '@' if '@' in path else path] - if kwargs.get("verbose"): - cmd.append("-v") - if kwargs.get("noignore"): - cmd.append("--no-ignore") - if kwargs.get("quiet"): - cmd.append("--quiet") - status, output = self._execsvn(*cmd, **kwargs) - if status == 0: - return [(x[0], x[8:]) for x in output.splitlines()] - return None - - def cleanup(self, path, **kwargs): - cmd = ["cleanup", path + '@' if '@' in path else path] - return self._execsvn_success(*cmd, **kwargs) - - def revert(self, path, **kwargs): - cmd = ["revert", path + '@' if '@' in path else path] - status, output = self._execsvn(*cmd, **kwargs) - if status == 0: - return [x.split() for x in output.split()] - return None - - def switch(self, url, oldurl=None, path=None, relocate=False, **kwargs): - cmd = ["switch"] - if relocate: - if oldurl is None: - raise Error("You must supply the old URL when "\ - "relocating working copies") - cmd.append("--relocate") - cmd.append(oldurl) - cmd.append(url) - if path is not None: - cmd.append(path) - return self._execsvn_success(*cmd, **kwargs) - - def update(self, path, **kwargs): - cmd = ["update", path + '@' if '@' in path else 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, limit=None, **kwargs): - cmd = ["log", "-v", url] - if start is not None or end != 0: - if start is not None and 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") - start = start or "HEAD" - cmd.append("-r %s:%s" % (start, end)) - if limit is not None: - try: - limit = int(limit) - except (ValueError, TypeError): - raise Error("invalid limit number provided") - cmd.append("--limit %d" % limit) - 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)$") - changedpat = re.compile(r"^\s+(?P<action>[^\s]+) (?P<path>[^\s]+)(?: \([^\s]+ (?P<from_path>[^:]+)(?:\:(?P<from_rev>[0-9]+))?\))?$") - logseparator = "-"*72 - linesleft = 0 - entry = None - log = [] - appendchanged = 0 - changedheader = 0 - for line in output.splitlines(): - line = line.rstrip() - if changedheader: - appendchanged = 1 - changedheader = 0 - elif appendchanged: - if not line: - appendchanged = 0 - continue - m = changedpat.match(line) - if m: - changed = m.groupdict().copy() - from_rev = changed.get("from_rev") - if from_rev is not None: - try: - changed["from_rev"] = int(from_rev) - except (ValueError, TypeError): - raise Error("invalid revision number in svn log") - entry.changed.append(changed) - elif 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) - changedheader = 1 - else: - entry.lines.append(line) - linesleft -= 1 - log.sort() - log.reverse() - return log - - def mv(self, path, dest, message=None, **kwargs): - cmd = ["mv", path, dest, ] - if message: - cmd.append("-m '%s'"%message) - else: - kwargs['show'] = True - self._add_log(cmd, kwargs) - return self._execsvn_success(*cmd, **kwargs) - -class SVNLook: +class SVNLook(VCSLook): 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 key in kwargs: - execcmd_kwargs[key] = kwargs[key] - return execcmd(*execcmd_args, **execcmd_kwargs) - - def _add_txnrev(self, cmd_args, received_kwargs): - if "txn" in received_kwargs: - 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 "rev" in received_kwargs: - 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() + VCSLook.__init__(self, repospath, txn, rev) + self.execcmd = "svnlook" # vim:et:ts=4:sw=4 diff --git a/MgaRepo/util.py b/MgaRepo/util.py index 52ae50f..057923f 100644 --- a/MgaRepo/util.py +++ b/MgaRepo/util.py @@ -2,6 +2,7 @@ from MgaRepo import Error, config +import shlex import subprocess import getpass import sys @@ -11,73 +12,75 @@ import select from io import StringIO import httplib2 -# Our own version of commands' commands_exec(). We have a commands -# module directory, so we can't import Python's standard module - -# 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.""" - pipe = subprocess.Popen('{ ' + cmd + '; } 2>&1', stdin = subprocess.PIPE, stdout=subprocess.PIPE, universal_newlines = True, shell = True) - of = pipe.stdout.fileno() - text = '' - pipe.stdin.close() - while True: - text += os.read(of,8192).decode('utf8') - status = pipe.poll() - if status is not None or text == '': - break - if text[-1:] == '\n': text = text[:-1] - return status, text +class CommandError(Error): + def __init__(self, cmdline, status, output): + self.cmdline = cmdline + self.status = status + self.output = output + + def __str__(self): + return "command failed: %s\n%s\n" % (self.cmdline, self.output) def execcmd(*cmd, **kwargs): - cmdstr = " ".join(cmd) - verbose = config.getbool("global", "verbose", 0) - if kwargs.get('info') : - prefix='LANGUAGE=C LC_TIME=C ' + assert (kwargs.get("collecterr") and kwargs.get("show")) or not kwargs.get("collecterr"), \ + ("execcmd is implemented to handle collecterr=True only if show=True") + # split command args + if isinstance(cmd[0], str): + cmdargs = shlex.split(cmd[0]) else: - prefix='LANG=C LANGUAGE=C LC_ALL=C ' - if verbose: - print(prefix + cmdstr) - if kwargs.get("show"): - if kwargs.get("geterr"): - err = StringIO() - pstdin = kwargs.get("stdin") if kwargs.get("stdin") else None - p = subprocess.Popen(prefix + cmdstr, shell=True, - stdout=subprocess.PIPE, stderr=subprocess.PIPE, - stdin=pstdin) - of = p.stdout.fileno() - ef = p.stderr.fileno() - while True: - r,w,x = select.select((of,ef), (), ()) - odata = None - if of in r: - odata = (os.read(of, 8192)).decode('utf8') - sys.stdout.write(odata) - edata = None - if ef in r: - edata = (os.read(ef, 8192)).decode('utf8') - err.write(edata) - sys.stderr.write(edata) - - status = p.poll() - if status is not None and odata == '' and edata == '': - break - output = err.getvalue() + cmdargs = cmd[0][:] + + stdout = None + stderr = None + env = {} + env.update(os.environ) + if kwargs.get("info") or not kwargs.get("show") or (kwargs.get("show") and kwargs.get("collecterr")): + if kwargs.get("info"): + env.update({"LANGUAGE": "C", "LC_TIME": "C"}) else: - status = os.system(prefix + cmdstr) - output = "" - else: - status, output = commands_getstatusoutput(prefix + cmdstr) - if status != 0 and not kwargs.get("noerror"): - if kwargs.get("cleanerr") and not verbose: - raise Error(output) + env.update({"LANG": "C", "LANGUAGE": "C", "LC_ALL": "C"}) + stdout = subprocess.PIPE + if kwargs.get("collecterr"): + stderr = subprocess.PIPE else: - raise Error("command failed: %s\n%s\n" % (cmdstr, output)) - if verbose: - print(output) - sys.stdout.write(output) - return status, output + stderr = subprocess.STDOUT + + proc = subprocess.Popen(cmdargs, shell=False, stdout=stdout, + stderr=stderr, env=env) + + output = "" + + if kwargs.get("show") and kwargs.get("collecterr"): + error = StringIO() + wl = [] + outfd = proc.stdout.fileno() + errfd = proc.stderr.fileno() + rl = [outfd, errfd] + xl = wl + while proc.poll() is None: + mrl, _, _ = select.select(rl, wl, xl, 0.5) + for fd in mrl: + data = os.read(fd, 8192).decode('utf8') + if fd == errfd: + error.write(data) + sys.stderr.write(data) + else: + sys.stdout.write(data) + output = error.getvalue() + else: + proc.wait() + if proc.stdout is not None: + output = proc.stdout.read().decode('utf8') + if kwargs.get("strip", True): + output = output.rstrip() + + if (not kwargs.get("noerror")) and proc.returncode != 0: + if kwargs.get("cleanerr"): + msg = output + cmdline = subprocess.list2cmdline(cmdargs) + raise CommandError(cmdline, proc.returncode, output) + + return proc.returncode, output def get_output_exec(cmdstr): output = StringIO() |