aboutsummaryrefslogtreecommitdiffstats
path: root/RepSys/svn.py
diff options
context:
space:
mode:
Diffstat (limited to 'RepSys/svn.py')
-rw-r--r--RepSys/svn.py465
1 files changed, 127 insertions, 338 deletions
diff --git a/RepSys/svn.py b/RepSys/svn.py
index e83b072..0d49cf2 100644
--- a/RepSys/svn.py
+++ b/RepSys/svn.py
@@ -1,10 +1,15 @@
from RepSys import Error, config, pexpect
from RepSys.util import execcmd, get_auth
+
import sys
+import os
import re
import time
+import threading
+import tempfile
+import pysvn
-__all__ = ["SVN", "SVNLook", "SVNLogEntry"]
+__all__ = ["SVN", "Revision", "SVNLogEntry"]
class SVNLogEntry:
def __init__(self, revision, author, date):
@@ -17,345 +22,129 @@ class SVNLogEntry:
return cmp(self.date, other.date)
class SVN:
- def __init__(self, username=None, password=None, noauth=0, baseurl=None):
- self.noauth = noauth or (
- baseurl and (
- baseurl.startswith("file:") or
- baseurl.startswith("svn+ssh:")))
- if not self.noauth: # argh
- self.username, self.password = get_auth()
-
-
- def _execsvn(self, *args, **kwargs):
- cmdstr = "svn "+" ".join(args)
- if kwargs.get("local") or kwargs.get("noauth") or self.noauth:
- return execcmd(cmdstr, **kwargs)
- show = kwargs.get("show")
- noerror = kwargs.get("noerror")
- p = pexpect.spawn(cmdstr, timeout=1)
- p.setmaxread(1024)
- p.setecho(False)
- outlist = []
- while True:
- i = p.expect_exact([pexpect.EOF, pexpect.TIMEOUT,
- "username:", "password:",
- "(p)ermanently?",
- "Authorization failed"])
- if i == 0:
- if show and p.before:
- print p.before,
- outlist.append(p.before)
- break
- elif i == 1:
- if show and p.before:
- print p.before,
- outlist.append(p.before)
- elif i == 2:
- p.sendline(self.username)
- outlist = []
- elif i == 3:
- p.sendline(self.password)
- outlist = []
- elif i == 4:
- p.sendline("p")
- outlist = []
- elif i == 5:
- if not noerror:
- raise Error, "authorization failed"
- else:
- break
- while p.isalive():
+ _client = None
+ _client_lock = None
+ _current_message = None
+
+ def __init__(self):
+ self._client = pysvn.Client()
+ self._client_lock = threading.Lock()
+ self._client.callback_get_log_message = self._log_handler
+
+ def _log_handler(self):
+ if self._current_message is None:
+ raise ValueError, "No log message defined"
+ return True, self._current_message
+
+ def _get_log_message(self, received_kwargs):
+ message = received_kwargs.pop("log", None)
+ messagefile = received_kwargs.pop("logfile", None)
+ if messagefile and not message:
+ message = open(messagefile).read()
+ return message
+
+ def _make_wrapper(self, meth):
+ def wrapper(*args, **kwargs):
+ self._client_lock.acquire()
try:
- time.sleep(1)
- except (pexpect.TIMEOUT, pexpect.EOF):
- # Continue until the child dies
- pass
- status, output = p.exitstatus, "".join(outlist).strip()
- if status != 0 and not kwargs.get("noerror"):
- sys.stderr.write(cmdstr)
- sys.stderr.write("\n")
- sys.stderr.write(output)
- sys.stderr.write("\n")
- raise Error, "command failed: "+cmdstr
- return status, output
-
- def _execsvn_success(self, *args, **kwargs):
- status, output = self._execsvn(*args, **kwargs)
- return status == 0
-
- def _add_log(self, cmd_args, received_kwargs, optional=0):
- if (not optional or
- received_kwargs.has_key("log") or
- received_kwargs.has_key("logfile")):
- ret = received_kwargs.get("log")
- if ret is not None:
- cmd_args.append("-m '%s'" % ret)
- ret = received_kwargs.get("logfile")
- if ret is not None:
- cmd_args.append("-F '%s'" % ret)
-
- def _add_revision(self, cmd_args, received_kwargs, optional=0):
- if not optional or received_kwargs.has_key("rev"):
- ret = received_kwargs.get("rev")
- if isinstance(ret, basestring):
+ self._current_message = self._get_log_message(kwargs)
+ ignore_errors = kwargs.pop("noerror", None)
try:
- ret = int(ret)
- except ValueError:
- raise Error, "invalid revision provided"
- if ret:
- cmd_args.append("-r %d" % ret)
-
- def add(self, path, **kwargs):
- cmd = ["add", path]
- return self._execsvn_success(noauth=1, *cmd, **kwargs)
-
- def copy(self, pathfrom, pathto, **kwargs):
- cmd = ["copy", pathfrom, pathto]
- self._add_revision(cmd, kwargs, optional=1)
- self._add_log(cmd, kwargs)
- return self._execsvn_success(*cmd, **kwargs)
-
- def remove(self, path, force=0, **kwargs):
- cmd = ["remove", path]
- self._add_log(cmd, kwargs)
- if force:
- cmd.append("--force")
- return self._execsvn_success(*cmd, **kwargs)
-
- def mkdir(self, path, **kwargs):
- cmd = ["mkdir", path]
- self._add_log(cmd, kwargs)
- return self._execsvn_success(*cmd, **kwargs)
-
- def commit(self, path, **kwargs):
- cmd = ["commit", path]
- self._add_log(cmd, kwargs)
- return self._execsvn_success(*cmd, **kwargs)
-
- def 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 propset(self, propname, value, targets, **kwargs):
- cmd = ["propset", propname, "'%s'" % value, targets]
- return self._execsvn_success(*cmd, **kwargs)
-
- def propedit(self, propname, target, **kwargs):
- cmd = ["propedit", propname, target]
- if kwargs.get("rev"):
- cmd.append("--revprop")
- self._add_revision(cmd, kwargs)
- return self._execsvn_success(local=True, show=True, *cmd, **kwargs)
-
- def revision(self, path, **kwargs):
- cmd = ["info", path]
- status, output = self._execsvn(local=True, *cmd, **kwargs)
- if status == 0:
- for line in output.splitlines():
- if line.startswith("Revision: "):
- return int(line.split()[1])
- return None
-
- def info(self, path, **kwargs):
- cmd = ["info", path]
- status, output = self._execsvn(local=True, *cmd, **kwargs)
- if status == 0:
- return output.splitlines()
- return None
-
- def ls(self, path, **kwargs):
- cmd = ["ls", path]
- status, output = self._execsvn(*cmd, **kwargs)
- if status == 0:
- return output.split()
- return None
-
- def status(self, path, **kwargs):
- cmd = ["status", path]
- if kwargs.get("verbose"):
- cmd.append("-v")
- status, output = self._execsvn(*cmd, **kwargs)
- if status == 0:
- return [x.split() for x in output.splitlines()]
- return None
-
- def cleanup(self, path, **kwargs):
- cmd = ["cleanup", path]
- return self._execsvn_success(*cmd, **kwargs)
-
- def revert(self, path, **kwargs):
- cmd = ["revert", path]
- status, output = self._execsvn(*cmd, **kwargs)
- if status == 0:
- return [x.split() for x in output.split()]
- return None
-
- def update(self, path, **kwargs):
- cmd = ["update", path]
- self._add_revision(cmd, kwargs, optional=1)
- status, output = self._execsvn(*cmd, **kwargs)
- if status == 0:
- return [x.split() for x in output.split()]
- return None
-
- def merge(self, url1, url2=None, rev1=None, rev2=None, path=None, **kwargs):
- cmd = ["merge"]
- if rev1 and rev2 and not url2:
- cmd.append("-r")
- cmd.append("%s:%s" % (rev1, rev2))
- cmd.append(url1)
+ return meth(*args, **kwargs)
+ except pysvn.ClientError:
+ if not ignore_errors:
+ raise
+ return False
+ finally:
+ self._current_message = None
+ self._client_lock.release()
+ return wrapper
+
+ def __getattr__(self, attrname):
+ meth = getattr(self._client, attrname)
+ wrapper = self._make_wrapper(meth)
+ return wrapper
+
+ def revision(number=None, head=None):
+ if number is not None:
+ args = (pysvn.opt_revision_kind.number, number)
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", 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)$")
- logseparator = "-"*72
- linesleft = 0
- entry = None
- log = []
- emptyline = 0
- for line in output.splitlines():
- if emptyline:
- emptyline = 0
- continue
- line = line.rstrip()
- if linesleft == 0:
- if line != logseparator:
- m = revheader.match(line)
- if m:
- linesleft = int(m.group("lines"))
- timestr = " ".join(m.group("date").split()[:2])
- timetuple = time.strptime(timestr,
- "%Y-%m-%d %H:%M:%S")
- entry = SVNLogEntry(int(m.group("revision")),
- m.group("author"), timetuple)
- log.append(entry)
- emptyline = 1
- else:
- entry.lines.append(line)
- linesleft -= 1
- log.sort()
- log.reverse()
- return log
-
-class SVNLook:
- def __init__(self, repospath, txn=None, rev=None):
- self.repospath = repospath
- self.txn = txn
- self.rev = rev
-
- def _execsvnlook(self, cmd, *args, **kwargs):
- execcmd_args = ["svnlook", cmd, self.repospath]
- self._add_txnrev(execcmd_args, kwargs)
- execcmd_args += args
- execcmd_kwargs = {}
- keywords = ["show", "noerror"]
- for key in keywords:
- if kwargs.has_key(key):
- execcmd_kwargs[key] = kwargs[key]
- return execcmd(*execcmd_args, **execcmd_kwargs)
-
- def _add_txnrev(self, cmd_args, received_kwargs):
- if received_kwargs.has_key("txn"):
- txn = received_kwargs.get("txn")
- if txn is not None:
- cmd_args += ["-t", txn]
- elif self.txn is not None:
- cmd_args += ["-t", self.txn]
- if received_kwargs.has_key("rev"):
- rev = received_kwargs.get("rev")
- if rev is not None:
- cmd_args += ["-r", rev]
- elif self.rev is not None:
- cmd_args += ["-r", self.rev]
-
- def changed(self, **kwargs):
- status, output = self._execsvnlook("changed", **kwargs)
- if status != 0:
- return None
- changes = []
- for line in output.splitlines():
- line = line.rstrip()
- if not line:
- continue
- entry = [None, None, None]
- changedata, changeprop, path = None, None, None
- if line[0] != "_":
- changedata = line[0]
- if line[1] != " ":
- changeprop = line[1]
- path = line[4:]
- changes.append((changedata, changeprop, path))
- return changes
-
- def author(self, **kwargs):
- status, output = self._execsvnlook("author", **kwargs)
- if status != 0:
- return None
- return output.strip()
+ args = (pysvn.opt_revision_kind.head,)
+ return pysvn.Revision(*args)
+ revision = staticmethod(revision)
+
+ # this override method fixed the problem in pysvn's mkdir which
+ # requires a log_message parameter
+ def mkdir(self, path, log=None, **kwargs):
+ meth = self.__getattr__("mkdir")
+ # we can't raise an error because pysvn's mkdir will use
+ # log_message only if path is remote, but it *always* requires this
+ # parameter. Also, 'log' is never used.
+ log = log or "There's a silent bug in your code"
+ return meth(path, log, log=None, **kwargs)
+
+ def checkin(self, path, log, **kwargs):
+ # XXX use EDITOR when log empty
+ meth = self.__getattr__("checkin")
+ return meth(path, log, log=None, **kwargs)
+
+ def log(self, *args, **kwargs):
+ meth = self.__getattr__("log")
+ entries = meth(*args, **kwargs)
+ for entrydic in entries:
+ entry = SVNLogEntry(entrydic["revision"].number,
+ entrydic["author"],
+ time.localtime(entrydic["date"]))
+ entry.lines[:] = entrydic["message"].split("\n")
+ yield entry
+
+ def exists(self, path):
+ return self.ls(path, noerror=1) is not False
+
+ def _edit_message(self, message):
+ # argh!
+ editor = os.getenv("EDITOR", "vim")
+ fd, fpath = tempfile.mkstemp(prefix="repsys")
+ result = (False, None)
+ try:
+ f = os.fdopen(fd, "w")
+ f.write(message)
+ f.close()
+ lastchange = os.stat(fpath).st_mtime
+ while 1:
+ os.system("%s %s" % (editor, fpath))
+ newchange = os.stat(fpath).st_mtime
+ if newchange == lastchange:
+ print "Log message unchanged or not specified"
+ print "(a)bort, (c)ontinue, (e)dit"
+ choice = raw_input()
+ if not choice or choice[0] == 'e':
+ continue
+ elif choice[0] == 'a':
+ break
+ elif choice[0] == 'c':
+ pass # ignore and go ahead
+ newmessage = open(fpath).read()
+ result = (True, newmessage)
+ break
+ finally:
+ os.unlink(fpath)
+ return result
+
+ def propedit(self, propname, pkgdirurl, revision, revprop=False):
+ revision = self.revision(revision)
+ if revprop:
+ propget = self.revpropget
+ propset = self.revpropset
+ else:
+ propget = self.propget
+ propset = self.propset
+ revision, message = propget(propname, pkgdirurl, revision=revision)
+ changed, newmessage = self._edit_message(message)
+ try:
+ if changed:
+ propset(propname, newmessage, pkgdirurl, revision=revision)
+ except pysvn.ClientError, (msg,):
+ raise Error, msg
# vim:et:ts=4:sw=4