1. PyPA
  2. Python Packaging Authority Projects
  3. setuptools

Commits

philip_thiem  committed 7538cd9

Additional Tests, Various fixes, and encoding dealings

  • Participants
  • Parent commits 23e166c
  • Branches default

Comments (0)

Files changed (32)

File setuptools/command/egg_info.py

View file
 
     @staticmethod
     def get_svn_revision():
-        return str(svn_utils.parse_revision(os.curdir))
+        return str(svn_utils.SvnInfo.load(os.curdir).get_revision())
 
 
 

File setuptools/command/sdist.py

View file
 
 READMES = ('README', 'README.rst', 'README.txt')
 
-entities = [
-    ("&lt;","<"), ("&gt;", ">"), ("&quot;", '"'), ("&apos;", "'"),
-    ("&amp;", "&")
-]
-
-def unescape(data):
-    for old,new in entities:
-        data = data.replace(old,new)
-    return data
-
-def re_finder(pattern, postproc=None):
-    def find(dirname, filename):
-        f = open(filename,'rU')
-        data = f.read()
-        f.close()
-        for match in pattern.finditer(data):
-            path = match.group(1)
-            if postproc:
-                path = postproc(path)
-            yield joinpath(dirname,path)
-    return find
-
-def joinpath(prefix,suffix):
-    if not prefix:
-        return suffix
-    return os.path.join(prefix,suffix)
-
-
-
-
-
-
-
-
 
 def walk_revctrl(dirname=''):
     """Find all files under revision control"""
         for item in ep.load()(dirname):
             yield item
 
-def _default_revctrl(dirname=''):
-    for path, finder in finders:
-        path = joinpath(dirname,path)
+
+#TODO will need test case
+class re_finder(object):
+
+    def __init__(self, path, pattern, postproc=None):
+        self.pattern = pattern
+        self.postproc = postproc
+        self.path = convert_path(path)
+
+    def _finder(self, dirname, filename):
+        f = open(filename,'rU')
+        try:
+            data = f.read()
+        finally:
+            f.close()
+        for match in self.pattern.finditer(data):
+            path = match.group(1)
+            if postproc:
+                #postproc used to be used when the svn finder
+                #was an re_finder for calling unescape
+                path = postproc(path)
+            yield svn_utils.joinpath(dirname,path)
+
+    def __call__(self, dirname=''):
+        path = svn_utils.joinpath(dirname, self.path)
+
         if os.path.isfile(path):
-            for path in finder(dirname,path):
+            for path in self._finder(dirname,path):
                 if os.path.isfile(path):
                     yield path
                 elif os.path.isdir(path):
-                    for item in _default_revctrl(path):
+                    for item in self.find(path):
                         yield item
 
 
-def entries_externals_finder(dirname, filename):
-    for record in svn_utils.parse_dir_entries(dirname):
-        yield joinpath(dirname, record)
-
-    for name in svn_utils.parse_externals(dirname):
-        yield joinpath(dirname, name)
+def _default_revctrl(dirname=''):
+    'Primary svn_cvs entry point'
+    for finder in finders:
+        for item in finder(dirname):
+            yield item
 
 
 finders = [
-    (convert_path('CVS/Entries'),
-        re_finder(re.compile(r"^\w?/([^/]+)/", re.M))),
-    #combined externals due to common interface
-    #combined externals and enteries due to lack of dir_props in 1.7
-    (convert_path('.svn/entries'), entries_externals_finder),
+    re_finder('CVS/Entries', re.compile(r"^\w?/([^/]+)/", re.M)),
+    svn_utils.svn_finder,
 ]
 
 
 
 
 
+
 class sdist(_sdist):
     """Smart sdist that finds anything supported by revision control"""
 

File setuptools/compat.py

View file
     reduce = reduce
     unichr = unichr
     unicode = unicode
+    bytes = str
     from urllib import url2pathname
     import urllib2
     from urllib2 import urlopen, HTTPError, URLError, unquote, splituser
     from functools import reduce
     unichr = chr
     unicode = str
+    bytes = bytes
     from urllib.error import HTTPError, URLError
     import urllib.request as urllib2
     from urllib.request import urlopen, url2pathname

File setuptools/svn_utils.py

View file
 import os
 import re
 import sys
-import codecs
 from distutils import log
 import xml.dom.pulldom
+import shlex
+import locale
+import unicodedata
+from setuptools.compat import unicode, bytes
 
 try:
     import urlparse
 except ImportError:
     import urllib.parse as urlparse
 
-#requires python >= 2.4
 from subprocess import Popen as _Popen, PIPE as _PIPE
 
-#NOTE: Use of the command line options
-#      require SVN 1.3 or newer (December 2005)
-#      and SVN 1.3 hsan't been supported by the
-#      developers since mid 2008.
-
-
-#svnversion return values (previous implementations return max revision)
-#   4123:4168     mixed revision working copy
-#   4168M         modified working copy
-#   4123S         switched working copy
-#   4123:4168MS   mixed revision, modified, switched working copy
-_SVN_VER_RE = re.compile(r'(?:([\-0-9]+):)?(\d+)([a-z]*)\s*$', re.I)
-
+#NOTE: Use of the command line options require SVN 1.3 or newer (December 2005)
+#      and SVN 1.3 hasn't been supported by the developers since mid 2008.
 
 #subprocess is called several times with shell=(sys.platform=='win32')
 #see the follow for more information:
 #       http://stackoverflow.com/questions/5658622/
 #              python-subprocess-popen-environment-path
 def _run_command(args, stdout=_PIPE, stderr=_PIPE):
-        #regarding the shell argument, see: http://bugs.python.org/issue8557
-        try:
-            proc = _Popen(args, stdout=stdout, stderr=stderr,
-                          shell=(sys.platform == 'win32'))
+    #regarding the shell argument, see: http://bugs.python.org/issue8557
+    try:
+        args = [fsdecode(x) for x in args]
+        proc = _Popen(args, stdout=stdout, stderr=stderr,
+                      shell=(sys.platform == 'win32'))
 
-            data = proc.communicate()[0]
-        except OSError:
-            return 1, ''
+        data = proc.communicate()[0]
+    except OSError:
+        return 1, ''
 
-        #TODO: this is probably NOT always utf-8
-        try:
-            data = unicode(data, encoding='utf-8')
-        except NameError:
-            data = str(data, encoding='utf-8')
+    data = consoledecode(data)
 
-        #communciate calls wait()
-        return proc.returncode, data
-
-
-def _get_entry_name(entry):
-    return entry.getAttribute('path')
+    #communciate calls wait()
+    return proc.returncode, data
 
 
 def _get_entry_schedule(entry):
                     if t.nodeType == t.TEXT_NODE])
 
 
-def parse_revision(path):
-    code, data = _run_command(['svnversion', '-c', path])
+def _get_target_property(target):
+    property_text = target.getElementsByTagName('property')[0]
+    return "".join([t.nodeValue
+                    for t in property_text.childNodes
+                    if t.nodeType == t.TEXT_NODE])
 
-    if code:
-        log.warn("svnversion failed")
-        return 0
+
+def _get_xml_data(decoded_str):
+    if sys.version_info < (3, 0):
+        #old versions want an encoded string
+        data = decoded_str.encode('utf-8')
     else:
-        log.warn('Version: %s' % data.strip())
+        data = decoded_str
+    return data
 
-    parsed = _SVN_VER_RE.match(data)
-    if parsed:
-        try:
-            #No max needed this command summarizes working copy since 1.0
-            return int(parsed.group(2))
-        except ValueError:
-            #This should only happen if the revision is WAY too big.
-            pass
-    return 0
 
-#TODO: Need to do this with the -R because only root has .svn in 1.7.x
-def parse_dir_entries(path):
-    code, data = _run_command(['svn', 'info',
-                               '--depth', 'immediates', '--xml', path])
+def joinpath(prefix, suffix):
+    if not prefix or prefix == '.':
+        return suffix
+    return os.path.join(prefix, suffix)
 
-    if code:
-        log.warn("svn info failed")
-        return []
 
-    data = codecs.encode(data, 'UTF-8')
+def fsencode(path):
+    "Path must be unicode or in file system encoding already"
+    encoding = sys.getfilesystemencoding()
 
-    doc = xml.dom.pulldom.parseString(data)
+    if isinstance(path, unicode):
+        path = path.encode()
+    elif not isinstance(path, bytes):
+        raise TypeError('%s is not a string or byte type'
+                        % type(path).__name__)
+
+    #getfilessystemencoding doesn't have the mac-roman issue
+    if encoding == 'utf-8' and sys.platform == 'darwin':
+        path = path.decode('utf-8')
+        path = unicodedata.normalize('NFD', path)
+        path = path.encode('utf-8')
+
+    return path
+
+def fsdecode(path):
+    "Path must be unicode or in file system encoding already"
+    encoding = sys.getfilesystemencoding()
+    if isinstance(path, bytes):
+        path = path.decode(encoding)
+    elif not isinstance(path, unicode):
+        raise TypeError('%s is not a byte type'
+                        % type(path).__name__)
+
+    return unicodedata.normalize('NFC', path)
+
+def consoledecode(text):
+    encoding = locale.getpreferredencoding()
+    return text.decode(encoding)
+
+
+def parse_dir_entries(decoded_str):
+    '''Parse the entries from a recursive info xml'''
+    doc = xml.dom.pulldom.parseString(_get_xml_data(decoded_str))
     entries = list()
+
     for event, node in doc:
         if event == 'START_ELEMENT' and node.nodeName == 'entry':
             doc.expandNode(node)
-            entries.append(node)
+            if not _get_entry_schedule(node).startswith('delete'):
+                entries.append((node.getAttribute('path'),
+                                node.getAttribute('kind')))
 
-    if entries:
-        return [
-            _get_entry_name(element)
-            for element in entries[1:]
-            if _get_entry_schedule(element).lower() != 'deleted'
-        ]
-    else:
-        return []
+    return entries[1:]  # do not want the root directory
 
 
-#--xml wasn't supported until 1.5.x need to do -R
-#TODO: -R looks like directories are seperated by blank lines
-#      with dir - prepened to first directory
-#      what about directories with spaces?
-#          put quotes around them
-#      what about the URL's?
-#          same
-#      convert to UTF-8 and use csv
-#         delimiter = space
-#
-#-R without --xml parses a bit funny
-def parse_externals(path):
-    try:
-        code, lines = _run_command(['svn',
-                                    'propget', 'svn:externals', path])
+def parse_externals_xml(decoded_str, prefix=''):
+    '''Parse a propget svn:externals xml'''
+    prefix = os.path.normpath(prefix)
 
-        if code:
-            log.warn("svn propget failed")
-            return []
+    doc = xml.dom.pulldom.parseString(_get_xml_data(decoded_str))
+    externals = list()
 
-        lines = [line for line in lines.splitlines() if line]
-    except ValueError:
-        lines = []
+    for event, node in doc:
+        if event == 'START_ELEMENT' and node.nodeName == 'target':
+            doc.expandNode(node)
+            path = os.path.normpath(node.getAttribute('path'))
+            if os.path.normcase(path).startswith(prefix):
+                path = path[len(prefix)+1:]
 
+            data = _get_target_property(node)
+            for external in parse_external_prop(data):
+                externals.append(joinpath(path, external))
+
+    return externals  # do not want the root directory
+
+
+def parse_external_prop(lines):
+    """
+    Parse the value of a retrieved svn:externals entry.
+
+    possible token setups (with quotng and backscaping in laters versions)
+        URL[@#] EXT_FOLDERNAME
+        [-r#] URL EXT_FOLDERNAME
+        EXT_FOLDERNAME [-r#] URL
+    """
     externals = []
-    for line in lines:
-        line = line.split()
+    for line in lines.splitlines():
+        line = line.lstrip() #there might be a "\ "
         if not line:
             continue
 
+        if sys.version_info < (3, 0):
+            #shlex handles NULLs just fine and shlex in 2.7 tries to encode
+            #as ascii automatiically
+            line = line.encode('utf-8')
+        line = shlex.split(line)
+        if sys.version_info < (3, 0):
+            line = [x.decode('utf-8') for x in line]
+
+        #EXT_FOLDERNAME is either the first or last depending on where
+        #the URL falls
         if urlparse.urlsplit(line[-1])[0]:
-            externals.append(line[0])
+            external = line[0]
         else:
-            externals.append(line[-1])
+            external = line[-1]
+
+        externals.append(os.path.normpath(external))
 
     return externals
 
 
-def get_svn_tool_version():
-    _, data = _run_command(['svn', '--version', '--quiet'])
-    if data:
-        return data.strip()
-    else:
-        return ''
+class SvnInfo(object):
+    '''
+    Generic svn_info object.  No has little knowledge of how to extract
+    information.  Use cls.load to instatiate according svn version.
+
+    Paths are not filesystem encoded.
+    '''
+
+    @staticmethod
+    def get_svn_version():
+        code, data = _run_command(['svn', '--version', '--quiet'])
+        if code == 0 and data:
+            return unicode(data).strip()
+        else:
+            return unicode('')
+
+    #svnversion return values (previous implementations return max revision)
+    #   4123:4168     mixed revision working copy
+    #   4168M         modified working copy
+    #   4123S         switched working copy
+    #   4123:4168MS   mixed revision, modified, switched working copy
+    revision_re = re.compile(r'(?:([\-0-9]+):)?(\d+)([a-z]*)\s*$', re.I)
+
+    @classmethod
+    def load(cls, dirname=''):
+        code, data = _run_command(['svn', 'info', os.path.normpath(dirname)])
+        svn_version = tuple(cls.get_svn_version().split('.'))
+        base_svn_version = tuple(int(x) for x in svn_version[:2])
+        if code and base_svn_version:
+            #Not an SVN repository or compatible one
+            return SvnInfo(dirname)
+        elif base_svn_version < (1, 3):
+            log.warn('Insufficent version of SVN found')
+            return SvnInfo(dirname)
+        elif base_svn_version < (1, 5):
+            return Svn13Info(dirname)
+        else:
+            return Svn15Info(dirname)
+
+    def __init__(self, path=''):
+        self.path = path
+        self._entries = None
+        self._externals = None
+
+    def get_revision(self):
+        'Retrieve the directory revision informatino using svnversion'
+        code, data = _run_command(['svnversion', '-c', self.path])
+        if code:
+            log.warn("svnversion failed")
+            return 0
+
+        parsed = self.revision_re.match(data)
+        if parsed:
+            return int(parsed.group(2))
+        else:
+            return 0
+
+    @property
+    def entries(self):
+        if self._entries is None:
+            self._entries = self.get_entries()
+        return self._entries
+
+    @property
+    def externals(self):
+        if self._externals is None:
+            self._externals = self.get_externals()
+        return self._externals
+
+    def iter_externals(self):
+        '''
+        Iterate over the svn:external references in the repository path.
+        '''
+        for item in self.externals:
+            yield item
+
+    def iter_files(self):
+        '''
+        Iterate over the non-deleted file entries in the repository path
+        '''
+        for item, kind in self.entries:
+            if kind.lower()=='file':
+                yield item
+
+    def iter_dirs(self, include_root=True):
+        '''
+        Iterate over the non-deleted file entries in the repository path
+        '''
+        if include_root:
+            yield self.path
+        for item, kind in self.entries:
+            if kind.lower()=='dir':
+                yield item
+
+    def get_entries(self):
+        return []
+
+    def get_externals(self):
+        return []
+
+class Svn13Info(SvnInfo):
+    def get_entries(self):
+        code, data = _run_command(['svn', 'info', '-R', '--xml', self.path])
+
+        if code:
+            log.debug("svn info failed")
+            return []
+
+        return parse_dir_entries(data)
+
+    def get_externals(self):
+        #Previous to 1.5 --xml was not supported for svn propget and the -R
+        #output format breaks the shlex compatible semantics.
+        cmd = ['svn', 'propget', 'svn:externals']
+        result = []
+        for folder in self.iter_dirs():
+            code, lines = _run_command(cmd + [folder])
+            if code != 0:
+                log.warn("svn propget failed")
+                return []
+            for external in parse_external_prop(lines):
+                if folder:
+                    external = os.path.join(folder, external)
+                result.append(os.path.normpath(external))
+
+        return result
+
+
+class Svn15Info(Svn13Info):
+    def get_externals(self):
+        cmd = ['svn', 'propget', 'svn:externals', self.path, '-R', '--xml']
+        code, lines = _run_command(cmd)
+        if code:
+            log.debug("svn propget failed")
+            return []
+        return parse_externals_xml(lines, prefix=os.path.abspath(self.path))
+
+
+def svn_finder(dirname=''):
+    #combined externals due to common interface
+    #combined externals and entries due to lack of dir_props in 1.7
+    info = SvnInfo.load(dirname)
+    for path in info.iter_files():
+        yield fsencode(path)
+
+    for path in info.iter_externals():
+        sub_info = SvnInfo.load(path)
+        for sub_path in sub_info.iter_files():
+            yield fsencode(sub_path)
 
 if __name__ == '__main__':
-    def entries_externals_finder(dirname):
-        for record in parse_dir_entries(dirname):
-            yield os.path.join(dirname, record)
-
-        for name in parse_externals(dirname):
-            yield os.path.join(dirname, name)
-
-    for name in entries_externals_finder(sys.argv[1]):
+    for name in svn_finder(sys.argv[1]):
         print(name)

File setuptools/tests/environment.py

View file
+import os
+import zipfile
+import sys
+import tempfile
+import unittest
+import shutil
+import stat
+
+
+def _extract(self, member, path=None, pwd=None):
+    """for zipfile py2.5 borrowed from cpython"""
+    if not isinstance(member, zipfile.ZipInfo):
+        member = self.getinfo(member)
+
+    if path is None:
+        path = os.getcwd()
+
+    return _extract_member(self, member, path, pwd)
+
+
+def _extract_from_zip(self, name, dest_path):
+    dest_file = open(dest_path, 'wb')
+    try:
+        dest_file.write(self.read(name))
+    finally:
+        dest_file.close()
+
+def _extract_member(self, member, targetpath, pwd):
+    """for zipfile py2.5 borrowed from cpython"""
+    # build the destination pathname, replacing
+    # forward slashes to platform specific separators.
+    # Strip trailing path separator, unless it represents the root.
+    if (targetpath[-1:] in (os.path.sep, os.path.altsep)
+        and len(os.path.splitdrive(targetpath)[1]) > 1):
+        targetpath = targetpath[:-1]
+
+    # don't include leading "/" from file name if present
+    if member.filename[0] == '/':
+        targetpath = os.path.join(targetpath, member.filename[1:])
+    else:
+        targetpath = os.path.join(targetpath, member.filename)
+
+    targetpath = os.path.normpath(targetpath)
+
+    # Create all upper directories if necessary.
+    upperdirs = os.path.dirname(targetpath)
+    if upperdirs and not os.path.exists(upperdirs):
+        os.makedirs(upperdirs)
+
+    if member.filename[-1] == '/':
+        if not os.path.isdir(targetpath):
+            os.mkdir(targetpath)
+        return targetpath
+
+    _extract_from_zip(self, member.filename, targetpath)
+
+    return targetpath
+
+
+def _remove_dir(target):
+
+    #on windows this seems to a problem
+    for dir_path, dirs, files in os.walk(target):
+        os.chmod(dir_path, stat.S_IWRITE)
+        for filename in files:
+            os.chmod(os.path.join(dir_path, filename), stat.S_IWRITE)
+    shutil.rmtree(target)
+
+
+class ZippedEnvironment(unittest.TestCase):
+
+    datafile = None
+    dataname = None
+    old_cwd = None
+
+    def setUp(self):
+        if not os.path.isfile(self.datafile):
+            self.old_cwd = None
+            return
+
+        self.old_cwd = os.getcwd()
+
+        self.temp_dir = tempfile.mkdtemp()
+        zip_file, source, target = [None, None, None]
+        try:
+            zip_file = zipfile.ZipFile(self.datafile)
+            for files in zip_file.namelist():
+                _extract(zip_file, files, self.temp_dir)
+        finally:
+            if zip_file:
+                zip_file.close()
+            del zip_file
+
+        os.chdir(os.path.join(self.temp_dir, self.dataname))
+
+    def tearDown(self):
+        try:
+            if self.old_cwd:
+                os.chdir(self.old_cwd)
+                _remove_dir(self.temp_dir)
+        except OSError:
+            #sigh?
+            pass
+

File setuptools/tests/svn_data/svn13_example.zip

Binary file added.

File setuptools/tests/svn_data/svn13_ext_list.txt

View file
+third_party3 file:///C:/development/svn_example/repos/svn13/extra1
+third_party2 -r3 file:///C:/development/svn_example/repos/svn13/extra1
+third_party -r1 file:///C:/development/svn_example/repos/svn13/extra1

File setuptools/tests/svn_data/svn13_ext_list.xml

Empty file added.

File setuptools/tests/svn_data/svn13_info.xml

View file
+<?xml version="1.0" encoding="utf-8"?>
+<info>
+<entry
+   kind="dir"
+   path="svn13_example"
+   revision="6">
+<url>file:///C:/development/svn_example/repos/svn13/main</url>
+<repository>
+<root>file:///C:/development/svn_example/repos/svn13/main</root>
+<uuid>d2996769-47b0-9946-b618-da1aa3eceda3</uuid>
+</repository>
+<wc-info>
+<schedule>normal</schedule>
+<prop-updated>2013-07-13T15:33:23.187500Z</prop-updated>
+</wc-info>
+<commit
+   revision="6">
+<author>ptt</author>
+<date>2013-07-13T15:33:28.359375Z</date>
+</commit>
+</entry>
+<entry
+   kind="file"
+   path="svn13_example\a file"
+   revision="6">
+<url>file:///C:/development/svn_example/repos/svn13/main/a%20file</url>
+<repository>
+<root>file:///C:/development/svn_example/repos/svn13/main</root>
+<uuid>d2996769-47b0-9946-b618-da1aa3eceda3</uuid>
+</repository>
+<wc-info>
+<schedule>normal</schedule>
+<text-updated>2013-07-13T15:33:21.109375Z</text-updated>
+<checksum>a6166e5e98a5a503089cde9bc8031293</checksum>
+</wc-info>
+<commit
+   revision="3">
+<author>ptt</author>
+<date>2013-07-13T15:33:21.312500Z</date>
+</commit>
+</entry>
+<entry
+   kind="file"
+   path="svn13_example\to_delete"
+   revision="6">
+<url>file:///C:/development/svn_example/repos/svn13/main/to_delete</url>
+<repository>
+<root>file:///C:/development/svn_example/repos/svn13/main</root>
+<uuid>d2996769-47b0-9946-b618-da1aa3eceda3</uuid>
+</repository>
+<wc-info>
+<schedule>delete</schedule>
+<text-updated>2013-07-13T15:33:28.140625Z</text-updated>
+<checksum>d41d8cd98f00b204e9800998ecf8427e</checksum>
+</wc-info>
+<commit
+   revision="6">
+<author>ptt</author>
+<date>2013-07-13T15:33:28.359375Z</date>
+</commit>
+</entry>
+<entry
+   kind="dir"
+   path="svn13_example\folder"
+   revision="6">
+<url>file:///C:/development/svn_example/repos/svn13/main/folder</url>
+<repository>
+<root>file:///C:/development/svn_example/repos/svn13/main</root>
+<uuid>d2996769-47b0-9946-b618-da1aa3eceda3</uuid>
+</repository>
+<wc-info>
+<schedule>normal</schedule>
+<prop-updated>2013-07-13T15:33:26.187500Z</prop-updated>
+</wc-info>
+<commit
+   revision="5">
+<author>ptt</author>
+<date>2013-07-13T15:33:26.312500Z</date>
+</commit>
+</entry>
+<entry
+   kind="file"
+   path="svn13_example\folder\quest.txt"
+   revision="6">
+<url>file:///C:/development/svn_example/repos/svn13/main/folder/quest.txt</url>
+<repository>
+<root>file:///C:/development/svn_example/repos/svn13/main</root>
+<uuid>d2996769-47b0-9946-b618-da1aa3eceda3</uuid>
+</repository>
+<wc-info>
+<schedule>normal</schedule>
+<text-updated>2013-07-13T15:33:20.109375Z</text-updated>
+<checksum>795240c6a830c14f83961e57e07dad12</checksum>
+</wc-info>
+<commit
+   revision="2">
+<author>ptt</author>
+<date>2013-07-13T15:33:20.312500Z</date>
+</commit>
+</entry>
+<entry
+   kind="file"
+   path="svn13_example\folder\lalala.txt"
+   revision="6">
+<url>file:///C:/development/svn_example/repos/svn13/main/folder/lalala.txt</url>
+<repository>
+<root>file:///C:/development/svn_example/repos/svn13/main</root>
+<uuid>d2996769-47b0-9946-b618-da1aa3eceda3</uuid>
+</repository>
+<wc-info>
+<schedule>normal</schedule>
+<text-updated>2013-07-13T15:33:19.375000Z</text-updated>
+<checksum>d41d8cd98f00b204e9800998ecf8427e</checksum>
+</wc-info>
+<commit
+   revision="1">
+<author>ptt</author>
+<date>2013-07-13T15:33:19.609375Z</date>
+</commit>
+</entry>
+</info>

File setuptools/tests/svn_data/svn14_example.zip

Binary file added.

File setuptools/tests/svn_data/svn14_ext_list.txt

View file
+third_party3 file:///C:/development/svn_example/repos/svn13/extra1
+third_party2 -r3 file:///C:/development/svn_example/repos/svn13/extra1
+third_party -r1 file:///C:/development/svn_example/repos/svn13/extra1
+

File setuptools/tests/svn_data/svn14_ext_list.xml

Empty file added.

File setuptools/tests/svn_data/svn14_info.xml

View file
+<?xml version="1.0"?>
+<info>
+<entry
+   kind="dir"
+   path="svn14_example"
+   revision="6">
+<url>file:///C:/development/svn_example/repos/svn14/main</url>
+<repository>
+<root>file:///C:/development/svn_example/repos/svn14/main</root>
+<uuid>c75942e5-8b7a-354d-b1cf-73dee23fa94f</uuid>
+</repository>
+<wc-info>
+<schedule>normal</schedule>
+</wc-info>
+<commit
+   revision="6">
+<author>ptt</author>
+<date>2013-07-13T15:34:14.406250Z</date>
+</commit>
+</entry>
+<entry
+   kind="file"
+   path="svn14_example\a file"
+   revision="6">
+<url>file:///C:/development/svn_example/repos/svn14/main/a%20file</url>
+<repository>
+<root>file:///C:/development/svn_example/repos/svn14/main</root>
+<uuid>c75942e5-8b7a-354d-b1cf-73dee23fa94f</uuid>
+</repository>
+<wc-info>
+<schedule>normal</schedule>
+<text-updated>2013-07-13T15:34:08.109375Z</text-updated>
+<checksum>a6166e5e98a5a503089cde9bc8031293</checksum>
+</wc-info>
+<commit
+   revision="3">
+<author>ptt</author>
+<date>2013-07-13T15:34:08.390625Z</date>
+</commit>
+</entry>
+<entry
+   kind="file"
+   path="svn14_example\to_delete"
+   revision="6">
+<url>file:///C:/development/svn_example/repos/svn14/main/to_delete</url>
+<repository>
+<root>file:///C:/development/svn_example/repos/svn14/main</root>
+<uuid>c75942e5-8b7a-354d-b1cf-73dee23fa94f</uuid>
+</repository>
+<wc-info>
+<schedule>delete</schedule>
+<text-updated>2013-07-13T15:34:14.125000Z</text-updated>
+<checksum>d41d8cd98f00b204e9800998ecf8427e</checksum>
+</wc-info>
+<commit
+   revision="6">
+<author>ptt</author>
+<date>2013-07-13T15:34:14.406250Z</date>
+</commit>
+</entry>
+<entry
+   kind="dir"
+   path="svn14_example\folder"
+   revision="6">
+<url>file:///C:/development/svn_example/repos/svn14/main/folder</url>
+<repository>
+<root>file:///C:/development/svn_example/repos/svn14/main</root>
+<uuid>c75942e5-8b7a-354d-b1cf-73dee23fa94f</uuid>
+</repository>
+<wc-info>
+<schedule>normal</schedule>
+</wc-info>
+<commit
+   revision="5">
+<author>ptt</author>
+<date>2013-07-13T15:34:12.390625Z</date>
+</commit>
+</entry>
+<entry
+   kind="file"
+   path="svn14_example\folder\quest.txt"
+   revision="6">
+<url>file:///C:/development/svn_example/repos/svn14/main/folder/quest.txt</url>
+<repository>
+<root>file:///C:/development/svn_example/repos/svn14/main</root>
+<uuid>c75942e5-8b7a-354d-b1cf-73dee23fa94f</uuid>
+</repository>
+<wc-info>
+<schedule>normal</schedule>
+<text-updated>2013-07-13T15:34:07.109375Z</text-updated>
+<checksum>795240c6a830c14f83961e57e07dad12</checksum>
+</wc-info>
+<commit
+   revision="2">
+<author>ptt</author>
+<date>2013-07-13T15:34:07.390625Z</date>
+</commit>
+</entry>
+<entry
+   kind="file"
+   path="svn14_example\folder\lalala.txt"
+   revision="6">
+<url>file:///C:/development/svn_example/repos/svn14/main/folder/lalala.txt</url>
+<repository>
+<root>file:///C:/development/svn_example/repos/svn14/main</root>
+<uuid>c75942e5-8b7a-354d-b1cf-73dee23fa94f</uuid>
+</repository>
+<wc-info>
+<schedule>normal</schedule>
+<text-updated>2013-07-13T15:34:06.250000Z</text-updated>
+<checksum>d41d8cd98f00b204e9800998ecf8427e</checksum>
+</wc-info>
+<commit
+   revision="1">
+<author>ptt</author>
+<date>2013-07-13T15:34:06.531250Z</date>
+</commit>
+</entry>
+</info>

File setuptools/tests/svn_data/svn15_example.zip

Binary file added.

File setuptools/tests/svn_data/svn15_ext_list.txt

View file
+third_party3 file:///C:/development/svn_example/repos/svn15/extra1
+-r3 file:///C:/development/svn_example/repos/svn15/extra1 third_party2
+file:///C:/development/svn_example/repos/svn15/extra1@r1 third_party
+

File setuptools/tests/svn_data/svn15_ext_list.xml

View file
+<?xml version="1.0"?>
+<properties>
+<target
+   path="C:/development/svn_example/svn15_example/folder">
+<property
+   name="svn:externals">third_party3 file:///C:/development/svn_example/repos/svn15/extra2
+-r3 file:///C:/development/svn_example/repos/svn15/extra2 third_party2
+file:///C:/development/svn_example/repos/svn15/extra2@r1 third_party大介
+</property>
+</target>
+<target
+   path="C:/development/svn_example/svn15_example">
+<property
+   name="svn:externals">third_party3 file:///C:/development/svn_example/repos/svn15/extra1
+-r3 file:///C:/development/svn_example/repos/svn15/extra1 third_party2
+file:///C:/development/svn_example/repos/svn15/extra1@r1 third_party大介
+</property>
+</target>
+</properties>

File setuptools/tests/svn_data/svn15_info.xml

View file
+<?xml version="1.0"?>
+<info>
+<entry
+   kind="dir"
+   path="svn15_example"
+   revision="6">
+<url>file:///C:/development/svn_example/repos/svn15/main</url>
+<repository>
+<root>file:///C:/development/svn_example/repos/svn15/main</root>
+<uuid>4eab6983-54fe-384b-a282-9306f52d948f</uuid>
+</repository>
+<wc-info>
+<schedule>normal</schedule>
+<depth>infinity</depth>
+</wc-info>
+<commit
+   revision="6">
+<author>ptt</author>
+<date>2013-07-13T15:34:49.562500Z</date>
+</commit>
+</entry>
+<entry
+   kind="file"
+   path="svn15_example\a file"
+   revision="6">
+<url>file:///C:/development/svn_example/repos/svn15/main/a%20file</url>
+<repository>
+<root>file:///C:/development/svn_example/repos/svn15/main</root>
+<uuid>4eab6983-54fe-384b-a282-9306f52d948f</uuid>
+</repository>
+<wc-info>
+<schedule>normal</schedule>
+<depth>infinity</depth>
+<text-updated>2013-07-13T15:34:43.109375Z</text-updated>
+<checksum>a6166e5e98a5a503089cde9bc8031293</checksum>
+</wc-info>
+<commit
+   revision="3">
+<author>ptt</author>
+<date>2013-07-13T15:34:43.484375Z</date>
+</commit>
+</entry>
+<entry
+   kind="file"
+   path="svn15_example\to_delete"
+   revision="6">
+<url>file:///C:/development/svn_example/repos/svn15/main/to_delete</url>
+<repository>
+<root>file:///C:/development/svn_example/repos/svn15/main</root>
+<uuid>4eab6983-54fe-384b-a282-9306f52d948f</uuid>
+</repository>
+<wc-info>
+<schedule>delete</schedule>
+<depth>infinity</depth>
+<text-updated>2013-07-13T15:34:49.125000Z</text-updated>
+<checksum>d41d8cd98f00b204e9800998ecf8427e</checksum>
+</wc-info>
+<commit
+   revision="6">
+<author>ptt</author>
+<date>2013-07-13T15:34:49.562500Z</date>
+</commit>
+</entry>
+<entry
+   kind="dir"
+   path="svn15_example\folder"
+   revision="6">
+<url>file:///C:/development/svn_example/repos/svn15/main/folder</url>
+<repository>
+<root>file:///C:/development/svn_example/repos/svn15/main</root>
+<uuid>4eab6983-54fe-384b-a282-9306f52d948f</uuid>
+</repository>
+<wc-info>
+<schedule>normal</schedule>
+<depth>infinity</depth>
+</wc-info>
+<commit
+   revision="5">
+<author>ptt</author>
+<date>2013-07-13T15:34:47.515625Z</date>
+</commit>
+</entry>
+<entry
+   kind="file"
+   path="svn15_example\folder\quest.txt"
+   revision="6">
+<url>file:///C:/development/svn_example/repos/svn15/main/folder/quest.txt</url>
+<repository>
+<root>file:///C:/development/svn_example/repos/svn15/main</root>
+<uuid>4eab6983-54fe-384b-a282-9306f52d948f</uuid>
+</repository>
+<wc-info>
+<schedule>normal</schedule>
+<depth>infinity</depth>
+<text-updated>2013-07-13T15:34:42.109375Z</text-updated>
+<checksum>795240c6a830c14f83961e57e07dad12</checksum>
+</wc-info>
+<commit
+   revision="2">
+<author>ptt</author>
+<date>2013-07-13T15:34:42.484375Z</date>
+</commit>
+</entry>
+<entry
+   kind="file"
+   path="svn15_example\folder\lalala.txt"
+   revision="6">
+<url>file:///C:/development/svn_example/repos/svn15/main/folder/lalala.txt</url>
+<repository>
+<root>file:///C:/development/svn_example/repos/svn15/main</root>
+<uuid>4eab6983-54fe-384b-a282-9306f52d948f</uuid>
+</repository>
+<wc-info>
+<schedule>normal</schedule>
+<depth>infinity</depth>
+<text-updated>2013-07-13T15:34:41.375000Z</text-updated>
+<checksum>d41d8cd98f00b204e9800998ecf8427e</checksum>
+</wc-info>
+<commit
+   revision="1">
+<author>ptt</author>
+<date>2013-07-13T15:34:41.734375Z</date>
+</commit>
+</entry>
+</info>

File setuptools/tests/svn_data/svn16_example.zip

Binary file added.

File setuptools/tests/svn_data/svn16_ext_list.txt

View file
+"third party3" file:///C:/development/svn_example/repos/svn16/extra1 
+'third party3b' file:///C:/development/svn_example/repos/svn16/extra1 
+-r3 file:///C:/development/svn_example/repos/svn16/extra1 third\ party2
+file:///C:/development/svn_example/repos/svn16/extra1@r1 third_party

File setuptools/tests/svn_data/svn16_ext_list.xml

View file
+<?xml version="1.0"?>
+<properties>
+<target
+   path="C:/development/svn_example/svn16_example/folder">
+<property
+   name="svn:externals">"third party3" file:///C:/development/svn_example/repos/svn16/extra2 
+-r3 file:///C:/development/svn_example/repos/svn16/extra2 third\ party2
+file:///C:/development/svn_example/repos/svn16/extra2@r1 third_party大介
+</property>
+</target>
+<target
+   path="C:/development/svn_example/svn16_example">
+<property
+   name="svn:externals">"third party3" file:///C:/development/svn_example/repos/svn16/extra1 
+-r3 file:///C:/development/svn_example/repos/svn16/extra1 third\ party2
+file:///C:/development/svn_example/repos/svn16/extra1@r1 third_party大介
+</property>
+</target>
+</properties>

File setuptools/tests/svn_data/svn16_info.xml

View file
+<?xml version="1.0"?>
+<info>
+<entry
+   kind="dir"
+   path="svn16_example"
+   revision="6">
+<url>file:///C:/development/svn_example/repos/svn16/main</url>
+<repository>
+<root>file:///C:/development/svn_example/repos/svn16/main</root>
+<uuid>bd8d2cfc-1a74-de45-b166-262010c17c0a</uuid>
+</repository>
+<wc-info>
+<schedule>normal</schedule>
+<depth>infinity</depth>
+</wc-info>
+<commit
+   revision="6">
+<author>ptt</author>
+<date>2013-07-13T15:35:17.390625Z</date>
+</commit>
+</entry>
+<entry
+   kind="file"
+   path="svn16_example\a file"
+   revision="6">
+<url>file:///C:/development/svn_example/repos/svn16/main/a%20file</url>
+<repository>
+<root>file:///C:/development/svn_example/repos/svn16/main</root>
+<uuid>bd8d2cfc-1a74-de45-b166-262010c17c0a</uuid>
+</repository>
+<wc-info>
+<schedule>normal</schedule>
+<depth>infinity</depth>
+<text-updated>2013-07-13T15:35:14.578125Z</text-updated>
+<checksum>a6166e5e98a5a503089cde9bc8031293</checksum>
+</wc-info>
+<commit
+   revision="3">
+<author>ptt</author>
+<date>2013-07-13T15:35:14.906250Z</date>
+</commit>
+</entry>
+<entry
+   kind="file"
+   path="svn16_example\to_delete"
+   revision="6">
+<url>file:///C:/development/svn_example/repos/svn16/main/to_delete</url>
+<repository>
+<root>file:///C:/development/svn_example/repos/svn16/main</root>
+<uuid>bd8d2cfc-1a74-de45-b166-262010c17c0a</uuid>
+</repository>
+<wc-info>
+<schedule>delete</schedule>
+<depth>infinity</depth>
+<text-updated>2013-07-13T15:35:17.046875Z</text-updated>
+<checksum>d41d8cd98f00b204e9800998ecf8427e</checksum>
+</wc-info>
+<commit
+   revision="6">
+<author>ptt</author>
+<date>2013-07-13T15:35:17.390625Z</date>
+</commit>
+</entry>
+<entry
+   kind="dir"
+   path="svn16_example\folder"
+   revision="6">
+<url>file:///C:/development/svn_example/repos/svn16/main/folder</url>
+<repository>
+<root>file:///C:/development/svn_example/repos/svn16/main</root>
+<uuid>bd8d2cfc-1a74-de45-b166-262010c17c0a</uuid>
+</repository>
+<wc-info>
+<schedule>normal</schedule>
+<depth>infinity</depth>
+</wc-info>
+<commit
+   revision="5">
+<author>ptt</author>
+<date>2013-07-13T15:35:16.406250Z</date>
+</commit>
+</entry>
+<entry
+   kind="file"
+   path="svn16_example\folder\quest.txt"
+   revision="6">
+<url>file:///C:/development/svn_example/repos/svn16/main/folder/quest.txt</url>
+<repository>
+<root>file:///C:/development/svn_example/repos/svn16/main</root>
+<uuid>bd8d2cfc-1a74-de45-b166-262010c17c0a</uuid>
+</repository>
+<wc-info>
+<schedule>normal</schedule>
+<depth>infinity</depth>
+<text-updated>2013-07-13T15:35:14.078125Z</text-updated>
+<checksum>795240c6a830c14f83961e57e07dad12</checksum>
+</wc-info>
+<commit
+   revision="2">
+<author>ptt</author>
+<date>2013-07-13T15:35:14.421875Z</date>
+</commit>
+</entry>
+<entry
+   kind="file"
+   path="svn16_example\folder\lalala.txt"
+   revision="6">
+<url>file:///C:/development/svn_example/repos/svn16/main/folder/lalala.txt</url>
+<repository>
+<root>file:///C:/development/svn_example/repos/svn16/main</root>
+<uuid>bd8d2cfc-1a74-de45-b166-262010c17c0a</uuid>
+</repository>
+<wc-info>
+<schedule>normal</schedule>
+<depth>infinity</depth>
+<text-updated>2013-07-13T15:35:12.171875Z</text-updated>
+<checksum>d41d8cd98f00b204e9800998ecf8427e</checksum>
+</wc-info>
+<commit
+   revision="1">
+<author>ptt</author>
+<date>2013-07-13T15:35:13.906250Z</date>
+</commit>
+</entry>
+</info>

File setuptools/tests/svn_data/svn17_example.zip

Binary file added.

File setuptools/tests/svn_data/svn17_ext_list.txt

View file
+"third party3" file:///C:/development/svn_example/repos/svn17/extra1 
+'third party3b' file:///C:/development/svn_example/repos/svn17/extra1 
+-r3 file:///C:/development/svn_example/repos/svn17/extra1 third\ party2
+file:///C:/development/svn_example/repos/svn17/extra1@r1 third_party

File setuptools/tests/svn_data/svn17_ext_list.xml

View file
+<?xml version="1.0" encoding="UTF-8"?>
+<properties>
+<target
+   path="C:/development/svn_example/svn17_example">
+<property
+   name="svn:externals">"third party3" file:///C:/development/svn_example/repos/svn16/extra1 
+-r3 file:///C:/development/svn_example/repos/svn16/extra1 third\ party2
+file:///C:/development/svn_example/repos/svn16/extra1@r1 third_party大介
+</property>
+</target>
+<target
+   path="C:/development/svn_example/svn17_example/folder">
+<property
+   name="svn:externals">"third party3" file:///C:/development/svn_example/repos/svn17/extra2 
+-r3 file:///C:/development/svn_example/repos/svn17/extra2 third\ party2
+file:///C:/development/svn_example/repos/svn17/extra2@r1 third_party大介
+</property>
+</target>
+</properties>

File setuptools/tests/svn_data/svn17_info.xml

View file
+<?xml version="1.0" encoding="UTF-8"?>
+<info>
+<entry
+   kind="dir"
+   path="svn17_example"
+   revision="6">
+<url>file:///C:/development/svn_example/repos/svn17/main</url>
+<repository>
+<root>file:///C:/development/svn_example/repos/svn17/main</root>
+<uuid>5ba45434-5197-164e-afab-81923f4744f5</uuid>
+</repository>
+<wc-info>
+<wcroot-abspath>C:/development/svn_example/svn17_example</wcroot-abspath>
+<schedule>normal</schedule>
+<depth>infinity</depth>
+</wc-info>
+<commit
+   revision="6">
+<author>ptt</author>
+<date>2013-07-13T15:35:36.171875Z</date>
+</commit>
+</entry>
+<entry
+   path="svn17_example\folder"
+   revision="6"
+   kind="dir">
+<url>file:///C:/development/svn_example/repos/svn17/main/folder</url>
+<repository>
+<root>file:///C:/development/svn_example/repos/svn17/main</root>
+<uuid>5ba45434-5197-164e-afab-81923f4744f5</uuid>
+</repository>
+<wc-info>
+<wcroot-abspath>C:/development/svn_example/svn17_example</wcroot-abspath>
+<schedule>normal</schedule>
+<depth>infinity</depth>
+</wc-info>
+<commit
+   revision="5">
+<author>ptt</author>
+<date>2013-07-13T15:35:34.859375Z</date>
+</commit>
+</entry>
+<entry
+   kind="file"
+   path="svn17_example\folder\quest.txt"
+   revision="6">
+<url>file:///C:/development/svn_example/repos/svn17/main/folder/quest.txt</url>
+<repository>
+<root>file:///C:/development/svn_example/repos/svn17/main</root>
+<uuid>5ba45434-5197-164e-afab-81923f4744f5</uuid>
+</repository>
+<wc-info>
+<wcroot-abspath>C:/development/svn_example/svn17_example</wcroot-abspath>
+<schedule>normal</schedule>
+<depth>infinity</depth>
+<text-updated>2013-07-13T15:35:32.812500Z</text-updated>
+<checksum>bc80eba9e7a10c0a571a4678c520bc9683f3bac2</checksum>
+</wc-info>
+<commit
+   revision="2">
+<author>ptt</author>
+<date>2013-07-13T15:35:33.109375Z</date>
+</commit>
+</entry>
+<entry
+   kind="file"
+   path="svn17_example\folder\lalala.txt"
+   revision="6">
+<url>file:///C:/development/svn_example/repos/svn17/main/folder/lalala.txt</url>
+<repository>
+<root>file:///C:/development/svn_example/repos/svn17/main</root>
+<uuid>5ba45434-5197-164e-afab-81923f4744f5</uuid>
+</repository>
+<wc-info>
+<wcroot-abspath>C:/development/svn_example/svn17_example</wcroot-abspath>
+<schedule>normal</schedule>
+<depth>infinity</depth>
+<text-updated>2013-07-13T15:35:32.343750Z</text-updated>
+<checksum>da39a3ee5e6b4b0d3255bfef95601890afd80709</checksum>
+</wc-info>
+<commit
+   revision="1">
+<author>ptt</author>
+<date>2013-07-13T15:35:32.687500Z</date>
+</commit>
+</entry>
+<entry
+   path="svn17_example\a file"
+   revision="6"
+   kind="file">
+<url>file:///C:/development/svn_example/repos/svn17/main/a%20file</url>
+<repository>
+<root>file:///C:/development/svn_example/repos/svn17/main</root>
+<uuid>5ba45434-5197-164e-afab-81923f4744f5</uuid>
+</repository>
+<wc-info>
+<wcroot-abspath>C:/development/svn_example/svn17_example</wcroot-abspath>
+<schedule>normal</schedule>
+<depth>infinity</depth>
+<text-updated>2013-07-13T15:35:33.187500Z</text-updated>
+<checksum>43785ab4b1816b49f242990883292813cd4f486c</checksum>
+</wc-info>
+<commit
+   revision="3">
+<author>ptt</author>
+<date>2013-07-13T15:35:33.515625Z</date>
+</commit>
+</entry>
+<entry
+   path="svn17_example\to_delete"
+   revision="6"
+   kind="file">
+<url>file:///C:/development/svn_example/repos/svn17/main/to_delete</url>
+<repository>
+<root>file:///C:/development/svn_example/repos/svn17/main</root>
+<uuid>5ba45434-5197-164e-afab-81923f4744f5</uuid>
+</repository>
+<wc-info>
+<wcroot-abspath>C:/development/svn_example/svn17_example</wcroot-abspath>
+<schedule>delete</schedule>
+<depth>infinity</depth>
+<checksum>da39a3ee5e6b4b0d3255bfef95601890afd80709</checksum>
+</wc-info>
+<commit
+   revision="6">
+<author>ptt</author>
+<date>2013-07-13T15:35:36.171875Z</date>
+</commit>
+</entry>
+</info>

File setuptools/tests/svn_data/svn18_example.zip

Binary file added.

File setuptools/tests/svn_data/svn18_ext_list.txt

View file
+"third party3" file:///C:/development/svn_example/repos/svn18/extra1 
+'third party3b' file:///C:/development/svn_example/repos/svn18/extra1 
+-r3 file:///C:/development/svn_example/repos/svn18/extra1 third\ party2
+file:///C:/development/svn_example/repos/svn18/extra1@r1 third_party

File setuptools/tests/svn_data/svn18_ext_list.xml

View file
+<?xml version="1.0" encoding="UTF-8"?>
+<properties>
+<target
+   path="C:/development/svn_example/svn18_example">
+<property
+   name="svn:externals">"third party3" file:///C:/development/svn_example/repos/svn16/extra1 
+-r3 file:///C:/development/svn_example/repos/svn16/extra1 third\ party2
+file:///C:/development/svn_example/repos/svn16/extra1@r1 third_party大介
+</property>
+</target>
+<target
+   path="C:/development/svn_example/svn18_example/folder">
+<property
+   name="svn:externals">"third party3" file:///C:/development/svn_example/repos/svn18/extra2 
+-r3 file:///C:/development/svn_example/repos/svn18/extra2 third\ party2
+file:///C:/development/svn_example/repos/svn18/extra2@r1 third_party大介
+</property>
+</target>
+</properties>

File setuptools/tests/svn_data/svn18_info.xml

View file
+<?xml version="1.0" encoding="UTF-8"?>
+<info>
+<entry
+   path="svn18_example"
+   revision="6"
+   kind="dir">
+<url>file:///C:/development/svn_example/repos/svn18/main</url>
+<relative-url>^/</relative-url>
+<repository>
+<root>file:///C:/development/svn_example/repos/svn18/main</root>
+<uuid>3c5e3929-c92b-7045-9ba9-5e65d3dd1ee9</uuid>
+</repository>
+<wc-info>
+<wcroot-abspath>C:/development/svn_example/svn18_example</wcroot-abspath>
+<schedule>normal</schedule>
+<depth>infinity</depth>
+</wc-info>
+<commit
+   revision="6">
+<author>ptt</author>
+<date>2013-07-13T15:35:57.796875Z</date>
+</commit>
+</entry>
+<entry
+   kind="file"
+   path="svn18_example\a file"
+   revision="6">
+<url>file:///C:/development/svn_example/repos/svn18/main/a%20file</url>
+<relative-url>^/a%20file</relative-url>
+<repository>
+<root>file:///C:/development/svn_example/repos/svn18/main</root>
+<uuid>3c5e3929-c92b-7045-9ba9-5e65d3dd1ee9</uuid>
+</repository>
+<wc-info>
+<wcroot-abspath>C:/development/svn_example/svn18_example</wcroot-abspath>
+<schedule>normal</schedule>
+<depth>infinity</depth>
+<text-updated>2013-07-13T15:35:54.906250Z</text-updated>
+<checksum>43785ab4b1816b49f242990883292813cd4f486c</checksum>
+</wc-info>
+<commit
+   revision="3">
+<author>ptt</author>
+<date>2013-07-13T15:35:55.265625Z</date>
+</commit>
+</entry>
+<entry
+   kind="file"
+   path="svn18_example\to_delete"
+   revision="6">
+<url>file:///C:/development/svn_example/repos/svn18/main/to_delete</url>
+<relative-url>^/to_delete</relative-url>
+<repository>
+<root>file:///C:/development/svn_example/repos/svn18/main</root>
+<uuid>3c5e3929-c92b-7045-9ba9-5e65d3dd1ee9</uuid>
+</repository>
+<wc-info>
+<wcroot-abspath>C:/development/svn_example/svn18_example</wcroot-abspath>
+<schedule>delete</schedule>
+<depth>infinity</depth>
+<checksum>da39a3ee5e6b4b0d3255bfef95601890afd80709</checksum>
+</wc-info>
+<commit
+   revision="6">
+<author>ptt</author>
+<date>2013-07-13T15:35:57.796875Z</date>
+</commit>
+</entry>
+<entry
+   kind="dir"
+   path="svn18_example\folder"
+   revision="6">
+<url>file:///C:/development/svn_example/repos/svn18/main/folder</url>
+<relative-url>^/folder</relative-url>
+<repository>
+<root>file:///C:/development/svn_example/repos/svn18/main</root>
+<uuid>3c5e3929-c92b-7045-9ba9-5e65d3dd1ee9</uuid>
+</repository>
+<wc-info>
+<wcroot-abspath>C:/development/svn_example/svn18_example</wcroot-abspath>
+<schedule>normal</schedule>
+<depth>infinity</depth>
+</wc-info>
+<commit
+   revision="5">
+<author>ptt</author>
+<date>2013-07-13T15:35:56.750000Z</date>
+</commit>
+</entry>
+<entry
+   path="svn18_example\folder\quest.txt"
+   revision="6"
+   kind="file">
+<url>file:///C:/development/svn_example/repos/svn18/main/folder/quest.txt</url>
+<relative-url>^/folder/quest.txt</relative-url>
+<repository>
+<root>file:///C:/development/svn_example/repos/svn18/main</root>
+<uuid>3c5e3929-c92b-7045-9ba9-5e65d3dd1ee9</uuid>
+</repository>
+<wc-info>
+<wcroot-abspath>C:/development/svn_example/svn18_example</wcroot-abspath>
+<schedule>normal</schedule>
+<depth>infinity</depth>
+<text-updated>2013-07-13T15:35:54.484375Z</text-updated>
+<checksum>bc80eba9e7a10c0a571a4678c520bc9683f3bac2</checksum>
+</wc-info>
+<commit
+   revision="2">
+<author>ptt</author>
+<date>2013-07-13T15:35:54.843750Z</date>
+</commit>
+</entry>
+<entry
+   path="svn18_example\folder\lalala.txt"
+   revision="6"
+   kind="file">
+<url>file:///C:/development/svn_example/repos/svn18/main/folder/lalala.txt</url>
+<relative-url>^/folder/lalala.txt</relative-url>
+<repository>
+<root>file:///C:/development/svn_example/repos/svn18/main</root>
+<uuid>3c5e3929-c92b-7045-9ba9-5e65d3dd1ee9</uuid>
+</repository>
+<wc-info>
+<wcroot-abspath>C:/development/svn_example/svn18_example</wcroot-abspath>
+<schedule>normal</schedule>
+<depth>infinity</depth>
+<text-updated>2013-07-13T15:35:54.015625Z</text-updated>
+<checksum>da39a3ee5e6b4b0d3255bfef95601890afd80709</checksum>
+</wc-info>
+<commit
+   revision="1">
+<author>ptt</author>
+<date>2013-07-13T15:35:54.375000Z</date>
+</commit>
+</entry>
+</info>

File setuptools/tests/test_egg_info.py

View file
 import os
+import sys
 import tempfile
 import shutil
 import unittest
 
 import pkg_resources
 from setuptools.command import egg_info
+from setuptools import svn_utils
 
 ENTRIES_V10 = pkg_resources.resource_string(__name__, 'entries-v10')
 "An entries file generated with svn 1.6.17 against the legacy Setuptools repo"
     def test_version_10_format(self):
         """
         """
+        #keeping this set for 1.6 is a good check on the get_svn_revision
+        #to ensure I return using svnversion what would had been returned
+        version_str = svn_utils.SvnInfo.get_svn_version()
+        version = [int(x) for x in version_str.split('.')[:2]]
+        if version != [1,6]:
+            if hasattr(self, 'skipTest'):
+                self.skipTest('')
+            else:
+                sys.stderr.write('\n   Skipping due to SVN Version\n')
+                return
+
         self._write_entries(ENTRIES_V10)
         rev = egg_info.egg_info.get_svn_revision()
         self.assertEqual(rev, '89000')

File setuptools/tests/test_sdist.py

View file
 import tempfile
 import unittest
 import unicodedata
+from setuptools.tests import environment
 
 from setuptools.compat import StringIO, unicode
-from setuptools.command.sdist import sdist
+from setuptools.command.sdist import sdist, walk_revctrl
 from setuptools.command.egg_info import manifest_maker
 from setuptools.dist import Distribution
-
+from setuptools import svn_utils
+from setuptools.svn_utils import fsencode
 
 SETUP_ATTRS = {
     'name': 'sdist_test',
             self.assertTrue(filename in cmd.filelist.files)
 
 
+class TestSvn(environment.ZippedEnvironment):
+
+    def setUp(self):
+        version = svn_utils.SvnInfo.get_svn_version()
+        self.base_version = tuple([int(x) for x in version.split('.')][:2])
+
+        if not self.base_version:
+            raise ValueError('No SVN tools installed')
+        elif self.base_version < (1,3):
+            raise ValueError('Insufficient SVN Version %s' % version)
+        elif self.base_version >= (1,9):
+            #trying the latest version
+            self.base_version = (1,8)
+
+        self.dataname = "svn%i%i_example" % self.base_version
+        self.datafile = os.path.join('setuptools', 'tests',
+                                     'svn_data', self.dataname + ".zip")
+        super(TestSvn, self).setUp()
+
+    def test_walksvn(self):
+        if self.base_version >= (1,6):
+            folder2 = 'third party2'
+            folder3 = 'third party3'
+        else:
+            folder2 = 'third_party2'
+            folder3 = 'third_party3'
+
+        #TODO is this right
+        expected = set([
+            os.path.join('a file'),
+            os.path.join(folder2, 'Changes.txt'),
+            os.path.join(folder2, 'MD5SUMS'),
+            os.path.join(folder2, 'README.txt'),
+            os.path.join(folder3, 'Changes.txt'),
+            os.path.join(folder3, 'MD5SUMS'),
+            os.path.join(folder3, 'README.txt'),
+            os.path.join(folder3, 'TODO.txt'),
+            os.path.join(folder3, 'fin'),
+            os.path.join('third_party', 'README.txt'),
+            os.path.join('folder', folder2, 'Changes.txt'),
+            os.path.join('folder', folder2, 'MD5SUMS'),
+            os.path.join('folder', folder2, 'WatashiNiYomimasu.txt'),
+            os.path.join( 'folder', folder3, 'Changes.txt'),
+            os.path.join('folder', folder3, 'fin'),
+            os.path.join('folder', folder3, 'MD5SUMS'),
+            os.path.join('folder', folder3, 'oops'),
+            os.path.join('folder', folder3, 'WatashiNiYomimasu.txt'),
+            os.path.join('folder', folder3, 'ZuMachen.txt'),
+            os.path.join('folder', 'third_party', 'WatashiNiYomimasu.txt'),
+            os.path.join('folder', 'lalala.txt'),
+            os.path.join('folder', 'quest.txt'),
+            #The example will have a deleted file (or should) but shouldn't return it
+            ])
+        expected = set(fsencode(x) for x in expected)
+        self.assertEqual(set(x for x in walk_revctrl()), expected)
+
+
+
 def test_suite():
     return unittest.defaultTestLoader.loadTestsFromName(__name__)
 

File setuptools/tests/test_svn.py

View file
 
 
 import os
-import zipfile
 import sys
-import tempfile
 import unittest
-import shutil
-import stat
+import codecs
+from setuptools.tests import environment
+from setuptools.svn_utils import fsencode
+from setuptools.compat import unicode, unichr
 
 from setuptools import svn_utils
-from setuptools.command import egg_info
-from setuptools.command import sdist
 
 #requires python >= 2.4
 from subprocess import call as _call
 
-def _extract(self, member, path=None, pwd=None):
-    """for zipfile py2.5 borrowed from cpython"""
-    if not isinstance(member, zipfile.ZipInfo):
-        member = self.getinfo(member)
+from distutils import log
 
-    if path is None:
-        path = os.getcwd()
-
-    return _extract_member(self, member, path, pwd)
-
-def _extract_from_zip(self, name, dest_path):
-    dest_file = open(dest_path, 'wb')
-    try:
-        dest_file.write(self.read(name))
-    finally:
-        dest_file.close()
-
-def _extract_member(self, member, targetpath, pwd):
-    """for zipfile py2.5 borrowed from cpython"""
-    # build the destination pathname, replacing
-    # forward slashes to platform specific separators.
-    # Strip trailing path separator, unless it represents the root.
-    if (targetpath[-1:] in (os.path.sep, os.path.altsep)
-        and len(os.path.splitdrive(targetpath)[1]) > 1):
-        targetpath = targetpath[:-1]
-
-    # don't include leading "/" from file name if present
-    if member.filename[0] == '/':
-        targetpath = os.path.join(targetpath, member.filename[1:])
-    else:
-        targetpath = os.path.join(targetpath, member.filename)
-
-    targetpath = os.path.normpath(targetpath)
-
-    # Create all upper directories if necessary.
-    upperdirs = os.path.dirname(targetpath)
-    if upperdirs and not os.path.exists(upperdirs):
-        os.makedirs(upperdirs)
-
-    if member.filename[-1] == '/':
-        if not os.path.isdir(targetpath):
-            os.mkdir(targetpath)
-        return targetpath
-
-    _extract_from_zip(self, member.filename, targetpath)
-
-    return targetpath
-
-
-def _remove_dir(target):
-
-    #on windows this seems to a problem
-    for dir_path, dirs, files in os.walk(target):
-        os.chmod(dir_path, stat.S_IWRITE)
-        for filename in files:
-            os.chmod(os.path.join(dir_path, filename), stat.S_IWRITE)
-    shutil.rmtree(target)
 
 
 class TestSvnVersion(unittest.TestCase):
         old_path = os.environ[path_variable]
         os.environ[path_variable] = ''
         try:
-            version = svn_utils.get_svn_tool_version()
+            version = svn_utils.SvnInfo.get_svn_version()
             self.assertEqual(version, '')
         finally:
             os.environ[path_variable] = old_path
 
     def test_svn_should_exist(self):
-        version = svn_utils.get_svn_tool_version()
+        version = svn_utils.SvnInfo.get_svn_version()
         self.assertNotEqual(version, '')
 
+def _read_utf8_file(path):
+    fileobj = None
+    try:
+        fileobj = codecs.open(path, 'r', 'utf-8')
+        data = fileobj.read()
+        return data
+    finally:
+        if fileobj:
+            fileobj.close()
 
-class TestSvn_1_7(unittest.TestCase):
+
+class ParserInfoXML(unittest.TestCase):
+
+    def parse_tester(self, svn_name, ext_spaces):
+        path = os.path.join('setuptools', 'tests',
+                            'svn_data', svn_name + '_info.xml')
+        #Remember these are pre-generated to test XML parsing
+        #  so these paths might not valid on your system
+        example_base = "%s_example" % svn_name
+
+        data = _read_utf8_file(path)
+
+        if ext_spaces:
+            folder2 = 'third party2'
+            folder3 = 'third party3'
+        else:
+            folder2 = 'third_party2'
+            folder3 = 'third_party3'
+
+        expected = set([
+            ("\\".join((example_base, 'a file')), 'file'),
+            ("\\".join((example_base, 'folder')), 'dir'),
+            ("\\".join((example_base, 'folder', 'lalala.txt')), 'file'),
+            ("\\".join((example_base, 'folder', 'quest.txt')), 'file'),
+            ])
+        self.assertEqual(set(x for x in svn_utils.parse_dir_entries(data)),
+                         expected)
+
+    def test_svn13(self):
+        self.parse_tester('svn13', False)
+
+    def test_svn14(self):
+        self.parse_tester('svn14', False)
+
+    def test_svn15(self):
+        self.parse_tester('svn15', False)
+
+    def test_svn16(self):
+        self.parse_tester('svn16', True)
+
+    def test_svn17(self):
+        self.parse_tester('svn17', True)
+
+    def test_svn18(self):
+        self.parse_tester('svn18', True)
+
+class ParserExternalXML(unittest.TestCase):
+
+    def parse_tester(self, svn_name, ext_spaces):
+        path = os.path.join('setuptools', 'tests',
+                            'svn_data', svn_name + '_ext_list.xml')
+        example_base = svn_name + '_example'
+        data = _read_utf8_file(path)
+
+        if ext_spaces:
+            folder2 = 'third party2'
+            folder3 = 'third party3'
+        else:
+            folder2 = 'third_party2'
+            folder3 = 'third_party3'
+
+        expected = set([
+            "\\".join((example_base, folder2)),
+            "\\".join((example_base, folder3)),
+                                     #third_party大介
+            "\\".join((example_base,
+                       unicode('third_party') +
+                       unichr(0x5927) + unichr(0x4ecb))),
+            "\\".join((example_base, 'folder', folder2)),
+            "\\".join((example_base, 'folder', folder3)),
+            "\\".join((example_base, 'folder',
+                       unicode('third_party') +
+                       unichr(0x5927) + unichr(0x4ecb))),
+            ])
+
+        dir_base = r'c:\development\svn_example'
+        self.assertEqual(set(x for x \
+            in svn_utils.parse_externals_xml(data, dir_base)), expected)
+
+    def test_svn15(self):
+        self.parse_tester('svn15', False)
+
+    def test_svn16(self):
+        self.parse_tester('svn16', True)
+
+    def test_svn17(self):
+        self.parse_tester('svn17', True)
+
+    def test_svn18(self):
+        self.parse_tester('svn18', True)
+
+
+class ParseExternal(unittest.TestCase):
+
+    def parse_tester(self, svn_name, ext_spaces):
+        path = os.path.join('setuptools', 'tests',
+                            'svn_data', svn_name + '_ext_list.txt')
+        example_base = svn_name + '_example'
+        data = _read_utf8_file(path)
+
+        if ext_spaces:
+            expected = set(['third party2', 'third party3',
+                            'third party3b', 'third_party'])
+        else:
+            expected = set(['third_party2', 'third_party3', 'third_party'])
+
+        self.assertEqual(set(x for x in svn_utils.parse_external_prop(data)),
+                         expected)
+
+    def test_svn13(self):
+        self.parse_tester('svn13', False)
+
+    def test_svn14(self):
+        self.parse_tester('svn14', False)
+
+    def test_svn15(self):
+        self.parse_tester('svn15', False)
+
+    def test_svn16(self):
+        self.parse_tester('svn16', True)
+
+    def test_svn17(self):
+        self.parse_tester('svn17', True)
+
+    def test_svn18(self):
+        self.parse_tester('svn18', True)
+
+
+class TestSvn(environment.ZippedEnvironment):
 
     def setUp(self):
-        version = svn_utils.get_svn_tool_version()
-        ver_list = [int(x) for x in version.split('.')]
-        if ver_list < [1,7,0]:
-            self.version_err = 'Insufficent Subversion (%s)' % version
+        version = svn_utils.SvnInfo.get_svn_version()
+        self.base_version = tuple([int(x) for x in version.split('.')[:2]])
+
+        if not self.base_version:
+            raise ValueError('No SVN tools installed')
+        elif self.base_version < (1,3):
+            raise ValueError('Insufficient SVN Version %s' % version)
+        elif self.base_version >= (1,9):
+            #trying the latest version
+            self.base_version = (1,8)
+
+        self.dataname = "svn%i%i_example" % self.base_version
+        self.datafile = os.path.join('setuptools', 'tests',