consolidate svn path implementations and tests into files named after the package namespaces.
--HG-- branch : trunk
This commit is contained in:
		
							parent
							
								
									f3fcb5e6d3
								
							
						
					
					
						commit
						5118821c10
					
				| 
						 | 
					@ -104,10 +104,10 @@ initpkg(__name__,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # path implementation
 | 
					    # path implementation
 | 
				
			||||||
    'path.__doc__'           : ('./path/__init__.py', '__doc__'),
 | 
					    'path.__doc__'           : ('./path/__init__.py', '__doc__'),
 | 
				
			||||||
    'path.svnwc'             : ('./path/svn/wccommand.py', 'SvnWCCommandPath'),
 | 
					    'path.svnwc'             : ('./path/svnwc.py', 'SvnWCCommandPath'),
 | 
				
			||||||
    'path.svnurl'            : ('./path/svn/urlcommand.py', 'SvnCommandPath'),
 | 
					    'path.svnurl'            : ('./path/svnurl.py', 'SvnCommandPath'),
 | 
				
			||||||
    'path.local'             : ('./path/local.py', 'LocalPath'),
 | 
					    'path.local'             : ('./path/local.py', 'LocalPath'),
 | 
				
			||||||
    'path.SvnAuth'           : ('./path/svn/svncommon.py', 'SvnAuth'),
 | 
					    'path.SvnAuth'           : ('./path/svnwc.py', 'SvnAuth'),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # some nice slightly magic APIs
 | 
					    # some nice slightly magic APIs
 | 
				
			||||||
    'magic.__doc__'          : ('./magic/__init__.py', '__doc__'),
 | 
					    'magic.__doc__'          : ('./magic/__init__.py', '__doc__'),
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1 +0,0 @@
 | 
				
			||||||
#
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,69 +0,0 @@
 | 
				
			||||||
"""
 | 
					 | 
				
			||||||
# generic cache mechanism for subversion-related structures
 | 
					 | 
				
			||||||
# XXX make mt-safe
 | 
					 | 
				
			||||||
"""
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import time
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
proplist = {}
 | 
					 | 
				
			||||||
info = {}
 | 
					 | 
				
			||||||
entries = {}
 | 
					 | 
				
			||||||
prop = {}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#-----------------------------------------------------------
 | 
					 | 
				
			||||||
# Caching latest repository revision and repo-paths
 | 
					 | 
				
			||||||
# (getting them is slow with the current implementations)
 | 
					 | 
				
			||||||
#
 | 
					 | 
				
			||||||
# XXX make mt-safe
 | 
					 | 
				
			||||||
#-----------------------------------------------------------
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class RepoEntry:
 | 
					 | 
				
			||||||
    def __init__(self, url, rev, timestamp):
 | 
					 | 
				
			||||||
        self.url = url
 | 
					 | 
				
			||||||
        self.rev = rev
 | 
					 | 
				
			||||||
        self.timestamp = timestamp
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def __str__(self):
 | 
					 | 
				
			||||||
        return "repo: %s;%s  %s" %(self.url, self.rev, self.timestamp)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class RepoCache:
 | 
					 | 
				
			||||||
    """ The Repocache manages discovered repository paths
 | 
					 | 
				
			||||||
    and their revisions.  If inside a timeout the cache
 | 
					 | 
				
			||||||
    will even return the revision of the root.
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    timeout = 20 # seconds after which we forget that we know the last revision
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def __init__(self):
 | 
					 | 
				
			||||||
        self.repos = []
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def clear(self):
 | 
					 | 
				
			||||||
        self.repos = []
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def put(self, url, rev, timestamp=None):
 | 
					 | 
				
			||||||
        if rev is None:
 | 
					 | 
				
			||||||
            return
 | 
					 | 
				
			||||||
        if timestamp is None:
 | 
					 | 
				
			||||||
            timestamp = time.time()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        for entry in self.repos:
 | 
					 | 
				
			||||||
            if url == entry.url:
 | 
					 | 
				
			||||||
                entry.timestamp = timestamp
 | 
					 | 
				
			||||||
                entry.rev = rev
 | 
					 | 
				
			||||||
                #print "set repo", entry
 | 
					 | 
				
			||||||
                break
 | 
					 | 
				
			||||||
        else:
 | 
					 | 
				
			||||||
            entry = RepoEntry(url, rev, timestamp)
 | 
					 | 
				
			||||||
            self.repos.append(entry)
 | 
					 | 
				
			||||||
            #print "appended repo", entry
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def get(self, url):
 | 
					 | 
				
			||||||
        now = time.time()
 | 
					 | 
				
			||||||
        for entry in self.repos:
 | 
					 | 
				
			||||||
            if url.startswith(entry.url):
 | 
					 | 
				
			||||||
                if now < entry.timestamp + self.timeout:
 | 
					 | 
				
			||||||
                    #print "returning immediate Etrny", entry
 | 
					 | 
				
			||||||
                    return entry.url, entry.rev
 | 
					 | 
				
			||||||
                return entry.url, -1
 | 
					 | 
				
			||||||
        return url, -1
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
repositories = RepoCache()
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,368 +0,0 @@
 | 
				
			||||||
"""
 | 
					 | 
				
			||||||
module with a base subversion path object.
 | 
					 | 
				
			||||||
"""
 | 
					 | 
				
			||||||
import os, sys, time, re, string
 | 
					 | 
				
			||||||
import py
 | 
					 | 
				
			||||||
from py.__.path import common
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
ALLOWED_CHARS = "_ -/\\=$.~+" #add characters as necessary when tested
 | 
					 | 
				
			||||||
if sys.platform == "win32":
 | 
					 | 
				
			||||||
    ALLOWED_CHARS += ":"
 | 
					 | 
				
			||||||
ALLOWED_CHARS_HOST = ALLOWED_CHARS + '@:'
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
def _getsvnversion(ver=[]):
 | 
					 | 
				
			||||||
    try:
 | 
					 | 
				
			||||||
        return ver[0]
 | 
					 | 
				
			||||||
    except IndexError:
 | 
					 | 
				
			||||||
        v = py.process.cmdexec("svn -q --version")
 | 
					 | 
				
			||||||
        v.strip()
 | 
					 | 
				
			||||||
        v = '.'.join(v.split('.')[:2])
 | 
					 | 
				
			||||||
        ver.append(v)
 | 
					 | 
				
			||||||
        return v
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
def _escape_helper(text):
 | 
					 | 
				
			||||||
    text = str(text)
 | 
					 | 
				
			||||||
    if py.std.sys.platform != 'win32':
 | 
					 | 
				
			||||||
        text = str(text).replace('$', '\\$')
 | 
					 | 
				
			||||||
    return text
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
def _check_for_bad_chars(text, allowed_chars=ALLOWED_CHARS):
 | 
					 | 
				
			||||||
    for c in str(text):
 | 
					 | 
				
			||||||
        if c.isalnum():
 | 
					 | 
				
			||||||
            continue
 | 
					 | 
				
			||||||
        if c in allowed_chars:
 | 
					 | 
				
			||||||
            continue
 | 
					 | 
				
			||||||
        return True
 | 
					 | 
				
			||||||
    return False
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
def checkbadchars(url):
 | 
					 | 
				
			||||||
    # (hpk) not quite sure about the exact purpose, guido w.? 
 | 
					 | 
				
			||||||
    proto, uri = url.split("://", 1)
 | 
					 | 
				
			||||||
    if proto != "file":
 | 
					 | 
				
			||||||
        host, uripath = uri.split('/', 1)
 | 
					 | 
				
			||||||
        # only check for bad chars in the non-protocol parts
 | 
					 | 
				
			||||||
        if (_check_for_bad_chars(host, ALLOWED_CHARS_HOST) \
 | 
					 | 
				
			||||||
            or _check_for_bad_chars(uripath, ALLOWED_CHARS)):
 | 
					 | 
				
			||||||
            raise ValueError("bad char in %r" % (url, ))
 | 
					 | 
				
			||||||
            
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#_______________________________________________________________
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class SvnPathBase(common.PathBase):
 | 
					 | 
				
			||||||
    """ Base implementation for SvnPath implementations. """
 | 
					 | 
				
			||||||
    sep = '/'
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def _geturl(self):
 | 
					 | 
				
			||||||
        return self.strpath
 | 
					 | 
				
			||||||
    url = property(_geturl, None, None, "url of this svn-path.")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def __str__(self):
 | 
					 | 
				
			||||||
        """ return a string representation (including rev-number) """
 | 
					 | 
				
			||||||
        return self.strpath
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def __hash__(self):
 | 
					 | 
				
			||||||
        return hash(self.strpath)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def new(self, **kw):
 | 
					 | 
				
			||||||
        """ create a modified version of this path. A 'rev' argument
 | 
					 | 
				
			||||||
            indicates a new revision.
 | 
					 | 
				
			||||||
            the following keyword arguments modify various path parts:
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
              http://host.com/repo/path/file.ext
 | 
					 | 
				
			||||||
              |-----------------------|          dirname
 | 
					 | 
				
			||||||
                                        |------| basename
 | 
					 | 
				
			||||||
                                        |--|     purebasename
 | 
					 | 
				
			||||||
                                            |--| ext
 | 
					 | 
				
			||||||
        """
 | 
					 | 
				
			||||||
        obj = object.__new__(self.__class__)
 | 
					 | 
				
			||||||
        obj.rev = kw.get('rev', self.rev)
 | 
					 | 
				
			||||||
        obj.auth = kw.get('auth', self.auth)
 | 
					 | 
				
			||||||
        dirname, basename, purebasename, ext = self._getbyspec(
 | 
					 | 
				
			||||||
             "dirname,basename,purebasename,ext")
 | 
					 | 
				
			||||||
        if 'basename' in kw:
 | 
					 | 
				
			||||||
            if 'purebasename' in kw or 'ext' in kw:
 | 
					 | 
				
			||||||
                raise ValueError("invalid specification %r" % kw)
 | 
					 | 
				
			||||||
        else:
 | 
					 | 
				
			||||||
            pb = kw.setdefault('purebasename', purebasename)
 | 
					 | 
				
			||||||
            ext = kw.setdefault('ext', ext)
 | 
					 | 
				
			||||||
            if ext and not ext.startswith('.'):
 | 
					 | 
				
			||||||
                ext = '.' + ext
 | 
					 | 
				
			||||||
            kw['basename'] = pb + ext
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        kw.setdefault('dirname', dirname)
 | 
					 | 
				
			||||||
        kw.setdefault('sep', self.sep)
 | 
					 | 
				
			||||||
        if kw['basename']:
 | 
					 | 
				
			||||||
            obj.strpath = "%(dirname)s%(sep)s%(basename)s" % kw
 | 
					 | 
				
			||||||
        else:
 | 
					 | 
				
			||||||
            obj.strpath = "%(dirname)s" % kw
 | 
					 | 
				
			||||||
        return obj
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def _getbyspec(self, spec):
 | 
					 | 
				
			||||||
        """ get specified parts of the path.  'arg' is a string
 | 
					 | 
				
			||||||
            with comma separated path parts. The parts are returned
 | 
					 | 
				
			||||||
            in exactly the order of the specification.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            you may specify the following parts:
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            http://host.com/repo/path/file.ext
 | 
					 | 
				
			||||||
            |-----------------------|          dirname
 | 
					 | 
				
			||||||
                                      |------| basename
 | 
					 | 
				
			||||||
                                      |--|     purebasename
 | 
					 | 
				
			||||||
                                          |--| ext
 | 
					 | 
				
			||||||
        """
 | 
					 | 
				
			||||||
        res = []
 | 
					 | 
				
			||||||
        parts = self.strpath.split(self.sep)
 | 
					 | 
				
			||||||
        for name in spec.split(','):
 | 
					 | 
				
			||||||
            name = name.strip()
 | 
					 | 
				
			||||||
            if name == 'dirname':
 | 
					 | 
				
			||||||
                res.append(self.sep.join(parts[:-1]))
 | 
					 | 
				
			||||||
            elif name == 'basename':
 | 
					 | 
				
			||||||
                res.append(parts[-1])
 | 
					 | 
				
			||||||
            else:
 | 
					 | 
				
			||||||
                basename = parts[-1]
 | 
					 | 
				
			||||||
                i = basename.rfind('.')
 | 
					 | 
				
			||||||
                if i == -1:
 | 
					 | 
				
			||||||
                    purebasename, ext = basename, ''
 | 
					 | 
				
			||||||
                else:
 | 
					 | 
				
			||||||
                    purebasename, ext = basename[:i], basename[i:]
 | 
					 | 
				
			||||||
                if name == 'purebasename':
 | 
					 | 
				
			||||||
                    res.append(purebasename)
 | 
					 | 
				
			||||||
                elif name == 'ext':
 | 
					 | 
				
			||||||
                    res.append(ext)
 | 
					 | 
				
			||||||
                else:
 | 
					 | 
				
			||||||
                    raise NameError, "Don't know part %r" % name
 | 
					 | 
				
			||||||
        return res
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def __eq__(self, other):
 | 
					 | 
				
			||||||
        """ return true if path and rev attributes each match """
 | 
					 | 
				
			||||||
        return (str(self) == str(other) and
 | 
					 | 
				
			||||||
               (self.rev == other.rev or self.rev == other.rev))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def __ne__(self, other):
 | 
					 | 
				
			||||||
        return not self == other
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def join(self, *args):
 | 
					 | 
				
			||||||
        """ return a new Path (with the same revision) which is composed
 | 
					 | 
				
			||||||
            of the self Path followed by 'args' path components.
 | 
					 | 
				
			||||||
        """
 | 
					 | 
				
			||||||
        if not args:
 | 
					 | 
				
			||||||
            return self
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        args = tuple([arg.strip(self.sep) for arg in args])
 | 
					 | 
				
			||||||
        parts = (self.strpath, ) + args
 | 
					 | 
				
			||||||
        newpath = self.__class__(self.sep.join(parts), self.rev, self.auth)
 | 
					 | 
				
			||||||
        return newpath
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def propget(self, name):
 | 
					 | 
				
			||||||
        """ return the content of the given property. """
 | 
					 | 
				
			||||||
        value = self._propget(name)
 | 
					 | 
				
			||||||
        return value
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def proplist(self):
 | 
					 | 
				
			||||||
        """ list all property names. """
 | 
					 | 
				
			||||||
        content = self._proplist()
 | 
					 | 
				
			||||||
        return content
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def listdir(self, fil=None, sort=None):
 | 
					 | 
				
			||||||
        """ list directory contents, possibly filter by the given fil func
 | 
					 | 
				
			||||||
            and possibly sorted.
 | 
					 | 
				
			||||||
        """
 | 
					 | 
				
			||||||
        if isinstance(fil, str):
 | 
					 | 
				
			||||||
            fil = common.FNMatcher(fil)
 | 
					 | 
				
			||||||
        nameinfo_seq = self._listdir_nameinfo()
 | 
					 | 
				
			||||||
        if len(nameinfo_seq) == 1:
 | 
					 | 
				
			||||||
            name, info = nameinfo_seq[0]
 | 
					 | 
				
			||||||
            if name == self.basename and info.kind == 'file':
 | 
					 | 
				
			||||||
                #if not self.check(dir=1):
 | 
					 | 
				
			||||||
                raise py.error.ENOTDIR(self)
 | 
					 | 
				
			||||||
        paths = self._make_path_tuple(nameinfo_seq)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if fil or sort:
 | 
					 | 
				
			||||||
            paths = filter(fil, paths)
 | 
					 | 
				
			||||||
            paths = isinstance(paths, list) and paths or list(paths)
 | 
					 | 
				
			||||||
            if callable(sort):
 | 
					 | 
				
			||||||
                paths.sort(sort)
 | 
					 | 
				
			||||||
            elif sort:
 | 
					 | 
				
			||||||
                paths.sort()
 | 
					 | 
				
			||||||
        return paths
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def info(self):
 | 
					 | 
				
			||||||
        """ return an Info structure with svn-provided information. """
 | 
					 | 
				
			||||||
        parent = self.dirpath()
 | 
					 | 
				
			||||||
        nameinfo_seq = parent._listdir_nameinfo()
 | 
					 | 
				
			||||||
        bn = self.basename
 | 
					 | 
				
			||||||
        for name, info in nameinfo_seq:
 | 
					 | 
				
			||||||
            if name == bn:
 | 
					 | 
				
			||||||
                return info
 | 
					 | 
				
			||||||
        raise py.error.ENOENT(self)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def size(self):
 | 
					 | 
				
			||||||
        """ Return the size of the file content of the Path. """
 | 
					 | 
				
			||||||
        return self.info().size
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def mtime(self):
 | 
					 | 
				
			||||||
        """ Return the last modification time of the file. """
 | 
					 | 
				
			||||||
        return self.info().mtime
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    # shared help methods
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def _escape(self, cmd):
 | 
					 | 
				
			||||||
        return _escape_helper(cmd)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def _make_path_tuple(self, nameinfo_seq):
 | 
					 | 
				
			||||||
        """ return a tuple of paths from a nameinfo-tuple sequence.
 | 
					 | 
				
			||||||
        """
 | 
					 | 
				
			||||||
        #assert self.rev is not None, "revision of %s should not be None here" % self
 | 
					 | 
				
			||||||
        res = []
 | 
					 | 
				
			||||||
        for name, info in nameinfo_seq:
 | 
					 | 
				
			||||||
            child = self.join(name)
 | 
					 | 
				
			||||||
            res.append(child)
 | 
					 | 
				
			||||||
        return tuple(res)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def _childmaxrev(self):
 | 
					 | 
				
			||||||
        """ return maximum revision number of childs (or self.rev if no childs) """
 | 
					 | 
				
			||||||
        rev = self.rev
 | 
					 | 
				
			||||||
        for name, info in self._listdir_nameinfo():
 | 
					 | 
				
			||||||
            rev = max(rev, info.created_rev)
 | 
					 | 
				
			||||||
        return rev
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    #def _getlatestrevision(self):
 | 
					 | 
				
			||||||
    #    """ return latest repo-revision for this path. """
 | 
					 | 
				
			||||||
    #    url = self.strpath
 | 
					 | 
				
			||||||
    #    path = self.__class__(url, None)
 | 
					 | 
				
			||||||
    #
 | 
					 | 
				
			||||||
    #    # we need a long walk to find the root-repo and revision
 | 
					 | 
				
			||||||
    #    while 1:
 | 
					 | 
				
			||||||
    #        try:
 | 
					 | 
				
			||||||
    #            rev = max(rev, path._childmaxrev())
 | 
					 | 
				
			||||||
    #            previous = path
 | 
					 | 
				
			||||||
    #            path = path.dirpath()
 | 
					 | 
				
			||||||
    #        except (IOError, process.cmdexec.Error):
 | 
					 | 
				
			||||||
    #            break
 | 
					 | 
				
			||||||
    #    if rev is None:
 | 
					 | 
				
			||||||
    #        raise IOError, "could not determine newest repo revision for %s" % self
 | 
					 | 
				
			||||||
    #    return rev
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    class Checkers(common.Checkers):
 | 
					 | 
				
			||||||
        def dir(self):
 | 
					 | 
				
			||||||
            try:
 | 
					 | 
				
			||||||
                return self.path.info().kind == 'dir'
 | 
					 | 
				
			||||||
            except py.error.Error:
 | 
					 | 
				
			||||||
                return self._listdirworks()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        def _listdirworks(self):
 | 
					 | 
				
			||||||
            try:
 | 
					 | 
				
			||||||
                self.path.listdir()
 | 
					 | 
				
			||||||
            except py.error.ENOENT:
 | 
					 | 
				
			||||||
                return False
 | 
					 | 
				
			||||||
            else:
 | 
					 | 
				
			||||||
                return True
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        def file(self):
 | 
					 | 
				
			||||||
            try:
 | 
					 | 
				
			||||||
                return self.path.info().kind == 'file'
 | 
					 | 
				
			||||||
            except py.error.ENOENT:
 | 
					 | 
				
			||||||
                return False
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        def exists(self):
 | 
					 | 
				
			||||||
            try:
 | 
					 | 
				
			||||||
                return self.path.info()
 | 
					 | 
				
			||||||
            except py.error.ENOENT:
 | 
					 | 
				
			||||||
                return self._listdirworks()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
def parse_apr_time(timestr):
 | 
					 | 
				
			||||||
    i = timestr.rfind('.')
 | 
					 | 
				
			||||||
    if i == -1:
 | 
					 | 
				
			||||||
        raise ValueError, "could not parse %s" % timestr
 | 
					 | 
				
			||||||
    timestr = timestr[:i]
 | 
					 | 
				
			||||||
    parsedtime = time.strptime(timestr, "%Y-%m-%dT%H:%M:%S")
 | 
					 | 
				
			||||||
    return time.mktime(parsedtime)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class PropListDict(dict):
 | 
					 | 
				
			||||||
    """ a Dictionary which fetches values (InfoSvnCommand instances) lazily"""
 | 
					 | 
				
			||||||
    def __init__(self, path, keynames):
 | 
					 | 
				
			||||||
        dict.__init__(self, [(x, None) for x in keynames])
 | 
					 | 
				
			||||||
        self.path = path
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def __getitem__(self, key):
 | 
					 | 
				
			||||||
        value = dict.__getitem__(self, key)
 | 
					 | 
				
			||||||
        if value is None:
 | 
					 | 
				
			||||||
            value = self.path.propget(key)
 | 
					 | 
				
			||||||
            dict.__setitem__(self, key, value)
 | 
					 | 
				
			||||||
        return value
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
def fixlocale():
 | 
					 | 
				
			||||||
    if sys.platform != 'win32':
 | 
					 | 
				
			||||||
        return 'LC_ALL=C '
 | 
					 | 
				
			||||||
    return ''
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# some nasty chunk of code to solve path and url conversion and quoting issues
 | 
					 | 
				
			||||||
ILLEGAL_CHARS = '* | \ / : < > ? \t \n \x0b \x0c \r'.split(' ')
 | 
					 | 
				
			||||||
if os.sep in ILLEGAL_CHARS:
 | 
					 | 
				
			||||||
    ILLEGAL_CHARS.remove(os.sep)
 | 
					 | 
				
			||||||
ISWINDOWS = sys.platform == 'win32'
 | 
					 | 
				
			||||||
_reg_allow_disk = re.compile(r'^([a-z]\:\\)?[^:]+$', re.I)
 | 
					 | 
				
			||||||
def _check_path(path):
 | 
					 | 
				
			||||||
    illegal = ILLEGAL_CHARS[:]
 | 
					 | 
				
			||||||
    sp = path.strpath
 | 
					 | 
				
			||||||
    if ISWINDOWS:
 | 
					 | 
				
			||||||
        illegal.remove(':')
 | 
					 | 
				
			||||||
        if not _reg_allow_disk.match(sp):
 | 
					 | 
				
			||||||
            raise ValueError('path may not contain a colon (:)')
 | 
					 | 
				
			||||||
    for char in sp:
 | 
					 | 
				
			||||||
        if char not in string.printable or char in illegal:
 | 
					 | 
				
			||||||
            raise ValueError('illegal character %r in path' % (char,))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
def path_to_fspath(path, addat=True):
 | 
					 | 
				
			||||||
    _check_path(path)
 | 
					 | 
				
			||||||
    sp = path.strpath
 | 
					 | 
				
			||||||
    if addat and path.rev != -1:
 | 
					 | 
				
			||||||
        sp = '%s@%s' % (sp, path.rev)
 | 
					 | 
				
			||||||
    elif addat:
 | 
					 | 
				
			||||||
        sp = '%s@HEAD' % (sp,)
 | 
					 | 
				
			||||||
    return sp
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
def url_from_path(path):
 | 
					 | 
				
			||||||
    fspath = path_to_fspath(path, False)
 | 
					 | 
				
			||||||
    quote = py.std.urllib.quote
 | 
					 | 
				
			||||||
    if ISWINDOWS:
 | 
					 | 
				
			||||||
        match = _reg_allow_disk.match(fspath)
 | 
					 | 
				
			||||||
        fspath = fspath.replace('\\', '/')
 | 
					 | 
				
			||||||
        if match.group(1):
 | 
					 | 
				
			||||||
            fspath = '/%s%s' % (match.group(1).replace('\\', '/'),
 | 
					 | 
				
			||||||
                                quote(fspath[len(match.group(1)):]))
 | 
					 | 
				
			||||||
        else:
 | 
					 | 
				
			||||||
            fspath = quote(fspath)
 | 
					 | 
				
			||||||
    else:
 | 
					 | 
				
			||||||
        fspath = quote(fspath)
 | 
					 | 
				
			||||||
    if path.rev != -1:
 | 
					 | 
				
			||||||
        fspath = '%s@%s' % (fspath, path.rev)
 | 
					 | 
				
			||||||
    else:
 | 
					 | 
				
			||||||
        fspath = '%s@HEAD' % (fspath,)
 | 
					 | 
				
			||||||
    return 'file://%s' % (fspath,)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class SvnAuth(object):
 | 
					 | 
				
			||||||
    """ container for auth information for Subversion """
 | 
					 | 
				
			||||||
    def __init__(self, username, password, cache_auth=True, interactive=True):
 | 
					 | 
				
			||||||
        self.username = username
 | 
					 | 
				
			||||||
        self.password = password
 | 
					 | 
				
			||||||
        self.cache_auth = cache_auth
 | 
					 | 
				
			||||||
        self.interactive = interactive
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def makecmdoptions(self):
 | 
					 | 
				
			||||||
        uname = self.username.replace('"', '\\"')
 | 
					 | 
				
			||||||
        passwd = self.password.replace('"', '\\"')
 | 
					 | 
				
			||||||
        ret = []
 | 
					 | 
				
			||||||
        if uname:
 | 
					 | 
				
			||||||
            ret.append('--username="%s"' % (uname,))
 | 
					 | 
				
			||||||
        if passwd:
 | 
					 | 
				
			||||||
            ret.append('--password="%s"' % (passwd,))
 | 
					 | 
				
			||||||
        if not self.cache_auth:
 | 
					 | 
				
			||||||
            ret.append('--no-auth-cache')
 | 
					 | 
				
			||||||
        if not self.interactive:
 | 
					 | 
				
			||||||
            ret.append('--non-interactive')
 | 
					 | 
				
			||||||
        return ' '.join(ret)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def __str__(self):
 | 
					 | 
				
			||||||
        return "<SvnAuth username=%s ...>" %(self.username,)
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1 +0,0 @@
 | 
				
			||||||
#
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,25 +0,0 @@
 | 
				
			||||||
import py
 | 
					 | 
				
			||||||
from py.__.path.svn.testing.svntestbase import make_test_repo, getsvnbin
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class TestMakeRepo(object):
 | 
					 | 
				
			||||||
    def setup_class(cls):
 | 
					 | 
				
			||||||
        getsvnbin()
 | 
					 | 
				
			||||||
        cls.repo = make_test_repo()
 | 
					 | 
				
			||||||
        cls.wc = py.path.svnwc(py.test.ensuretemp("test-wc").join("wc"))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def test_empty_checkout(self):
 | 
					 | 
				
			||||||
        self.wc.checkout(self.repo)
 | 
					 | 
				
			||||||
        assert len(self.wc.listdir()) == 0
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def test_commit(self):
 | 
					 | 
				
			||||||
        self.wc.checkout(self.repo)
 | 
					 | 
				
			||||||
        p = self.wc.join("a_file")
 | 
					 | 
				
			||||||
        p.write("test file")
 | 
					 | 
				
			||||||
        p.add()
 | 
					 | 
				
			||||||
        rev = self.wc.commit("some test")
 | 
					 | 
				
			||||||
        assert p.info().rev == 1
 | 
					 | 
				
			||||||
        assert rev == 1
 | 
					 | 
				
			||||||
        rev = self.wc.commit()
 | 
					 | 
				
			||||||
        assert rev is None
 | 
					 | 
				
			||||||
        
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,16 +1,14 @@
 | 
				
			||||||
"""
 | 
					"""
 | 
				
			||||||
 | 
					 | 
				
			||||||
module defining a subversion path object based on the external
 | 
					module defining a subversion path object based on the external
 | 
				
			||||||
command 'svn'. This modules aims to work with svn 1.3 and higher
 | 
					command 'svn'. This modules aims to work with svn 1.3 and higher
 | 
				
			||||||
but might also interact well with earlier versions. 
 | 
					but might also interact well with earlier versions. 
 | 
				
			||||||
 | 
					 | 
				
			||||||
"""
 | 
					"""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import os, sys, time, re, calendar
 | 
					import os, sys, time, re
 | 
				
			||||||
import py
 | 
					import py
 | 
				
			||||||
from py import path, process
 | 
					from py import path, process
 | 
				
			||||||
from py.__.path import common
 | 
					from py.__.path import common
 | 
				
			||||||
from py.__.path.svn import svncommon
 | 
					from py.__.path import svnwc as svncommon
 | 
				
			||||||
from py.__.misc.cache import BuildcostAccessCache, AgingCache
 | 
					from py.__.misc.cache import BuildcostAccessCache, AgingCache
 | 
				
			||||||
 | 
					
 | 
				
			||||||
DEBUG=False 
 | 
					DEBUG=False 
 | 
				
			||||||
| 
						 | 
					@ -253,10 +251,10 @@ rev_end is the last revision (defaulting to HEAD).
 | 
				
			||||||
if verbose is True, then the LogEntry instances also know which files changed.
 | 
					if verbose is True, then the LogEntry instances also know which files changed.
 | 
				
			||||||
"""
 | 
					"""
 | 
				
			||||||
        assert self.check() #make it simpler for the pipe
 | 
					        assert self.check() #make it simpler for the pipe
 | 
				
			||||||
        rev_start = rev_start is None and _Head or rev_start
 | 
					        rev_start = rev_start is None and "HEAD" or rev_start
 | 
				
			||||||
        rev_end = rev_end is None and _Head or rev_end
 | 
					        rev_end = rev_end is None and "HEAD" or rev_end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if rev_start is _Head and rev_end == 1:
 | 
					        if rev_start == "HEAD" and rev_end == 1:
 | 
				
			||||||
            rev_opt = ""
 | 
					            rev_opt = ""
 | 
				
			||||||
        else:
 | 
					        else:
 | 
				
			||||||
            rev_opt = "-r %s:%s" % (rev_start, rev_end)
 | 
					            rev_opt = "-r %s:%s" % (rev_start, rev_end)
 | 
				
			||||||
| 
						 | 
					@ -268,7 +266,7 @@ if verbose is True, then the LogEntry instances also know which files changed.
 | 
				
			||||||
        result = []
 | 
					        result = []
 | 
				
			||||||
        for logentry in filter(None, tree.firstChild.childNodes):
 | 
					        for logentry in filter(None, tree.firstChild.childNodes):
 | 
				
			||||||
            if logentry.nodeType == logentry.ELEMENT_NODE:
 | 
					            if logentry.nodeType == logentry.ELEMENT_NODE:
 | 
				
			||||||
                result.append(LogEntry(logentry))
 | 
					                result.append(svncommon.LogEntry(logentry))
 | 
				
			||||||
        return result
 | 
					        return result
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#01234567890123456789012345678901234567890123467
 | 
					#01234567890123456789012345678901234567890123467
 | 
				
			||||||
| 
						 | 
					@ -313,6 +311,7 @@ def parse_time_with_missing_year(timestr):
 | 
				
			||||||
    the svn output doesn't show the year makes the 'timestr'
 | 
					    the svn output doesn't show the year makes the 'timestr'
 | 
				
			||||||
    ambigous.
 | 
					    ambigous.
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
 | 
					    import calendar
 | 
				
			||||||
    t_now = time.gmtime()
 | 
					    t_now = time.gmtime()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    tparts = timestr.split()
 | 
					    tparts = timestr.split()
 | 
				
			||||||
| 
						 | 
					@ -341,31 +340,3 @@ class PathEntry:
 | 
				
			||||||
            if self.copyfrom_path:
 | 
					            if self.copyfrom_path:
 | 
				
			||||||
                self.copyfrom_rev = int(ppart.getAttribute('copyfrom-rev'))
 | 
					                self.copyfrom_rev = int(ppart.getAttribute('copyfrom-rev'))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class LogEntry:
 | 
					 | 
				
			||||||
    def __init__(self, logentry):
 | 
					 | 
				
			||||||
        self.rev = int(logentry.getAttribute('revision'))
 | 
					 | 
				
			||||||
        for lpart in filter(None, logentry.childNodes):
 | 
					 | 
				
			||||||
            if lpart.nodeType == lpart.ELEMENT_NODE:
 | 
					 | 
				
			||||||
                if lpart.nodeName == u'author':
 | 
					 | 
				
			||||||
                    self.author = lpart.firstChild.nodeValue.encode('UTF-8')
 | 
					 | 
				
			||||||
                elif lpart.nodeName == u'msg':
 | 
					 | 
				
			||||||
                    if lpart.firstChild:
 | 
					 | 
				
			||||||
                        self.msg = lpart.firstChild.nodeValue.encode('UTF-8')
 | 
					 | 
				
			||||||
                    else:
 | 
					 | 
				
			||||||
                        self.msg = ''
 | 
					 | 
				
			||||||
                elif lpart.nodeName == u'date':
 | 
					 | 
				
			||||||
                    #2003-07-29T20:05:11.598637Z
 | 
					 | 
				
			||||||
                    timestr = lpart.firstChild.nodeValue.encode('UTF-8')
 | 
					 | 
				
			||||||
                    self.date = svncommon.parse_apr_time(timestr)
 | 
					 | 
				
			||||||
                elif lpart.nodeName == u'paths':
 | 
					 | 
				
			||||||
                    self.strpaths = []
 | 
					 | 
				
			||||||
                    for ppart in filter(None, lpart.childNodes):
 | 
					 | 
				
			||||||
                        if ppart.nodeType == ppart.ELEMENT_NODE:
 | 
					 | 
				
			||||||
                            self.strpaths.append(PathEntry(ppart))
 | 
					 | 
				
			||||||
    def __repr__(self):
 | 
					 | 
				
			||||||
        return '<Logentry rev=%d author=%s date=%s>' % (
 | 
					 | 
				
			||||||
            self.rev, self.author, self.date)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
_Head = "HEAD" 
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,20 +1,442 @@
 | 
				
			||||||
"""
 | 
					"""
 | 
				
			||||||
 | 
					 | 
				
			||||||
svn-Command based Implementation of a Subversion WorkingCopy Path.
 | 
					svn-Command based Implementation of a Subversion WorkingCopy Path.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  SvnWCCommandPath  is the main class.
 | 
					  SvnWCCommandPath  is the main class.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  SvnWC is an alias to this class.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
"""
 | 
					"""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import os, sys, time, re, calendar
 | 
					import os, sys, time, re, calendar
 | 
				
			||||||
import py
 | 
					import py
 | 
				
			||||||
from py.__.path import common
 | 
					from py.__.path import common
 | 
				
			||||||
from py.__.path.svn import cache
 | 
					 | 
				
			||||||
from py.__.path.svn import svncommon
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
DEBUG = 0
 | 
					#-----------------------------------------------------------
 | 
				
			||||||
 | 
					# Caching latest repository revision and repo-paths
 | 
				
			||||||
 | 
					# (getting them is slow with the current implementations)
 | 
				
			||||||
 | 
					#
 | 
				
			||||||
 | 
					# XXX make mt-safe
 | 
				
			||||||
 | 
					#-----------------------------------------------------------
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class cache:
 | 
				
			||||||
 | 
					    proplist = {}
 | 
				
			||||||
 | 
					    info = {}
 | 
				
			||||||
 | 
					    entries = {}
 | 
				
			||||||
 | 
					    prop = {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class RepoEntry:
 | 
				
			||||||
 | 
					    def __init__(self, url, rev, timestamp):
 | 
				
			||||||
 | 
					        self.url = url
 | 
				
			||||||
 | 
					        self.rev = rev
 | 
				
			||||||
 | 
					        self.timestamp = timestamp
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __str__(self):
 | 
				
			||||||
 | 
					        return "repo: %s;%s  %s" %(self.url, self.rev, self.timestamp)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class RepoCache:
 | 
				
			||||||
 | 
					    """ The Repocache manages discovered repository paths
 | 
				
			||||||
 | 
					    and their revisions.  If inside a timeout the cache
 | 
				
			||||||
 | 
					    will even return the revision of the root.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    timeout = 20 # seconds after which we forget that we know the last revision
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __init__(self):
 | 
				
			||||||
 | 
					        self.repos = []
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def clear(self):
 | 
				
			||||||
 | 
					        self.repos = []
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def put(self, url, rev, timestamp=None):
 | 
				
			||||||
 | 
					        if rev is None:
 | 
				
			||||||
 | 
					            return
 | 
				
			||||||
 | 
					        if timestamp is None:
 | 
				
			||||||
 | 
					            timestamp = time.time()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        for entry in self.repos:
 | 
				
			||||||
 | 
					            if url == entry.url:
 | 
				
			||||||
 | 
					                entry.timestamp = timestamp
 | 
				
			||||||
 | 
					                entry.rev = rev
 | 
				
			||||||
 | 
					                #print "set repo", entry
 | 
				
			||||||
 | 
					                break
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            entry = RepoEntry(url, rev, timestamp)
 | 
				
			||||||
 | 
					            self.repos.append(entry)
 | 
				
			||||||
 | 
					            #print "appended repo", entry
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def get(self, url):
 | 
				
			||||||
 | 
					        now = time.time()
 | 
				
			||||||
 | 
					        for entry in self.repos:
 | 
				
			||||||
 | 
					            if url.startswith(entry.url):
 | 
				
			||||||
 | 
					                if now < entry.timestamp + self.timeout:
 | 
				
			||||||
 | 
					                    #print "returning immediate Etrny", entry
 | 
				
			||||||
 | 
					                    return entry.url, entry.rev
 | 
				
			||||||
 | 
					                return entry.url, -1
 | 
				
			||||||
 | 
					        return url, -1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					repositories = RepoCache()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# svn support code 
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					ALLOWED_CHARS = "_ -/\\=$.~+" #add characters as necessary when tested
 | 
				
			||||||
 | 
					if sys.platform == "win32":
 | 
				
			||||||
 | 
					    ALLOWED_CHARS += ":"
 | 
				
			||||||
 | 
					ALLOWED_CHARS_HOST = ALLOWED_CHARS + '@:'
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					def _getsvnversion(ver=[]):
 | 
				
			||||||
 | 
					    try:
 | 
				
			||||||
 | 
					        return ver[0]
 | 
				
			||||||
 | 
					    except IndexError:
 | 
				
			||||||
 | 
					        v = py.process.cmdexec("svn -q --version")
 | 
				
			||||||
 | 
					        v.strip()
 | 
				
			||||||
 | 
					        v = '.'.join(v.split('.')[:2])
 | 
				
			||||||
 | 
					        ver.append(v)
 | 
				
			||||||
 | 
					        return v
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def _escape_helper(text):
 | 
				
			||||||
 | 
					    text = str(text)
 | 
				
			||||||
 | 
					    if py.std.sys.platform != 'win32':
 | 
				
			||||||
 | 
					        text = str(text).replace('$', '\\$')
 | 
				
			||||||
 | 
					    return text
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def _check_for_bad_chars(text, allowed_chars=ALLOWED_CHARS):
 | 
				
			||||||
 | 
					    for c in str(text):
 | 
				
			||||||
 | 
					        if c.isalnum():
 | 
				
			||||||
 | 
					            continue
 | 
				
			||||||
 | 
					        if c in allowed_chars:
 | 
				
			||||||
 | 
					            continue
 | 
				
			||||||
 | 
					        return True
 | 
				
			||||||
 | 
					    return False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def checkbadchars(url):
 | 
				
			||||||
 | 
					    # (hpk) not quite sure about the exact purpose, guido w.? 
 | 
				
			||||||
 | 
					    proto, uri = url.split("://", 1)
 | 
				
			||||||
 | 
					    if proto != "file":
 | 
				
			||||||
 | 
					        host, uripath = uri.split('/', 1)
 | 
				
			||||||
 | 
					        # only check for bad chars in the non-protocol parts
 | 
				
			||||||
 | 
					        if (_check_for_bad_chars(host, ALLOWED_CHARS_HOST) \
 | 
				
			||||||
 | 
					            or _check_for_bad_chars(uripath, ALLOWED_CHARS)):
 | 
				
			||||||
 | 
					            raise ValueError("bad char in %r" % (url, ))
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#_______________________________________________________________
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class SvnPathBase(common.PathBase):
 | 
				
			||||||
 | 
					    """ Base implementation for SvnPath implementations. """
 | 
				
			||||||
 | 
					    sep = '/'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _geturl(self):
 | 
				
			||||||
 | 
					        return self.strpath
 | 
				
			||||||
 | 
					    url = property(_geturl, None, None, "url of this svn-path.")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __str__(self):
 | 
				
			||||||
 | 
					        """ return a string representation (including rev-number) """
 | 
				
			||||||
 | 
					        return self.strpath
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __hash__(self):
 | 
				
			||||||
 | 
					        return hash(self.strpath)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def new(self, **kw):
 | 
				
			||||||
 | 
					        """ create a modified version of this path. A 'rev' argument
 | 
				
			||||||
 | 
					            indicates a new revision.
 | 
				
			||||||
 | 
					            the following keyword arguments modify various path parts:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					              http://host.com/repo/path/file.ext
 | 
				
			||||||
 | 
					              |-----------------------|          dirname
 | 
				
			||||||
 | 
					                                        |------| basename
 | 
				
			||||||
 | 
					                                        |--|     purebasename
 | 
				
			||||||
 | 
					                                            |--| ext
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        obj = object.__new__(self.__class__)
 | 
				
			||||||
 | 
					        obj.rev = kw.get('rev', self.rev)
 | 
				
			||||||
 | 
					        obj.auth = kw.get('auth', self.auth)
 | 
				
			||||||
 | 
					        dirname, basename, purebasename, ext = self._getbyspec(
 | 
				
			||||||
 | 
					             "dirname,basename,purebasename,ext")
 | 
				
			||||||
 | 
					        if 'basename' in kw:
 | 
				
			||||||
 | 
					            if 'purebasename' in kw or 'ext' in kw:
 | 
				
			||||||
 | 
					                raise ValueError("invalid specification %r" % kw)
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            pb = kw.setdefault('purebasename', purebasename)
 | 
				
			||||||
 | 
					            ext = kw.setdefault('ext', ext)
 | 
				
			||||||
 | 
					            if ext and not ext.startswith('.'):
 | 
				
			||||||
 | 
					                ext = '.' + ext
 | 
				
			||||||
 | 
					            kw['basename'] = pb + ext
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        kw.setdefault('dirname', dirname)
 | 
				
			||||||
 | 
					        kw.setdefault('sep', self.sep)
 | 
				
			||||||
 | 
					        if kw['basename']:
 | 
				
			||||||
 | 
					            obj.strpath = "%(dirname)s%(sep)s%(basename)s" % kw
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            obj.strpath = "%(dirname)s" % kw
 | 
				
			||||||
 | 
					        return obj
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _getbyspec(self, spec):
 | 
				
			||||||
 | 
					        """ get specified parts of the path.  'arg' is a string
 | 
				
			||||||
 | 
					            with comma separated path parts. The parts are returned
 | 
				
			||||||
 | 
					            in exactly the order of the specification.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            you may specify the following parts:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            http://host.com/repo/path/file.ext
 | 
				
			||||||
 | 
					            |-----------------------|          dirname
 | 
				
			||||||
 | 
					                                      |------| basename
 | 
				
			||||||
 | 
					                                      |--|     purebasename
 | 
				
			||||||
 | 
					                                          |--| ext
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        res = []
 | 
				
			||||||
 | 
					        parts = self.strpath.split(self.sep)
 | 
				
			||||||
 | 
					        for name in spec.split(','):
 | 
				
			||||||
 | 
					            name = name.strip()
 | 
				
			||||||
 | 
					            if name == 'dirname':
 | 
				
			||||||
 | 
					                res.append(self.sep.join(parts[:-1]))
 | 
				
			||||||
 | 
					            elif name == 'basename':
 | 
				
			||||||
 | 
					                res.append(parts[-1])
 | 
				
			||||||
 | 
					            else:
 | 
				
			||||||
 | 
					                basename = parts[-1]
 | 
				
			||||||
 | 
					                i = basename.rfind('.')
 | 
				
			||||||
 | 
					                if i == -1:
 | 
				
			||||||
 | 
					                    purebasename, ext = basename, ''
 | 
				
			||||||
 | 
					                else:
 | 
				
			||||||
 | 
					                    purebasename, ext = basename[:i], basename[i:]
 | 
				
			||||||
 | 
					                if name == 'purebasename':
 | 
				
			||||||
 | 
					                    res.append(purebasename)
 | 
				
			||||||
 | 
					                elif name == 'ext':
 | 
				
			||||||
 | 
					                    res.append(ext)
 | 
				
			||||||
 | 
					                else:
 | 
				
			||||||
 | 
					                    raise NameError, "Don't know part %r" % name
 | 
				
			||||||
 | 
					        return res
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __eq__(self, other):
 | 
				
			||||||
 | 
					        """ return true if path and rev attributes each match """
 | 
				
			||||||
 | 
					        return (str(self) == str(other) and
 | 
				
			||||||
 | 
					               (self.rev == other.rev or self.rev == other.rev))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __ne__(self, other):
 | 
				
			||||||
 | 
					        return not self == other
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def join(self, *args):
 | 
				
			||||||
 | 
					        """ return a new Path (with the same revision) which is composed
 | 
				
			||||||
 | 
					            of the self Path followed by 'args' path components.
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        if not args:
 | 
				
			||||||
 | 
					            return self
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        args = tuple([arg.strip(self.sep) for arg in args])
 | 
				
			||||||
 | 
					        parts = (self.strpath, ) + args
 | 
				
			||||||
 | 
					        newpath = self.__class__(self.sep.join(parts), self.rev, self.auth)
 | 
				
			||||||
 | 
					        return newpath
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def propget(self, name):
 | 
				
			||||||
 | 
					        """ return the content of the given property. """
 | 
				
			||||||
 | 
					        value = self._propget(name)
 | 
				
			||||||
 | 
					        return value
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def proplist(self):
 | 
				
			||||||
 | 
					        """ list all property names. """
 | 
				
			||||||
 | 
					        content = self._proplist()
 | 
				
			||||||
 | 
					        return content
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def listdir(self, fil=None, sort=None):
 | 
				
			||||||
 | 
					        """ list directory contents, possibly filter by the given fil func
 | 
				
			||||||
 | 
					            and possibly sorted.
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        if isinstance(fil, str):
 | 
				
			||||||
 | 
					            fil = common.FNMatcher(fil)
 | 
				
			||||||
 | 
					        nameinfo_seq = self._listdir_nameinfo()
 | 
				
			||||||
 | 
					        if len(nameinfo_seq) == 1:
 | 
				
			||||||
 | 
					            name, info = nameinfo_seq[0]
 | 
				
			||||||
 | 
					            if name == self.basename and info.kind == 'file':
 | 
				
			||||||
 | 
					                #if not self.check(dir=1):
 | 
				
			||||||
 | 
					                raise py.error.ENOTDIR(self)
 | 
				
			||||||
 | 
					        paths = self._make_path_tuple(nameinfo_seq)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if fil or sort:
 | 
				
			||||||
 | 
					            paths = filter(fil, paths)
 | 
				
			||||||
 | 
					            paths = isinstance(paths, list) and paths or list(paths)
 | 
				
			||||||
 | 
					            if callable(sort):
 | 
				
			||||||
 | 
					                paths.sort(sort)
 | 
				
			||||||
 | 
					            elif sort:
 | 
				
			||||||
 | 
					                paths.sort()
 | 
				
			||||||
 | 
					        return paths
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def info(self):
 | 
				
			||||||
 | 
					        """ return an Info structure with svn-provided information. """
 | 
				
			||||||
 | 
					        parent = self.dirpath()
 | 
				
			||||||
 | 
					        nameinfo_seq = parent._listdir_nameinfo()
 | 
				
			||||||
 | 
					        bn = self.basename
 | 
				
			||||||
 | 
					        for name, info in nameinfo_seq:
 | 
				
			||||||
 | 
					            if name == bn:
 | 
				
			||||||
 | 
					                return info
 | 
				
			||||||
 | 
					        raise py.error.ENOENT(self)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def size(self):
 | 
				
			||||||
 | 
					        """ Return the size of the file content of the Path. """
 | 
				
			||||||
 | 
					        return self.info().size
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def mtime(self):
 | 
				
			||||||
 | 
					        """ Return the last modification time of the file. """
 | 
				
			||||||
 | 
					        return self.info().mtime
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # shared help methods
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _escape(self, cmd):
 | 
				
			||||||
 | 
					        return _escape_helper(cmd)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _make_path_tuple(self, nameinfo_seq):
 | 
				
			||||||
 | 
					        """ return a tuple of paths from a nameinfo-tuple sequence.
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        #assert self.rev is not None, "revision of %s should not be None here" % self
 | 
				
			||||||
 | 
					        res = []
 | 
				
			||||||
 | 
					        for name, info in nameinfo_seq:
 | 
				
			||||||
 | 
					            child = self.join(name)
 | 
				
			||||||
 | 
					            res.append(child)
 | 
				
			||||||
 | 
					        return tuple(res)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _childmaxrev(self):
 | 
				
			||||||
 | 
					        """ return maximum revision number of childs (or self.rev if no childs) """
 | 
				
			||||||
 | 
					        rev = self.rev
 | 
				
			||||||
 | 
					        for name, info in self._listdir_nameinfo():
 | 
				
			||||||
 | 
					            rev = max(rev, info.created_rev)
 | 
				
			||||||
 | 
					        return rev
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #def _getlatestrevision(self):
 | 
				
			||||||
 | 
					    #    """ return latest repo-revision for this path. """
 | 
				
			||||||
 | 
					    #    url = self.strpath
 | 
				
			||||||
 | 
					    #    path = self.__class__(url, None)
 | 
				
			||||||
 | 
					    #
 | 
				
			||||||
 | 
					    #    # we need a long walk to find the root-repo and revision
 | 
				
			||||||
 | 
					    #    while 1:
 | 
				
			||||||
 | 
					    #        try:
 | 
				
			||||||
 | 
					    #            rev = max(rev, path._childmaxrev())
 | 
				
			||||||
 | 
					    #            previous = path
 | 
				
			||||||
 | 
					    #            path = path.dirpath()
 | 
				
			||||||
 | 
					    #        except (IOError, process.cmdexec.Error):
 | 
				
			||||||
 | 
					    #            break
 | 
				
			||||||
 | 
					    #    if rev is None:
 | 
				
			||||||
 | 
					    #        raise IOError, "could not determine newest repo revision for %s" % self
 | 
				
			||||||
 | 
					    #    return rev
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    class Checkers(common.Checkers):
 | 
				
			||||||
 | 
					        def dir(self):
 | 
				
			||||||
 | 
					            try:
 | 
				
			||||||
 | 
					                return self.path.info().kind == 'dir'
 | 
				
			||||||
 | 
					            except py.error.Error:
 | 
				
			||||||
 | 
					                return self._listdirworks()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        def _listdirworks(self):
 | 
				
			||||||
 | 
					            try:
 | 
				
			||||||
 | 
					                self.path.listdir()
 | 
				
			||||||
 | 
					            except py.error.ENOENT:
 | 
				
			||||||
 | 
					                return False
 | 
				
			||||||
 | 
					            else:
 | 
				
			||||||
 | 
					                return True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        def file(self):
 | 
				
			||||||
 | 
					            try:
 | 
				
			||||||
 | 
					                return self.path.info().kind == 'file'
 | 
				
			||||||
 | 
					            except py.error.ENOENT:
 | 
				
			||||||
 | 
					                return False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        def exists(self):
 | 
				
			||||||
 | 
					            try:
 | 
				
			||||||
 | 
					                return self.path.info()
 | 
				
			||||||
 | 
					            except py.error.ENOENT:
 | 
				
			||||||
 | 
					                return self._listdirworks()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def parse_apr_time(timestr):
 | 
				
			||||||
 | 
					    i = timestr.rfind('.')
 | 
				
			||||||
 | 
					    if i == -1:
 | 
				
			||||||
 | 
					        raise ValueError, "could not parse %s" % timestr
 | 
				
			||||||
 | 
					    timestr = timestr[:i]
 | 
				
			||||||
 | 
					    parsedtime = time.strptime(timestr, "%Y-%m-%dT%H:%M:%S")
 | 
				
			||||||
 | 
					    return time.mktime(parsedtime)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class PropListDict(dict):
 | 
				
			||||||
 | 
					    """ a Dictionary which fetches values (InfoSvnCommand instances) lazily"""
 | 
				
			||||||
 | 
					    def __init__(self, path, keynames):
 | 
				
			||||||
 | 
					        dict.__init__(self, [(x, None) for x in keynames])
 | 
				
			||||||
 | 
					        self.path = path
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __getitem__(self, key):
 | 
				
			||||||
 | 
					        value = dict.__getitem__(self, key)
 | 
				
			||||||
 | 
					        if value is None:
 | 
				
			||||||
 | 
					            value = self.path.propget(key)
 | 
				
			||||||
 | 
					            dict.__setitem__(self, key, value)
 | 
				
			||||||
 | 
					        return value
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def fixlocale():
 | 
				
			||||||
 | 
					    if sys.platform != 'win32':
 | 
				
			||||||
 | 
					        return 'LC_ALL=C '
 | 
				
			||||||
 | 
					    return ''
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# some nasty chunk of code to solve path and url conversion and quoting issues
 | 
				
			||||||
 | 
					ILLEGAL_CHARS = '* | \ / : < > ? \t \n \x0b \x0c \r'.split(' ')
 | 
				
			||||||
 | 
					if os.sep in ILLEGAL_CHARS:
 | 
				
			||||||
 | 
					    ILLEGAL_CHARS.remove(os.sep)
 | 
				
			||||||
 | 
					ISWINDOWS = sys.platform == 'win32'
 | 
				
			||||||
 | 
					_reg_allow_disk = re.compile(r'^([a-z]\:\\)?[^:]+$', re.I)
 | 
				
			||||||
 | 
					def _check_path(path):
 | 
				
			||||||
 | 
					    illegal = ILLEGAL_CHARS[:]
 | 
				
			||||||
 | 
					    sp = path.strpath
 | 
				
			||||||
 | 
					    if ISWINDOWS:
 | 
				
			||||||
 | 
					        illegal.remove(':')
 | 
				
			||||||
 | 
					        if not _reg_allow_disk.match(sp):
 | 
				
			||||||
 | 
					            raise ValueError('path may not contain a colon (:)')
 | 
				
			||||||
 | 
					    for char in sp:
 | 
				
			||||||
 | 
					        if char not in string.printable or char in illegal:
 | 
				
			||||||
 | 
					            raise ValueError('illegal character %r in path' % (char,))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def path_to_fspath(path, addat=True):
 | 
				
			||||||
 | 
					    _check_path(path)
 | 
				
			||||||
 | 
					    sp = path.strpath
 | 
				
			||||||
 | 
					    if addat and path.rev != -1:
 | 
				
			||||||
 | 
					        sp = '%s@%s' % (sp, path.rev)
 | 
				
			||||||
 | 
					    elif addat:
 | 
				
			||||||
 | 
					        sp = '%s@HEAD' % (sp,)
 | 
				
			||||||
 | 
					    return sp
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					def url_from_path(path):
 | 
				
			||||||
 | 
					    fspath = path_to_fspath(path, False)
 | 
				
			||||||
 | 
					    quote = py.std.urllib.quote
 | 
				
			||||||
 | 
					    if ISWINDOWS:
 | 
				
			||||||
 | 
					        match = _reg_allow_disk.match(fspath)
 | 
				
			||||||
 | 
					        fspath = fspath.replace('\\', '/')
 | 
				
			||||||
 | 
					        if match.group(1):
 | 
				
			||||||
 | 
					            fspath = '/%s%s' % (match.group(1).replace('\\', '/'),
 | 
				
			||||||
 | 
					                                quote(fspath[len(match.group(1)):]))
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            fspath = quote(fspath)
 | 
				
			||||||
 | 
					    else:
 | 
				
			||||||
 | 
					        fspath = quote(fspath)
 | 
				
			||||||
 | 
					    if path.rev != -1:
 | 
				
			||||||
 | 
					        fspath = '%s@%s' % (fspath, path.rev)
 | 
				
			||||||
 | 
					    else:
 | 
				
			||||||
 | 
					        fspath = '%s@HEAD' % (fspath,)
 | 
				
			||||||
 | 
					    return 'file://%s' % (fspath,)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class SvnAuth(object):
 | 
				
			||||||
 | 
					    """ container for auth information for Subversion """
 | 
				
			||||||
 | 
					    def __init__(self, username, password, cache_auth=True, interactive=True):
 | 
				
			||||||
 | 
					        self.username = username
 | 
				
			||||||
 | 
					        self.password = password
 | 
				
			||||||
 | 
					        self.cache_auth = cache_auth
 | 
				
			||||||
 | 
					        self.interactive = interactive
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def makecmdoptions(self):
 | 
				
			||||||
 | 
					        uname = self.username.replace('"', '\\"')
 | 
				
			||||||
 | 
					        passwd = self.password.replace('"', '\\"')
 | 
				
			||||||
 | 
					        ret = []
 | 
				
			||||||
 | 
					        if uname:
 | 
				
			||||||
 | 
					            ret.append('--username="%s"' % (uname,))
 | 
				
			||||||
 | 
					        if passwd:
 | 
				
			||||||
 | 
					            ret.append('--password="%s"' % (passwd,))
 | 
				
			||||||
 | 
					        if not self.cache_auth:
 | 
				
			||||||
 | 
					            ret.append('--no-auth-cache')
 | 
				
			||||||
 | 
					        if not self.interactive:
 | 
				
			||||||
 | 
					            ret.append('--non-interactive')
 | 
				
			||||||
 | 
					        return ' '.join(ret)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __str__(self):
 | 
				
			||||||
 | 
					        return "<SvnAuth username=%s ...>" %(self.username,)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
rex_blame = re.compile(r'\s*(\d+)\s*(\S+) (.*)')
 | 
					rex_blame = re.compile(r'\s*(\d+)\s*(\S+) (.*)')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -31,8 +453,8 @@ class SvnWCCommandPath(common.PathBase):
 | 
				
			||||||
            if wcpath.__class__ == cls:
 | 
					            if wcpath.__class__ == cls:
 | 
				
			||||||
                return wcpath
 | 
					                return wcpath
 | 
				
			||||||
            wcpath = wcpath.localpath
 | 
					            wcpath = wcpath.localpath
 | 
				
			||||||
        if svncommon._check_for_bad_chars(str(wcpath),
 | 
					        if _check_for_bad_chars(str(wcpath),
 | 
				
			||||||
                                          svncommon.ALLOWED_CHARS):
 | 
					                                          ALLOWED_CHARS):
 | 
				
			||||||
            raise ValueError("bad char in wcpath %s" % (wcpath, ))
 | 
					            raise ValueError("bad char in wcpath %s" % (wcpath, ))
 | 
				
			||||||
        self.localpath = py.path.local(wcpath)
 | 
					        self.localpath = py.path.local(wcpath)
 | 
				
			||||||
        self.auth = auth
 | 
					        self.auth = auth
 | 
				
			||||||
| 
						 | 
					@ -53,7 +475,7 @@ class SvnWCCommandPath(common.PathBase):
 | 
				
			||||||
    url = property(_geturl, None, None, "url of this WC item")
 | 
					    url = property(_geturl, None, None, "url of this WC item")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def _escape(self, cmd):
 | 
					    def _escape(self, cmd):
 | 
				
			||||||
        return svncommon._escape_helper(cmd)
 | 
					        return _escape_helper(cmd)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def dump(self, obj):
 | 
					    def dump(self, obj):
 | 
				
			||||||
        """ pickle object into path location"""
 | 
					        """ pickle object into path location"""
 | 
				
			||||||
| 
						 | 
					@ -86,9 +508,7 @@ class SvnWCCommandPath(common.PathBase):
 | 
				
			||||||
        l.extend(args)
 | 
					        l.extend(args)
 | 
				
			||||||
        l.append('"%s"' % self._escape(self.strpath))
 | 
					        l.append('"%s"' % self._escape(self.strpath))
 | 
				
			||||||
        # try fixing the locale because we can't otherwise parse
 | 
					        # try fixing the locale because we can't otherwise parse
 | 
				
			||||||
        string = svncommon.fixlocale() + " ".join(l)
 | 
					        string = fixlocale() + " ".join(l)
 | 
				
			||||||
        if DEBUG:
 | 
					 | 
				
			||||||
            print "execing", string
 | 
					 | 
				
			||||||
        try:
 | 
					        try:
 | 
				
			||||||
            try:
 | 
					            try:
 | 
				
			||||||
                key = 'LC_MESSAGES'
 | 
					                key = 'LC_MESSAGES'
 | 
				
			||||||
| 
						 | 
					@ -122,10 +542,10 @@ class SvnWCCommandPath(common.PathBase):
 | 
				
			||||||
            url = self.url
 | 
					            url = self.url
 | 
				
			||||||
        if rev is None or rev == -1:
 | 
					        if rev is None or rev == -1:
 | 
				
			||||||
            if (py.std.sys.platform != 'win32' and
 | 
					            if (py.std.sys.platform != 'win32' and
 | 
				
			||||||
                    svncommon._getsvnversion() == '1.3'):
 | 
					                    _getsvnversion() == '1.3'):
 | 
				
			||||||
                url += "@HEAD" 
 | 
					                url += "@HEAD" 
 | 
				
			||||||
        else:
 | 
					        else:
 | 
				
			||||||
            if svncommon._getsvnversion() == '1.3':
 | 
					            if _getsvnversion() == '1.3':
 | 
				
			||||||
                url += "@%d" % rev
 | 
					                url += "@%d" % rev
 | 
				
			||||||
            else:
 | 
					            else:
 | 
				
			||||||
                args.append('-r' + str(rev))
 | 
					                args.append('-r' + str(rev))
 | 
				
			||||||
| 
						 | 
					@ -280,7 +700,8 @@ class SvnWCCommandPath(common.PathBase):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def blame(self):
 | 
					    def blame(self):
 | 
				
			||||||
        """ return a list of tuples of three elements:
 | 
					        """ return a list of tuples of three elements:
 | 
				
			||||||
(revision, commiter, line)"""
 | 
					            (revision, commiter, line)
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
        out = self._svn('blame')
 | 
					        out = self._svn('blame')
 | 
				
			||||||
        result = []
 | 
					        result = []
 | 
				
			||||||
        blamelines = out.splitlines()
 | 
					        blamelines = out.splitlines()
 | 
				
			||||||
| 
						 | 
					@ -298,7 +719,6 @@ class SvnWCCommandPath(common.PathBase):
 | 
				
			||||||
    _rex_commit = re.compile(r'.*Committed revision (\d+)\.$', re.DOTALL)
 | 
					    _rex_commit = re.compile(r'.*Committed revision (\d+)\.$', re.DOTALL)
 | 
				
			||||||
    def commit(self, msg='', rec=1):
 | 
					    def commit(self, msg='', rec=1):
 | 
				
			||||||
        """ commit with support for non-recursive commits """
 | 
					        """ commit with support for non-recursive commits """
 | 
				
			||||||
        from py.__.path.svn import cache
 | 
					 | 
				
			||||||
        # XXX i guess escaping should be done better here?!?
 | 
					        # XXX i guess escaping should be done better here?!?
 | 
				
			||||||
        cmd = 'commit -m "%s" --force-log' % (msg.replace('"', '\\"'),)
 | 
					        cmd = 'commit -m "%s" --force-log' % (msg.replace('"', '\\"'),)
 | 
				
			||||||
        if not rec:
 | 
					        if not rec:
 | 
				
			||||||
| 
						 | 
					@ -343,7 +763,7 @@ If rec is True, then return a dictionary mapping sub-paths to such mappings.
 | 
				
			||||||
            res = self._svn('proplist')
 | 
					            res = self._svn('proplist')
 | 
				
			||||||
            lines = res.split('\n')
 | 
					            lines = res.split('\n')
 | 
				
			||||||
            lines = map(str.strip, lines[1:])
 | 
					            lines = map(str.strip, lines[1:])
 | 
				
			||||||
            return svncommon.PropListDict(self, lines)
 | 
					            return PropListDict(self, lines)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def revert(self, rec=0):
 | 
					    def revert(self, rec=0):
 | 
				
			||||||
        """ revert the local changes of this path. if rec is True, do so
 | 
					        """ revert the local changes of this path. if rec is True, do so
 | 
				
			||||||
| 
						 | 
					@ -466,17 +886,15 @@ rev_start is the starting revision (defaulting to the first one).
 | 
				
			||||||
rev_end is the last revision (defaulting to HEAD).
 | 
					rev_end is the last revision (defaulting to HEAD).
 | 
				
			||||||
if verbose is True, then the LogEntry instances also know which files changed.
 | 
					if verbose is True, then the LogEntry instances also know which files changed.
 | 
				
			||||||
"""
 | 
					"""
 | 
				
			||||||
        from py.__.path.svn.urlcommand import _Head, LogEntry
 | 
					 | 
				
			||||||
        assert self.check()   # make it simpler for the pipe
 | 
					        assert self.check()   # make it simpler for the pipe
 | 
				
			||||||
        rev_start = rev_start is None and _Head or rev_start
 | 
					        rev_start = rev_start is None and "HEAD" or rev_start
 | 
				
			||||||
        rev_end = rev_end is None and _Head or rev_end
 | 
					        rev_end = rev_end is None and "HEAD" or rev_end
 | 
				
			||||||
 | 
					        if rev_start == "HEAD" and rev_end == 1:
 | 
				
			||||||
        if rev_start is _Head and rev_end == 1:
 | 
					 | 
				
			||||||
                rev_opt = ""
 | 
					                rev_opt = ""
 | 
				
			||||||
        else:
 | 
					        else:
 | 
				
			||||||
            rev_opt = "-r %s:%s" % (rev_start, rev_end)
 | 
					            rev_opt = "-r %s:%s" % (rev_start, rev_end)
 | 
				
			||||||
        verbose_opt = verbose and "-v" or ""
 | 
					        verbose_opt = verbose and "-v" or ""
 | 
				
			||||||
        locale_env = svncommon.fixlocale()
 | 
					        locale_env = fixlocale()
 | 
				
			||||||
        # some blather on stderr
 | 
					        # some blather on stderr
 | 
				
			||||||
        auth_opt = self._makeauthoptions()
 | 
					        auth_opt = self._makeauthoptions()
 | 
				
			||||||
        stdin, stdout, stderr  = os.popen3(locale_env +
 | 
					        stdin, stdout, stderr  = os.popen3(locale_env +
 | 
				
			||||||
| 
						 | 
					@ -807,7 +1225,7 @@ def make_recursive_propdict(wcroot,
 | 
				
			||||||
            propname = lines.pop(0).strip()
 | 
					            propname = lines.pop(0).strip()
 | 
				
			||||||
            propnames.append(propname)
 | 
					            propnames.append(propname)
 | 
				
			||||||
        assert propnames, "must have found properties!"
 | 
					        assert propnames, "must have found properties!"
 | 
				
			||||||
        pdict[wcpath] = svncommon.PropListDict(wcpath, propnames)
 | 
					        pdict[wcpath] = PropListDict(wcpath, propnames)
 | 
				
			||||||
    return pdict
 | 
					    return pdict
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def error_enhance((cls, error, tb)):
 | 
					def error_enhance((cls, error, tb)):
 | 
				
			||||||
| 
						 | 
					@ -820,3 +1238,30 @@ def importxml(cache=[]):
 | 
				
			||||||
    from xml.parsers.expat import ExpatError
 | 
					    from xml.parsers.expat import ExpatError
 | 
				
			||||||
    cache.extend([minidom, ExpatError])
 | 
					    cache.extend([minidom, ExpatError])
 | 
				
			||||||
    return cache
 | 
					    return cache
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class LogEntry:
 | 
				
			||||||
 | 
					    def __init__(self, logentry):
 | 
				
			||||||
 | 
					        self.rev = int(logentry.getAttribute('revision'))
 | 
				
			||||||
 | 
					        for lpart in filter(None, logentry.childNodes):
 | 
				
			||||||
 | 
					            if lpart.nodeType == lpart.ELEMENT_NODE:
 | 
				
			||||||
 | 
					                if lpart.nodeName == u'author':
 | 
				
			||||||
 | 
					                    self.author = lpart.firstChild.nodeValue.encode('UTF-8')
 | 
				
			||||||
 | 
					                elif lpart.nodeName == u'msg':
 | 
				
			||||||
 | 
					                    if lpart.firstChild:
 | 
				
			||||||
 | 
					                        self.msg = lpart.firstChild.nodeValue.encode('UTF-8')
 | 
				
			||||||
 | 
					                    else:
 | 
				
			||||||
 | 
					                        self.msg = ''
 | 
				
			||||||
 | 
					                elif lpart.nodeName == u'date':
 | 
				
			||||||
 | 
					                    #2003-07-29T20:05:11.598637Z
 | 
				
			||||||
 | 
					                    timestr = lpart.firstChild.nodeValue.encode('UTF-8')
 | 
				
			||||||
 | 
					                    self.date = parse_apr_time(timestr)
 | 
				
			||||||
 | 
					                elif lpart.nodeName == u'paths':
 | 
				
			||||||
 | 
					                    self.strpaths = []
 | 
				
			||||||
 | 
					                    for ppart in filter(None, lpart.childNodes):
 | 
				
			||||||
 | 
					                        if ppart.nodeType == ppart.ELEMENT_NODE:
 | 
				
			||||||
 | 
					                            self.strpaths.append(PathEntry(ppart))
 | 
				
			||||||
 | 
					    def __repr__(self):
 | 
				
			||||||
 | 
					        return '<Logentry rev=%d author=%s date=%s>' % (
 | 
				
			||||||
 | 
					            self.rev, self.author, self.date)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -2,7 +2,7 @@ import sys
 | 
				
			||||||
import py
 | 
					import py
 | 
				
			||||||
from py import path, test, process
 | 
					from py import path, test, process
 | 
				
			||||||
from py.__.path.testing.fscommon import CommonFSTests, setuptestfs
 | 
					from py.__.path.testing.fscommon import CommonFSTests, setuptestfs
 | 
				
			||||||
from py.__.path.svn import cache, svncommon
 | 
					from py.__.path import svnwc as svncommon
 | 
				
			||||||
 | 
					
 | 
				
			||||||
mypath = py.magic.autopath()
 | 
					mypath = py.magic.autopath()
 | 
				
			||||||
repodump = mypath.dirpath('repotest.dump')
 | 
					repodump = mypath.dirpath('repotest.dump')
 | 
				
			||||||
| 
						 | 
					@ -66,6 +66,7 @@ def restore_repowc((savedrepo, savedwc)):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# create an empty repository for testing purposes and return the url to it
 | 
					# create an empty repository for testing purposes and return the url to it
 | 
				
			||||||
def make_test_repo(name="test-repository"):
 | 
					def make_test_repo(name="test-repository"):
 | 
				
			||||||
 | 
					    getsvnbin()
 | 
				
			||||||
    repo = py.test.ensuretemp(name)
 | 
					    repo = py.test.ensuretemp(name)
 | 
				
			||||||
    try:
 | 
					    try:
 | 
				
			||||||
        py.process.cmdexec('svnadmin create %s' % repo)
 | 
					        py.process.cmdexec('svnadmin create %s' % repo)
 | 
				
			||||||
| 
						 | 
					@ -149,14 +150,14 @@ class CommonCommandAndBindingTests(CommonSvnTests):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # the following tests are easier if we have a path class
 | 
					    # the following tests are easier if we have a path class
 | 
				
			||||||
    def test_repocache_simple(self):
 | 
					    def test_repocache_simple(self):
 | 
				
			||||||
        repocache = cache.RepoCache()
 | 
					        repocache = svncommon.RepoCache()
 | 
				
			||||||
        repocache.put(self.root.strpath, 42)
 | 
					        repocache.put(self.root.strpath, 42)
 | 
				
			||||||
        url, rev = repocache.get(self.root.join('test').strpath)
 | 
					        url, rev = repocache.get(self.root.join('test').strpath)
 | 
				
			||||||
        assert rev == 42
 | 
					        assert rev == 42
 | 
				
			||||||
        assert url == self.root.strpath
 | 
					        assert url == self.root.strpath
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_repocache_notimeout(self):
 | 
					    def test_repocache_notimeout(self):
 | 
				
			||||||
        repocache = cache.RepoCache()
 | 
					        repocache = svncommon.RepoCache()
 | 
				
			||||||
        repocache.timeout = 0
 | 
					        repocache.timeout = 0
 | 
				
			||||||
        repocache.put(self.root.strpath, self.root.rev)
 | 
					        repocache.put(self.root.strpath, self.root.rev)
 | 
				
			||||||
        url, rev = repocache.get(self.root.strpath)
 | 
					        url, rev = repocache.get(self.root.strpath)
 | 
				
			||||||
| 
						 | 
					@ -164,7 +165,7 @@ class CommonCommandAndBindingTests(CommonSvnTests):
 | 
				
			||||||
        assert url == self.root.strpath
 | 
					        assert url == self.root.strpath
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_repocache_outdated(self):
 | 
					    def test_repocache_outdated(self):
 | 
				
			||||||
        repocache = cache.RepoCache()
 | 
					        repocache = svncommon.RepoCache()
 | 
				
			||||||
        repocache.put(self.root.strpath, 42, timestamp=0)
 | 
					        repocache.put(self.root.strpath, 42, timestamp=0)
 | 
				
			||||||
        url, rev = repocache.get(self.root.join('test').strpath)
 | 
					        url, rev = repocache.get(self.root.join('test').strpath)
 | 
				
			||||||
        assert rev == -1
 | 
					        assert rev == -1
 | 
				
			||||||
| 
						 | 
					@ -172,7 +173,7 @@ class CommonCommandAndBindingTests(CommonSvnTests):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def _test_getreporev(self):
 | 
					    def _test_getreporev(self):
 | 
				
			||||||
        """ this test runs so slow it's usually disabled """
 | 
					        """ this test runs so slow it's usually disabled """
 | 
				
			||||||
        old = cache.repositories.repos
 | 
					        old = svncommon.repositories.repos
 | 
				
			||||||
        try:
 | 
					        try:
 | 
				
			||||||
            _repocache.clear()
 | 
					            _repocache.clear()
 | 
				
			||||||
            root = self.root.new(rev=-1)
 | 
					            root = self.root.new(rev=-1)
 | 
				
			||||||
| 
						 | 
					@ -1,6 +1,6 @@
 | 
				
			||||||
import py
 | 
					import py
 | 
				
			||||||
from py.__.path.svn.urlcommand import InfoSvnCommand
 | 
					from py.__.path.svnurl import InfoSvnCommand
 | 
				
			||||||
from py.__.path.svn.testing.svntestbase import CommonCommandAndBindingTests, \
 | 
					from py.__.path.testing.svntestbase import CommonCommandAndBindingTests, \
 | 
				
			||||||
                                               getrepowc, getsvnbin
 | 
					                                               getrepowc, getsvnbin
 | 
				
			||||||
import datetime
 | 
					import datetime
 | 
				
			||||||
import time
 | 
					import time
 | 
				
			||||||
| 
						 | 
					@ -1,9 +1,8 @@
 | 
				
			||||||
import py
 | 
					import py
 | 
				
			||||||
import sys
 | 
					import sys
 | 
				
			||||||
from py.__.path.svn.testing.svntestbase import CommonSvnTests, getrepowc, getsvnbin
 | 
					from py.__.path.testing.svntestbase import CommonSvnTests, getrepowc, getsvnbin, make_test_repo
 | 
				
			||||||
from py.__.path.svn.wccommand import InfoSvnWCCommand, XMLWCStatus
 | 
					from py.__.path.svnwc import InfoSvnWCCommand, XMLWCStatus, parse_wcinfotime
 | 
				
			||||||
from py.__.path.svn.wccommand import parse_wcinfotime
 | 
					from py.__.path import svnwc as svncommon
 | 
				
			||||||
from py.__.path.svn import svncommon
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
if sys.platform != 'win32':
 | 
					if sys.platform != 'win32':
 | 
				
			||||||
    def normpath(p):
 | 
					    def normpath(p):
 | 
				
			||||||
| 
						 | 
					@ -23,6 +22,27 @@ else:
 | 
				
			||||||
def setup_module(mod):
 | 
					def setup_module(mod):
 | 
				
			||||||
    getsvnbin()
 | 
					    getsvnbin()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class TestMakeRepo(object):
 | 
				
			||||||
 | 
					    def setup_class(cls):
 | 
				
			||||||
 | 
					        cls.repo = make_test_repo()
 | 
				
			||||||
 | 
					        cls.wc = py.path.svnwc(py.test.ensuretemp("test-wc").join("wc"))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_empty_checkout(self):
 | 
				
			||||||
 | 
					        self.wc.checkout(self.repo)
 | 
				
			||||||
 | 
					        assert len(self.wc.listdir()) == 0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_commit(self):
 | 
				
			||||||
 | 
					        self.wc.checkout(self.repo)
 | 
				
			||||||
 | 
					        p = self.wc.join("a_file")
 | 
				
			||||||
 | 
					        p.write("test file")
 | 
				
			||||||
 | 
					        p.add()
 | 
				
			||||||
 | 
					        rev = self.wc.commit("some test")
 | 
				
			||||||
 | 
					        assert p.info().rev == 1
 | 
				
			||||||
 | 
					        assert rev == 1
 | 
				
			||||||
 | 
					        rev = self.wc.commit()
 | 
				
			||||||
 | 
					        assert rev is None
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class TestWCSvnCommandPath(CommonSvnTests):
 | 
					class TestWCSvnCommandPath(CommonSvnTests):
 | 
				
			||||||
    def setup_class(cls): 
 | 
					    def setup_class(cls): 
 | 
				
			||||||
        repo, cls.root = getrepowc()
 | 
					        repo, cls.root = getrepowc()
 | 
				
			||||||
| 
						 | 
					@ -427,8 +447,8 @@ class TestInfoSvnWCCommand:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_svn_1_2(self):
 | 
					    def test_svn_1_2(self):
 | 
				
			||||||
        output = """
 | 
					        output = """
 | 
				
			||||||
        Path: test_wccommand.py
 | 
					        Path: test_svnwc.py
 | 
				
			||||||
        Name: test_wccommand.py
 | 
					        Name: test_svnwc.py
 | 
				
			||||||
        URL: http://codespeak.net/svn/py/dist/py/path/svn/wccommand.py
 | 
					        URL: http://codespeak.net/svn/py/dist/py/path/svn/wccommand.py
 | 
				
			||||||
        Repository UUID: fd0d7bf2-dfb6-0310-8d31-b7ecfe96aada
 | 
					        Repository UUID: fd0d7bf2-dfb6-0310-8d31-b7ecfe96aada
 | 
				
			||||||
        Revision: 28137
 | 
					        Revision: 28137
 | 
				
			||||||
| 
						 | 
					@ -454,8 +474,8 @@ class TestInfoSvnWCCommand:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_svn_1_3(self):
 | 
					    def test_svn_1_3(self):
 | 
				
			||||||
        output = """
 | 
					        output = """
 | 
				
			||||||
        Path: test_wccommand.py
 | 
					        Path: test_svnwc.py
 | 
				
			||||||
        Name: test_wccommand.py
 | 
					        Name: test_svnwc.py
 | 
				
			||||||
        URL: http://codespeak.net/svn/py/dist/py/path/svn/wccommand.py
 | 
					        URL: http://codespeak.net/svn/py/dist/py/path/svn/wccommand.py
 | 
				
			||||||
        Repository Root: http://codespeak.net/svn
 | 
					        Repository Root: http://codespeak.net/svn
 | 
				
			||||||
        Repository UUID: fd0d7bf2-dfb6-0310-8d31-b7ecfe96aada
 | 
					        Repository UUID: fd0d7bf2-dfb6-0310-8d31-b7ecfe96aada
 | 
				
			||||||
		Loading…
	
		Reference in New Issue