diff options
-rw-r--r-- | MgaRepo/GitHub.py | 89 | ||||
-rw-r--r-- | MgaRepo/VCS.py | 470 | ||||
-rw-r--r-- | MgaRepo/binrepo.py | 4 | ||||
-rw-r--r-- | MgaRepo/commands/buildrpm.py | 45 | ||||
-rw-r--r-- | MgaRepo/commands/clone.py | 54 | ||||
-rw-r--r-- | MgaRepo/commands/getsrpm.py | 3 | ||||
-rw-r--r-- | MgaRepo/commands/github.py | 57 | ||||
-rw-r--r-- | MgaRepo/commands/log.py | 12 | ||||
-rw-r--r-- | MgaRepo/commands/maintdb.py | 3 | ||||
-rw-r--r-- | MgaRepo/commands/rpmlog.py | 10 | ||||
-rw-r--r-- | MgaRepo/commands/submit.py | 10 | ||||
-rw-r--r-- | MgaRepo/git.py | 192 | ||||
-rw-r--r-- | MgaRepo/log.py | 129 | ||||
-rw-r--r-- | MgaRepo/rpmutil.py | 314 | ||||
-rw-r--r-- | MgaRepo/simplerpm.py | 12 | ||||
-rw-r--r-- | MgaRepo/svn.py | 452 | ||||
-rw-r--r-- | MgaRepo/util.py | 139 | ||||
-rwxr-xr-x | create-srpm | 4 | ||||
-rwxr-xr-x | mgarepo | 1 | ||||
-rw-r--r-- | mgarepo.conf | 4 | ||||
-rwxr-xr-x | setup.py | 5 |
21 files changed, 1382 insertions, 627 deletions
diff --git a/MgaRepo/GitHub.py b/MgaRepo/GitHub.py new file mode 100644 index 0000000..71f53d6 --- /dev/null +++ b/MgaRepo/GitHub.py @@ -0,0 +1,89 @@ +from MgaRepo import Error, config +from MgaRepo.rpmutil import detectVCS, get_pkg_tag, clone +from MgaRepo.util import execcmd +from MgaRepo import layout +from MgaRepo.git import GIT +from MgaRepo.svn import SVN +from rpm import RPMTAG_SUMMARY, RPMTAG_URL +import github +import os + +class GitHub(object): + def __init__(self, username = config.get("github", "login"), password = config.get("github", "password")): + self._github = github.Github(login_or_token=username, password=password) + self._organization = self._github.get_organization(config.get("github", "organization", "mdkcauldron")) + self._repos = self._organization.get_repos() + + def repository_exists(self, name): + for repo in self._repos: + if repo.name == name: + return repo + return None + + def create_repository(self, pkgname, **kwargs): + repository = self._organization.create_repo(pkgname, **kwargs) + return repository + + def delete_repository(self, pkgname, **kwargs): + repository = self.repository_exists(pkgname) + if repository: + print("deleting repository %s" % repository.full_name) + repository.delete() + return True + raise Error("repository %s doesn't exist!" % (self._organization.login+"/"+pkgname)) + + def import_package(self, target): + if not os.path.exists(target): + target = layout.checkout_url(layout.package_url(target)) + vcs = detectVCS(target) + top_dir = vcs.get_topdir() + pkgname = layout.package_name(layout.remove_current(vcs.url)) + + repository = self.repository_exists(pkgname) + if not repository or not repository.get_stats_commit_activity(): + if not repository: + if os.path.exists(vcs.path): + summary = get_pkg_tag(RPMTAG_SUMMARY, path=top_dir) + url = get_pkg_tag(RPMTAG_URL, path=top_dir) + repository = self.create_repository(pkgname, description=summary, homepage=url) + print("GitHub repository created at " + repository.html_url) + else: + print("Empty GitHub repository already created at %s, using" % repository.html_url) + + if isinstance(vcs, GIT): + status, output = vcs.remote("add", repository.full_name, repository.ssh_url, noerror=True) + if status: + if status == 128 and ("fatal: remote %s already exists." % repository.full_name) \ + in output: + pass + else: + raise Error(output) + + status, output = vcs.push("--mirror", repository.full_name, show=True) + if status == 0: + print("Success!") + return True + elif isinstance(vcs, SVN): + clone(vcs.url, bindownload=False) + return self.import_package(pkgname) + + else: + raise Error("GitHub repository already exists at " + repository.html_url) + raise Error("GitHub import failed...") + + def clone_repository(self, pkgname, target=None): + if not target: + target = pkgname + repository = self.repository_exists(pkgname) + if repository: + svnurl = layout.checkout_url(layout.package_url(pkgname)) + if repository.permissions: + giturl = repository.ssh_url + else: + giturl = repository.git_url + execcmd(("git", "clone", "--mirror", giturl, os.path.join(target, ".git")), show=True) + git_svn = GIT(path=target, url=svnurl) + git_svn.init(svnurl, pkgname, branch="master", fullnames=True) + + return True + raise Error("Repository %s doesn't exist!" % (self._organization.login+"/"+pkgname)) diff --git a/MgaRepo/VCS.py b/MgaRepo/VCS.py new file mode 100644 index 0000000..ee419f4 --- /dev/null +++ b/MgaRepo/VCS.py @@ -0,0 +1,470 @@ +from MgaRepo import Error, SilentError, config +from MgaRepo.util import execcmd, get_auth +from MgaRepo import layout +from xml.etree import ElementTree +import sys +import os +import re +import time + +class VCSLogEntry(object): + def __init__(self, revision, author, date, lines=[], changed=[]): + self.revision = revision + self.author = author + self.date = date + self.changed = changed + self.lines = lines + + def __lt__(self, other): + return (self.date < other.date) + + def __eq__(self,other): + return (self.date == other.date) + +class VCS(object): + vcs_dirname = None + vcs_name = None + def __init__(self, path, url): + self.vcs_command = None + self.vcs_wrapper = "mga-ssh" + self.vcs_supports = {'clone' : False} + self.vcs_type = None + self.env_defaults = None + if not path and not url: + self._path = os.path.curdir + elif not path and url: + self._path = layout.package_name(layout.remove_current(url)) + else: + self._path = path + self._url = url + + def _execVcs(self, *args, **kwargs): + localcmds = ("add", "revert", "cleanup", "mv") + cmd = self.vcs_command + list(args) + kwargs["collecterr"] = kwargs.get("collecterr", False) + if kwargs.get("show"): + if not kwargs.get("local"): + kwargs["collecterr"] = True + else: + if self.vcs_command is "svn" and args[0] not in localcmds: + cmd.append("--non-interactive") + else: + if args[0] == "mv": + kwargs["collecterr"] = False + kwargs["cleanerr"] = kwargs.get("cleanerr", True) + if kwargs.get("xml"): + cmd.append("--xml") + try: + if args[0] in ('info', 'checkout','log'): + kwargs['info'] = True + else: + kwargs['info'] = False + return execcmd(*cmd, **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, xml=True, **kwargs) + if status != 0: + return None + + xmllog = ElementTree.fromstring(output) + log = [] + logentries = xmllog.getiterator("logentry") + for entry in logentries: + changed = [] + lines = [] + for pathelem in entry.getiterator("paths"): + path = pathelem.find("path") + from_rev = path.get("copyfrom-rev") + if from_rev: + from_rev = int(from_rev) + changed.append({"from_rev" : from_rev, "from_path" : path.get("copyfrom-path"), "action" : path.get("action"), "path" : path.text}) + date = entry.findtext("date").split("T") + timestr = "%s %s" % (date[0], date[1].split(".")[0]) + timetuple = time.strptime(timestr, "%Y-%m-%d %H:%M:%S") + lines.extend(entry.findtext("msg").rstrip().split("\n")) + logentry = VCSLogEntry(int(entry.attrib["revision"]), + entry.findtext("author"), timetuple, [line.rstrip() for line in lines], changed) + log.append(logentry) + 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) + + def get_topdir(self): + vcsdir = os.path.join(self._path, self.vcs_dirname) + if os.path.exists(vcsdir) and os.path.isdir(vcsdir): + return self._path + else: + return None + + @property + def path(self): + return self._path + + @property + def url(self): + if not self._url: + self._url = self.info2(self._path)["URL"] + return self._url + +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/buildrpm.py b/MgaRepo/commands/buildrpm.py new file mode 100644 index 0000000..9caec45 --- /dev/null +++ b/MgaRepo/commands/buildrpm.py @@ -0,0 +1,45 @@ +#!/usr/bin/python +# +from MgaRepo.command import do_command +from MgaRepo.rpmutil import build_rpm +from optparse import * + +HELP = """\ +Usage: mgarepo buildrpm [OPTIONS] + +Builds the binary RPM(s) (.rpm) file(s) of a given package. + +Options: + -bX Build stage option, where X is stage, default is -bb + -I Don't automatically try install missing build dependencies + -L Disable rpmlint check of packages built + -P USER Define the RPM packager information to USER + -d Use DNF + -q Quiet build output + -s Jump to specific build stage (--short-circuit) + -l Use subversion log to build rpm %changelog + -F Do not use full name & email for packagers in %changelog + -- Options and arguments following will be passed to rpmbuild + +""" + +def parse_options(): + parser = OptionParser(HELP) + parser.add_option("-b", dest="build_cmd", default="a") + parser.add_option("-I", dest="installdeps", action="store_false", default=True) + parser.add_option("-L", dest="rpmlint", action="store_false", default=True) + parser.add_option("-P", dest="packager", default="") + parser.add_option("-d", "--dnf", dest="use_dnf", action="store_true", default=False) + parser.add_option("-q", "--quiet", dest="verbose", action="store_false", default=True) + parser.add_option("-s", "--short-circuit", dest="short_circuit", action="store_true", default=False) + parser.add_option("-l", dest="svnlog", action="store_true", default=False) + parser.add_option("-F", dest="fullnames", default=True, + action="store_false") + opts, args = parser.parse_args() + opts.rpmargs = parser.rargs + return opts + +def main(): + do_command(parse_options, build_rpm) + +# vim:et:ts=4:sw=4 diff --git a/MgaRepo/commands/clone.py b/MgaRepo/commands/clone.py new file mode 100644 index 0000000..86793c9 --- /dev/null +++ b/MgaRepo/commands/clone.py @@ -0,0 +1,54 @@ +#!/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 + -F Do not convert svn usernames to full name & email + +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) + parser.add_option("-F", dest="fullnames", default=True, + action="store_false") + 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/getsrpm.py b/MgaRepo/commands/getsrpm.py index 82bd626..5eb630b 100644 --- a/MgaRepo/commands/getsrpm.py +++ b/MgaRepo/commands/getsrpm.py @@ -30,6 +30,7 @@ Options: -n Rename the package to include the revision number -l Use subversion log to build rpm %changelog -T FILE Template to be used to generate the %changelog + -F Do not use full name & email for packagers in %changelog -M Do not use the mirror (use the main repository) -h Show this message --strict Check if the given revision contains changes in REPPKGURL @@ -76,6 +77,8 @@ def parse_options(): parser.add_option("-n", dest="revname", action="store_true") parser.add_option("-l", dest="svnlog", action="store_true") parser.add_option("-T", dest="template", type="string", default=None) + parser.add_option("-F", dest="fullnames", default=True, + action="store_false") parser.add_option("-M", "--no-mirror", action="callback", callback=disable_mirror) parser.add_option("--strict", dest="strict", default=False, diff --git a/MgaRepo/commands/github.py b/MgaRepo/commands/github.py new file mode 100644 index 0000000..dece050 --- /dev/null +++ b/MgaRepo/commands/github.py @@ -0,0 +1,57 @@ +#!/usr/bin/python +from MgaRepo import Error +from MgaRepo.command import * +from MgaRepo.GitHub import GitHub +import getopt +import sys + +HELP = """\ +Usage: mgarepo github [OPTIONS] URL + +Import a git-svn cloned repository to github + +Options: + -h Show this message + +Examples: + mgarepo github import existingpkg + mgarepo github import svn://svn.mageia.org/svn/packages/cauldron/existingpkg +""" +def github_clone(pkg, **kwargs): + github = GitHub() + github.clone_repository(pkg) + +def github_import(target=".", **kwargs): + github = GitHub() + github.import_package(target) + +def github_delete(pkg, **kwargs): + github = GitHub() + github.delete_repository(pkg) + +def parse_options(): + parser = OptionParser(help=HELP) + opts, args = parser.parse_args() + if len(args) < 1: + raise Error("invalid arguments") + opts.func = globals().get("github_"+args[0], None) + if args[0] == "import": + if len(args) > 1: + opts.target = args[1] + elif args[0] == "delete" or args[0] == "clone": + opts.pkg = args[1] + else: + raise Error("invalid arguments: %s" % str(args)) + return opts + +def dispatch_cmd(*args, **kwargs): + func = kwargs.pop("func", None) + if func: + func(**kwargs) + else: + raise Error("invalid command: %s %s" % (sys.argv[0], sys.argv[1])) + +def main(): + do_command(parse_options, dispatch_cmd) + +# 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/rpmlog.py b/MgaRepo/commands/rpmlog.py index 88dfc4b..28d66f1 100644 --- a/MgaRepo/commands/rpmlog.py +++ b/MgaRepo/commands/rpmlog.py @@ -25,6 +25,7 @@ Options: -p Append changelog found in .spec file -s Sort changelog entries, even from the old log -M Do not use the mirror (use the main repository) + -F Do not use full name & email for packagers where available -h Show this message Examples: @@ -45,13 +46,15 @@ def parse_options(): action="store_true") parser.add_option("-M", "--no-mirror", action="callback", callback=disable_mirror) + parser.add_option("-F", dest="fullnames", default=True, + action="store_false") opts, args = parser.parse_args() if len(args) != 1: raise Error("invalid arguments") opts.pkgdirurl = layout.package_url(args[0]) return opts -def rpmlog(pkgdirurl, revision, size, template, oldlog, usespec, sort): +def rpmlog(pkgdirurl, revision, size, template, oldlog, usespec, sort, fullnames): another = None if usespec: svn = SVN() @@ -59,7 +62,10 @@ def rpmlog(pkgdirurl, revision, size, template, oldlog, usespec, sort): rawspec = svn.cat(specurl, rev=revision) spec, another = split_spec_changelog(StringIO(rawspec)) newlog = get_changelog(pkgdirurl, another=another, rev=revision, - size=size, sort=sort, template=template, oldlog=oldlog) + size=size, sort=sort, template=template, oldlog=oldlog, fullnames=fullnames) + # make sure stdout support unicode, otherwise it'll croak when encountered + if not "UTF-8" in sys.stdout.encoding: + sys.stdout = open(sys.stdout.fileno(), mode="w", encoding="UTF-8") sys.stdout.writelines(newlog) def main(): 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..4e763f8 --- /dev/null +++ b/MgaRepo/git.py @@ -0,0 +1,192 @@ +from MgaRepo import Error, config +from MgaRepo.util import execcmd +from MgaRepo.VCS import * +from MgaRepo.svn import SVN +from MgaRepo.log import UserTagParser +from os.path import basename, dirname, abspath, lexists, join +from os import chdir, getcwd +from tempfile import mkstemp +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): + vcs_dirname = ".git" + vcs_name = "git" + def __init__(self, path=None, url=None): + VCS.__init__(self, path, url) + self.vcs_command = config.get("global", "git-command", ["git"]) + self.vcs_supports['clone'] = True + self.env_defaults = {"GIT_SSH": self.vcs_wrapper} + + def configget(self, key="", location="--local"): + cmd = ["config", location, "--get-regexp", key] + config = None + status, output = self._execVcs(*cmd, noerror=True) + if not status and output: + config = eval("{'" + output.replace("\n", "',\n'").replace(" ", "' : '") + "'}") + return config + + def configset(self, config, location="--local"): + cmd = ("config", location) + for pair in config.items(): + status, output = self._execVcs(*cmd + pair) + if status: + return False + return True + + def clone(self, url, targetpath, fullnames=True, **kwargs): + for vcs in (SVN, GIT): + if lexists(join(targetpath, vcs.vcs_dirname)): + raise Error("target path %s already contains %s repository, aborting..." % (targetpath, vcs.vcs_name)) + + self.init(url, targetpath, fullnames=True, **kwargs) + if url.split(':')[0].find("svn") < 0: + return VCS.clone(self, url, **kwargs) + else: + return self.update(targetpath, clone=True, **kwargs) + + def init(self, url, targetpath, fullnames=True, branch=None, **kwargs): + # verify repo url + execcmd("svn", "info", url) + + topurl = dirname(url) + trunk = basename(url) + tags = "releases" + # cloning svn braches as well should rather be optionalif reenabled.. + #cmd = ["svn", "init", topurl, "--trunk="+trunk, "--tags="+tags", targetpath] + + cmd = ["svn", "init", url, abspath(targetpath)] + self._execVcs(*cmd, **kwargs) + os.environ.update({"GIT_WORK_TREE" : abspath(targetpath), "GIT_DIR" : join(abspath(targetpath),".git")}) + + if fullnames: + usermap = UserTagParser() + # store configuration in local git config so that'll be reused later when ie. updating + gitconfig = {"svn-remote.authorlog.url" : usermap.url, + "svn-remote.authorlog.defaultmail": usermap.defaultmail} + self.configset(gitconfig) + + if branch: + execcmd(("git", "init", "-q", self.path), **kwargs) + execcmd(("git", "checkout", "-q", branch), **kwargs) + cmd = ["svn", "rebase", "--local"] + status, output = self._execVcs(*cmd, **kwargs) + + return True + + def info(self, path, **kwargs): + cmd = ["svn", "info", path + '@' if '@' in path else path] + status, output = self._execVcs(local=True, noerror=True, *cmd, **kwargs) + if (("Not a git repository" not in output) and \ + ("Unable to determine upstream SVN information from working tree history" not in output)): + return output.splitlines() + 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("-u") + if kwargs.get("quiet"): + cmd.append("-s") + status, output = self._execVcs(*cmd, **kwargs) + if status == 0: + return [(x[0], x[8:]) for x in output.splitlines()] + return None + + def update(self, targetpath, clone=False, **kwargs): + os.environ.update({"GIT_WORK_TREE" : abspath(targetpath), "GIT_DIR" : join(abspath(targetpath),".git")}) + + if not clone: + cmd = ["svn", "log", "--oneline", "--limit=1"] + retval, result = self._execVcs(*cmd) + if retval: + return retval + + revision = result.split() + + if revision[0][0] == 'r': + startrev = "-r"+str(int(revision[0][1:])+1) + else: + startrev = "BASE" + + cmd = ["svn", "propget", "svn:entry:committed-rev"] + retval, lastrev = self._execVcs(*cmd) + if retval: + return retval + + #cmd = ["config", "--get-regexp", '^svn-remote.svn.(url|fetch)'] + cmd = ["config", "--get", "svn-remote.svn.url"] + retval, result = self._execVcs(*cmd) + if retval: + return retval + + #result = result.strip().split() + #url = result[1] + "/" + result[3].split(":")[0] + url = result.strip() + + # 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... + cmd = ["svn", "log", "-g", "--xml", url] + if not clone: + cmd.append("%s:%s" % (startrev,lastrev)) + retval, result = execcmd(*cmd) + if retval: + return retval + + xmllog = ElementTree.fromstring(result) + logentries = xmllog.getiterator("logentry") + revisions = [] + for entry in logentries: + revisions.append(int(entry.attrib["revision"])) + revisions.sort() + + fetchcmd = ["svn", "fetch", "--log-window-size=1000"] + gitconfig = self.configget("svn-remote.authorlog") + if gitconfig: + usermap = UserTagParser(url=gitconfig.get("svn-remote.authorlog.url"),defaultmail=gitconfig.get("svn-remote.authorlog.defaultmail")) + usermapfile = usermap.get_user_map_file() + fetchcmd.extend(("--authors-file", usermapfile)) + fetchcmd.append("") + + while revisions: + fetchcmd[-1] = "-r%d"%revisions.pop(0) + self._execVcs(*fetchcmd, **kwargs) + if gitconfig: + usermap.cleanup() + + cmd = ["svn", "rebase", "--log-window-size=1000", "--local", "--fetch-all", "git-svn"] + status, output = self._execVcs(*cmd, **kwargs) + if status == 0: + return [x.split() for x in output.split()] + return None + + def remote(self, *args, **kwargs): + cmd = ["remote"] + list(args) + status, output = self._execVcs(*cmd, **kwargs) + return status, output + + def pull(self, *args, **kwargs): + cmd = ["pull"] + list(args) + status, output = self._execVcs(*cmd, **kwargs) + return status, output + + def push(self, *args, **kwargs): + cmd = ["push"] + list(args) + status, output = self._execVcs(*cmd, **kwargs) + return status, output + +class GITLook(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/log.py b/MgaRepo/log.py index fab7d6e..f6c0fc9 100644 --- a/MgaRepo/log.py +++ b/MgaRepo/log.py @@ -8,6 +8,7 @@ from io import StringIO import sys import os +import os.path import re import time import locale @@ -27,7 +28,6 @@ def getrelease(pkgdirurl, rev=None, macros=[], exported=None, create=False): Is here where things should be changed if "automatic release increasing" will be used. """ - from MgaRepo.rpmutil import rpm_macros_defs svn = SVN() pkgcurrenturl = os.path.join(pkgdirurl, "current") specurl = os.path.join(pkgcurrenturl, "SPECS") @@ -43,17 +43,11 @@ def getrelease(pkgdirurl, rev=None, macros=[], exported=None, create=False): if not found: raise Error("no .spec file found inside %s" % specurl) specpath = found[0] - options = rpm_macros_defs(macros) - command = (("rpm -q --qf '%%{EPOCH}:%%{VERSION}-%%{RELEASE}\n' " - "--specfile %s %s") % - (specpath, options)) - pipe = subprocess.Popen(command, stdout=subprocess.PIPE, - stderr=subprocess.PIPE, shell=True) - pipe.wait() - output = pipe.stdout.read().decode('utf8') - error = pipe.stderr.read().decode('utf8') - if pipe.returncode != 0: - raise Error("Error in command %s: %s" % (command, error)) + options = [("--define", expr) for expr in macros] + command = ["rpm", "-q", "--qf", "%{EPOCH}:%{VERSION}-%{RELEASE}\n", + "--specfile", specpath] + command.extend(options) + status, output = execcmd(*command) releases = output.split() try: epoch, vr = releases[0].split(":", 1) @@ -81,17 +75,11 @@ def getrelease(pkgdirurl, rev=None, macros=[], exported=None, create=False): if not found: raise Error("no .src.rpm file found inside %s" % srpmurl) srpmpath = found[0] - options = rpm_macros_defs(macros) - command = (("rpm -qp --qf '%%{EPOCH}:%%{VERSION}-%%{RELEASE}\n' " - " %s %s") % - (srpmpath, options)) - pipe = subprocess.Popen(command, stdout=subprocess.PIPE, - stderr=subprocess.PIPE, shell=True) - pipe.wait() - output = pipe.stdout.read().decode('utf8') - error = pipe.stderr.read().decode('utf8') - if pipe.returncode != 0: - raise Error("Error in command %s: %s" % (command, error)) + options = [("--define", expr) for expr in macros] + command = ["rpm", "-q", "--qf", "%{EPOCH}:%{VERSION}-%{RELEASE}\n", + "--specfile", specpath] + command.extend(options) + status, output = execcmd(*command) releases = output.split() try: epoch, vr = releases[0].split(":", 1) @@ -257,11 +245,12 @@ def group_revisions_by_author(currentlog): emailpat = re.compile("(?P<name>.*?)\s*<(?P<email>.*?)>") - +usermap = {} def get_author_name(author): found = emailpat.match(config.get("users", author, author)) - name = ((found and found.group("name")) or author) - email = ((found and found.group("email")) or author) + gold = emailpat.match(usermap.get(author,"")) + name = ((found and found.group("name")) or (gold and gold.group("name")) or author) + email = ((found and found.group("email")) or (gold and gold.group("email")) or author+"@mageia.org") return name, email def parse_raw_date(rawdate): @@ -354,15 +343,18 @@ def dump_file(releases, currentlog=None, template=None): first = False else: draft = draft + spaces + line + "\n" - draft += '\n' + if rel is not releases_author[-1]: + draft += "\n" else: # default template if not releases_author[-1].visible: releases_author = releases_author[:-1] for rel in releases_author: if not rel.released: - draft = " (not released yet)\n" - draft = draft + "* {0} {1} <{2}> {3}-{4}\n+ Revision: {5}\n".format(rel.date, rel.author_name, rel.author_email, rel.version, rel.release, rel.revision) + unreleased = " (not released yet)\n" + else: + unreleased = "" + draft = draft + "* {0} {1} <{2}> {3}-{4}\n{5}+ Revision: {6}\n".format(rel.date, rel.author_name, rel.author_email, rel.version, rel.release, unreleased, rel.revision) if not rel.visible: draft = draft + "+ rebuild (emptylog)\n" for rev in rel.release_revisions: @@ -375,7 +367,8 @@ def dump_file(releases, currentlog=None, template=None): for rev in author.revisions: for line in rev.lines: draft = draft + line + "\n" - draft += "\n" + if rel is not releases_author[-1]: + draft += "\n" return draft class InvalidEntryError(Exception): @@ -606,9 +599,74 @@ def get_old_log(pkgdirurl): chlog.seek(0) return chlog +from html.parser import HTMLParser +from urllib.request import urlopen +class UserTagParser(HTMLParser): + li = False + ahref = False + userpage = None + namepat = re.compile("(?P<name>.*?)\s*\((?P<user>.*?)\)") + usermap = {} + usermapfile = None + + def __init__(self, url=None, defaultmail=None, *cmd, **kwargs): + HTMLParser.__init__(self, *cmd, **kwargs) + self.url = url or "http://people.mageia.org/u/" + self.defaultmail = defaultmail or "mageia.org" + + def handle_starttag(self, tag, attrs): + if tag == "li": + self.li = True + if self.li and tag == "a": + for att in attrs: + if att[0] == "href": + self.ahref = True + self.userpage = att[1] + + def handle_endtag(self, tag): + if self.li and tag == "a": + self.ahref = False + self.userpage = None + if tag == "li": + self.li = False + + def handle_data(self, data): + if self.li and self.ahref: + found = self.namepat.match(data) + if found: + user = found.group("user") + name = found.group("name") + if user and name and user+".html" == self.userpage: + self.usermap[user] = "%s <%s@%s>" % (name, user, self.defaultmail) + + def get_user_map(self): + f = urlopen(self.url) + userhtml = f.read().decode("UTF-8") + f.close() + self.feed(userhtml) + return self.usermap + + def get_user_map_file(self): + if not self.usermap: + self.get_user_map() + self.usermapfile = tempfile.mkstemp(suffix=".txt", prefix="usermap") + f = open(self.usermapfile[0], "w", encoding="UTF-8") + f.writelines("%s = %s\n" % user for user in sorted(self.usermap.items())) + f.close() + return self.usermapfile[1] + + def cleanup(self): + if os.path.exists(self.usermapfile[1]): + os.unlink(self.usermapfile[1]) + +def _map_user_names(): + if not usermap: + parser = UserTagParser() + usermap.update(parser.get_user_map()) + def get_changelog(pkgdirurl, another=None, svn=True, rev=None, size=None, submit=False, sort=False, template=None, macros=[], exported=None, - oldlog=False, create=False): + oldlog=False, create=False, fullnames=False): """Generates the changelog for a given package URL @another: a stream with the contents of a changelog to be merged with @@ -634,6 +692,9 @@ def get_changelog(pkgdirurl, another=None, svn=True, rev=None, size=None, """ newlog = StringIO() if svn: + if fullnames: + if not usermap: + _map_user_names() rawsvnlog = svn2rpm(pkgdirurl, rev=rev, size=size, submit=submit, template=template, macros=macros, exported=exported, create=create) newlog.write(rawsvnlog) @@ -648,19 +709,21 @@ def get_changelog(pkgdirurl, another=None, svn=True, rev=None, size=None, return newlog def specfile_svn2rpm(pkgdirurl, specfile, rev=None, size=None, - submit=False, sort=False, template=None, macros=[], exported=None, create=False): + submit=False, sort=False, template=None, macros=[], exported=None, create=False, fullnames=False): with open(specfile, encoding = 'utf-8') as fi: spec, oldchlog = split_spec_changelog(fi) another = None if config.getbool("log", "merge-spec", False): another = oldchlog sort = sort or config.getbool("log", "sort", False) + if fullnames: + _map_user_names() chlog = get_changelog(pkgdirurl, another=another, rev=rev, size=size, submit=submit, sort=sort, template=template, macros=macros, exported=exported, oldlog=True, create=create) with open(specfile, "w", encoding='utf-8') as fo: fo.writelines(spec) - fo.write("\n\n%changelog\n") + fo.write("\n%changelog\n") fo.writelines(chlog) if __name__ == "__main__": diff --git a/MgaRepo/rpmutil.py b/MgaRepo/rpmutil.py index 769167c..49ef07f 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 @@ -14,13 +15,38 @@ import glob import sys import os +def detectVCS(url): + if ':' in url: + protocol,uri = url.split(":") + if "svn" in protocol: + return SVN(url=url) + elif "git" in protocol: + return GIT(url=url) + elif "http" in protocol: + if uri.endswith(".git"): + return GIT(url=url) + elif "svn" in uri: + return SVN(url=url) + raise Error("Unknown protocol %s for %s" % (protocol, url)) + elif os.path.exists(url) and os.path.isdir(url): + while True: + url = os.path.abspath(url) + for vcs in (SVN, GIT): + vcsdir = os.path.join(url, vcs.vcs_dirname) + if os.path.exists(vcsdir) and os.path.isdir(vcsdir): + return vcs(path=url) + url = os.path.dirname(url) + if url == "/": + break + raise Error("No supported repository found at path: %s" % url) + def get_spec(pkgdirurl, targetdir=".", submit=False): - svn = SVN() + svn = detectVCS(pkgdirurl) tmpdir = tempfile.mktemp() 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,14 +59,9 @@ 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() + svn = detectVCS(url) info = svn.info2(url) if info is None: raise Error("can't fetch svn info about the URL: %s" % url) @@ -95,16 +116,21 @@ def get_srpm(pkgdirurl, template = None, macros = [], verbose = 0, - strict = False): - svn = SVN() + strict = False, + fullnames = False): + svn = detectVCS(pkgdirurl) 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": @@ -133,24 +159,29 @@ 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] - defs = rpm_macros_defs(macros) sourcecmd = config.get("helper", "rpmbuild", "rpmbuild") if packager: packager = " --define 'packager %s'" % packager + sourcecmd = config.get("helper", "rpmbuild", "rpmbuild") + args = [sourcecmd, "-bs", "--nodeps"] + for pair in rpmdefs: + args.extend(pair) + for pair in macros: + args.extend(("--define", "%s %s" % pair)) + args.append(spec) if svnlog: submit = not not revision try: log.specfile_svn2rpm(pkgdirurl, spec, revision, submit=submit, - template=template, macros=macros, exported=tmpdir) + template=template, macros=macros, exported=tmpdir, fullnames=fullnames) except: - 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)) + #cmd = [sourcecmd, topdir, builddir, rpmdir, sourcedir, specdir + execcmd(args) cp_srpms(revision, revname, geturl, targetdirs, srpmsdir, verbose) log.specfile_svn2rpm(pkgdirurl, spec, revision, submit=submit, template=template, macros=macros, exported=tmpdir, create=True) @@ -162,9 +193,15 @@ def get_srpm(pkgdirurl, if status != 0: raise Error("script %s failed" % script) - 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)) + 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 = cp_srpms(revision, revname, geturl, targetdirs, srpmsdir, verbose) @@ -175,7 +212,7 @@ def get_srpm(pkgdirurl, def patch_spec(pkgdirurl, patchfile, log=""): #FIXME use get_spec - svn = SVN() + svn = detectVCS(pkgdirurl) tmpdir = tempfile.mktemp() try: geturl = layout.checkout_url(pkgdirurl, append_path="SPECS") @@ -184,7 +221,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: @@ -195,7 +232,6 @@ def patch_spec(pkgdirurl, patchfile, log=""): def put_srpm(srpmfile, markrelease=False, striplog=True, branch=None, baseurl=None, baseold=None, logmsg=None, rename=True): - svn = SVN() srpm = SRPM(srpmfile) tmpdir = tempfile.mktemp() if baseurl: @@ -203,6 +239,7 @@ def put_srpm(srpmfile, markrelease=False, striplog=True, branch=None, else: pkgurl = layout.package_url(srpm.name, distro=branch, mirrored=False) + svn = detectVCS(pkgdirurl) print("Importing package to %s" % pkgurl) try: if srpm.epoch: @@ -264,9 +301,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 @@ -355,8 +392,106 @@ def put_srpm(srpmfile, markrelease=False, striplog=True, branch=None, log="Copying release %s-%s to releases/ directory." % (version, srpm.release)) +def build_rpm(build_cmd="b", + verbose=True, + rpmlint=True, + short_circuit=False, + packager = None, + installdeps = True, + use_dnf = False, + svnlog = False, + fullnames = True, + macros = [], + **rpmargs): + top = os.getcwd() + topdir = "_topdir %s" % top + builddir = "_builddir %s/%s" % (top, "BUILD") + rpmdir = "_rpmdir %s/%s" % (top, "RPMS") + sourcedir = "_sourcedir %s/%s" % (top, "SOURCES") + specdir = "_specdir %s/%s" % (top, "SPECS") + srcrpmdir = "_srcrpmdir %s/%s" % (top, "SRPMS") + patchdir = "_patchdir %s/%s" % (top, "SOURCES") + + build = os.path.join(top, "BUILD") + if not os.path.exists(build): + os.mkdir(build) + specsdir = os.path.join(top, "SPECS") + speclist = glob.glob(os.path.join(specsdir, "*.spec")) + if not speclist: + raise Error("no spec files found") + spec = speclist[0] + + # If we're building package with %changelog, we'll make a temporary + # copy of the spec file with %changelog applied that we'll use for + # building. This way we avoid modifying files in repository. + # TODO: implement support for external changelog in rpm + if svnlog: + vcs = detectVCS(top) + specsdir = tempfile.mkdtemp() + shutil.copy(spec, specsdir) + specdir = "_specdir "+specsdir + spec = os.path.join(specsdir,os.path.basename(spec)) + info = vcs.info2(top) + pkgdirurl = layout.remove_current(info["URL"]) + log.specfile_svn2rpm(pkgdirurl, spec, rev=None, submit=False, + template=None, macros=macros, exported=top, fullnames=fullnames) + + rpmdefs = [("--define", expr) for expr in (topdir, builddir, rpmdir, + sourcedir, specdir, srcrpmdir, patchdir)] + + if packager: + rpmdefs.append(("--define", "packager %s" % packager)) + + if rpmlint: + rpmdefs.append(("--define", "_build_pkgcheck_set %{_bindir}/rpmlint")) + + rpmbuild = config.get("helper", "rpmbuild", "rpmbuild") + args = [rpmbuild, spec] + if short_circuit: + args.append("--short-circuit") + for pair in rpmdefs: + args.extend(pair) + for pair in macros: + args.extend(("--define", "%s %s" % pair)) + args.extend(("--define", "_disable_source_fetch 0")) + args.extend(*rpmargs.values()) + os.environ["LC_ALL"] = "C" + # First check whether dependencies are satisfied + status, output = execcmd(*args + ["--nobuild"], show=verbose, collecterr=True, noerror=True) + if status: + if "error: Failed build dependencies:" in output: + if not installdeps: + raise Error("Automatic installation of dependencies disabled," + "aborting...") + else: + if verbose: + print("Installing missing build dependencies") + if use_dnf: + pkg_mgr_base = ["dnf"] + pkg_mgr_builddep = pkg_mgr_base + ["--assume-yes", "--setopt=install_weak_deps=False", "builddep"] + else: + pkg_mgr_base = ["urpmi"] + pkg_mgr_builddep = pkg_mgr_base + ["--auto", "--buildrequires", "--no-recommends"] + if os.getuid() != 0: + print("Trying to obtain privileges for installing build dependencies:") + sudocheck = ["sudo", "-l"] + pkg_mgr_base + status, output = execcmd(*sudocheck, collecter=True, noerror=True) + if status: + raise Error("%s\nFailed! Cannot proceed without, aborting..." + % output.splitlines()[-1]) + cmd_base = ["sudo"] + pkg_mgr_builddep + else: + cmd_base = pkg_mgr_builddep + cmd = cmd_base + [spec] + status, output = execcmd(*cmd, show=verbose, collecter=True, noerror=True) + + status, output = execcmd(*args + ["-b"+build_cmd], show=verbose) + if svnlog: + if os.path.isdir(specsdir): + shutil.rmtree(specsdir) + def create_package(pkgdirurl, log="", verbose=0): - svn = SVN() + svn = detectVCS(pkgdirurl) tmpdir = tempfile.mktemp() try: basename = layout.package_name(pkgdirurl) @@ -397,7 +532,7 @@ revision: %s return log def mark_release(pkgdirurl, version, release, revision): - svn = SVN() + svn = detectVCS(pkgdirurl) releasesurl = layout.checkout_url(pkgdirurl, releases=True) versionurl = "/".join([releasesurl, version]) releaseurl = "/".join([versionurl, release]) @@ -419,7 +554,7 @@ def mark_release(pkgdirurl, version, release, revision): log=markreleaselog) def check_changed(pkgdirurl, all=0, show=0, verbose=0): - svn = SVN() + svn = detectVCS(pkgdirurl) if all: baseurl = pkgdirurl packages = [] @@ -490,33 +625,48 @@ def checkout(pkgdirurl, path=None, revision=None, branch=None, distro=None, back if path is None: path = layout.package_name(pkgdirurl) mirror.info(current, write=True) - svn = SVN() + svn = detectVCS(pkgdirurl) svn.checkout(current, path, rev=revision, show=1) if not spec: binrepo.download_binaries(path) -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 - if basedir is None: - basedir = os.path.curdir - while not ispkgtopdir(basedir): - if os.path.abspath(basedir) == "/": - raise Error("can't find top package directories SOURCES and SPECS") - basedir = os.path.join(basedir, os.path.pardir) - if basedir.startswith("./"): - basedir = basedir[2:] - return basedir - -def ispkgtopdir(path=None): +def clone(pkgdirurl, path=None, revision=None, branch=None, distro=None, backports=None, + spec=False, fullnames = True, bindownload = True): + o_pkgdirurl = pkgdirurl + pkgdirurl = layout.package_url(o_pkgdirurl, distro=distro, backports=backports) + append = None + if spec: + append = "SPECS" + current = layout.checkout_url(pkgdirurl, branch=branch, backports=backports, + append_path=append) + if path is None: + path = layout.package_name(pkgdirurl) + mirror.info(current, write=True) + git = GIT() + git.clone(current, path, fullnames=fullnames, show=1) + if not spec and bindownload: + binrepo.download_binaries(path) + +def getpkgtopdir(basedir=os.path.curdir): + vcs = detectVCS(basedir) + if vcs: + basedir = os.path.relpath(vcs.get_topdir()) + if ispkgtopdir(basedir, vcs_dirname=vcs.vcs_dirname): + return basedir + raise Error("can't find top package directories SOURCES and SPECS") + +def ispkgtopdir(path=None, vcs_dirname=None): if path is None: path = os.getcwd() names = os.listdir(path) - return (".svn" in names and "SPECS" in names and "SOURCES" in names) + if not vcs_dirname: + vcs = detectVCS(path) + vcs_dirname = vcs.vcs_dirname + return (vcs_dirname in names and "SPECS" in names and "SOURCES" in names) def sync(dryrun=False, commit=False, download=False): - svn = SVN() topdir = getpkgtopdir() + svn = detectVCS(topdir) spath = binrepo.sources_path(topdir) binrepoentries = binrepo.parse_sources(spath) # run svn info because svn st does not complain when topdir is not an @@ -598,7 +748,7 @@ def sync(dryrun=False, commit=False, download=False): upload([path], commit=commit) def commit(target=".", message=None, logfile=None): - svn = SVN() + svn = detectVCS(target) status = svn.status(target, quiet=True) if not status: print("nothing to commit") @@ -633,39 +783,41 @@ def spec_sources(topdir): return sources def update(target=None): - svn = SVN() + vcs = None info = None - svn_target = None + vcs_target = None br_target = None if target: - svn_target = target + vcs_target = target else: top = getpkgtopdir() - svn_target = top + vcs_target = top br_target = top - if svn_target: - svn.update(svn_target, show=True) + if vcs_target: + vcs = detectVCS(vcs_target) + vcs.update(vcs_target, show=True) if br_target: - info = svn.info2(svn_target) - if not br_target and not svn_target: - raise Error("target not in SVN nor in binaries "\ - "repository: %s" % target) + if not vcs: + vcs = detectVCS(br_target) + info = vcs.info2(vcs_target) + if not br_target and not vcs_target: + raise Error("target not in %s nor in binaries "\ + "repository: %s" % (type(vcs).__name__,target)) url = info["URL"] binrepo.download_binaries(br_target) def upload(paths, commit=False): + topdir = getpkgtopdir() + svn = detectVCS(topdir) for path in paths: if os.path.isdir(path) or binrepo.is_binary(path): - topdir = getpkgtopdir() binrepo.upload_binary(topdir, os.path.basename(path)) binrepo.update_sources(topdir, added=[path]) if commit: - svn = SVN() silent = config.get("log", "ignore-string", "SILENT") message = "%s: new file %s" % (silent, path) svn.commit(binrepo.sources_path(topdir), log=message) else: - svn = SVN() svn.add(path, local=True) if commit: silent = config.get("log", "ignore-string", "SILENT") @@ -674,16 +826,15 @@ def upload(paths, commit=False): def delete(paths, commit=False): silent = config.get("log", "ignore-string", "SILENT") + topdir = getpkgtopdir() + svn = detectVCS(topdir) for path in paths: message = "%s: delete file %s" % (silent, path) if binrepo.is_binary(path): - topdir = getpkgtopdir() binrepo.update_sources(topdir, removed=[os.path.basename(path)]) if commit: - svn = SVN() svn.commit(binrepo.sources_path(topdir), log=message) else: - svn = SVN() svn.remove(path, local=True) if commit: svn.commit(path, log=message) @@ -692,7 +843,7 @@ def obsolete(pkgdirurl, branch=None, distro=None, backports=None, commit=False, o_pkgdirurl = pkgdirurl pkgdirurl = layout.package_url(o_pkgdirurl, distro=distro, backports=backports) pkgdest = layout.package_url(o_pkgdirurl, obsolete=True, backports=backports) - svn = SVN() + svn = detectVCS(pkgdirurl) svn.mv(pkgdirurl, pkgdest, message=log) if commit: svn.commit(path, log=log) @@ -727,7 +878,7 @@ def get_submit_info(path): if not os.path.isdir(os.path.join(path, ".svn")): raise Error("subversion directory not found") - svn = SVN() + svn = detectVCS(path) # Now, extract the package name. info = svn.info2(path) @@ -766,4 +917,25 @@ def get_submit_info(path): return name, url, max +def get_pkg_tag(tag, path=os.path.curdir, subpkg=None): + topdir = getpkgtopdir(path) + speclist = glob.glob(os.path.join(topdir, "SPECS", "*.spec")) + if not speclist: + raise Error("no spec files found") + specfile = speclist[0] + + pkg = rpm.spec(specfile) + if subpkg is None: + header = pkg.sourceHeader + elif isinstance(subpkg,int): + header = pkg.packages(subpkg) + else: + raise Error("Subpkg must be the index number of a package,"\ + "or None for source package") + + if isinstance(header[tag],bytes): + return header[tag].decode("utf8") + else: + return header[tag] + # vim:et:ts=4:sw=4 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..5f8851c 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,21 @@ 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): + vcs_dirname = ".svn" + vcs_name = "svn" + def __init__(self, path=None, url=None): + VCS.__init__(self, path, url) + 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 538eae8..64faaa1 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,89 @@ 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 ' - 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() + 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): + if len(cmd) is 1: + cmdargs = shlex.split(cmd[0]) else: - status = os.system(cmdstr) - output = "" + cmdargs = cmd[:] 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) + 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: + 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)) + stderr = subprocess.STDOUT + + verbose = config.getbool("global", "verbose", 0) if verbose: - print(output) - sys.stdout.write(output) - return status, output + print("cmd: " + str(cmd).lstrip("(").rstrip(")").replace("', '", "' '")) + + 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: + # Make sure to avoid buffer getting full. + # Otherwise if ie. using proc.wait() both python + # and the process will just hang + while proc.poll() is None: + if proc.stdout is not None: + output += proc.stdout.read(8192).decode('utf8') + # Make sure that we've emptied the buffer entirely + 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() diff --git a/create-srpm b/create-srpm index 497ecd6..a2418c2 100755 --- a/create-srpm +++ b/create-srpm @@ -10,7 +10,6 @@ import sys import os import pwd import optparse -import subprocess import urllib.parse import urllib.request, urllib.parse, urllib.error import rpm @@ -120,8 +119,7 @@ class CmdIface: upload_command.append(x) upload_command.append(targetname) upload_command.extend(uploadsrpms) - command = subprocess.list2cmdline(upload_command) - status, output = execcmd(command, noerror=1) + status, output = execcmd(upload_command, noerror=1) for srpm in uploadsrpms: if os.path.isfile(srpm): os.unlink(srpm) @@ -13,6 +13,7 @@ Tool to access and manage a package repository structure. Useful commands: co checkout a package + clone clone a package repository ci commit changes sync add-remove all file changes from the .spec submit submit a package for build diff --git a/mgarepo.conf b/mgarepo.conf index 932fa0d..bbb1d62 100644 --- a/mgarepo.conf +++ b/mgarepo.conf @@ -31,3 +31,7 @@ upload_host = binrepo.mageia.org host = maintdb.mageia.org url = http://maintdb.mageia.org/ +[github] +#login = someuser +#password = somepassword +organization = mdkcauldron @@ -1,5 +1,5 @@ #!/usr/bin/python3 -from distutils.core import setup +from setuptools import setup import sys import re @@ -27,7 +27,8 @@ setup(name="mgarepo", ("share/bash-completion/completions", ["bash-completion/mgarepo"]), ("/etc/", ["mgarepo.conf"]), - ("share/man/man8/", ["mgarepo.8"])] + ("share/man/man8/", ["mgarepo.8"])], + install_requires=['PyGithub>=1.26.0'] ) # vim:ts=4:sw=4:et |