From 30bb58f069cf1624f35cfbdb725e8e443ff64330 Mon Sep 17 00:00:00 2001 From: Philip Thiem Date: Sat, 28 Sep 2013 12:14:12 -0500 Subject: Added a legacy fallback test Added in code to after a deprecation warning parse the .svn files Should also parse externals. --HG-- extra : rebase_source : 9dd3bcf22cb56eb0826051f9e477f155e47cdbf6 --- setuptools/svn_utils.py | 240 +++++++++++++++++++++++++++++++++++--- setuptools/tests/test_egg_info.py | 21 ++++ 2 files changed, 244 insertions(+), 17 deletions(-) (limited to 'setuptools') diff --git a/setuptools/svn_utils.py b/setuptools/svn_utils.py index 17b211d7..22b45cd7 100644 --- a/setuptools/svn_utils.py +++ b/setuptools/svn_utils.py @@ -6,7 +6,9 @@ import xml.dom.pulldom import shlex import locale import unicodedata +import warnings from setuptools.compat import unicode, bytes +from xml.sax.saxutils import unescape try: import urlparse @@ -23,6 +25,8 @@ from subprocess import Popen as _Popen, PIPE as _PIPE # http://bugs.python.org/issue8557 # 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: @@ -63,10 +67,10 @@ def _get_xml_data(decoded_str): return data -def joinpath(prefix, suffix): +def joinpath(prefix, *suffix): if not prefix or prefix == '.': - return suffix - return os.path.join(prefix, suffix) + return os.path.join(*suffix) + return os.path.join(prefix, *suffix) def fsencode(path): @@ -87,6 +91,7 @@ def fsencode(path): return path + def fsdecode(path): "Path must be unicode or in file system encoding already" encoding = sys.getfilesystemencoding() @@ -98,6 +103,7 @@ def fsdecode(path): return unicodedata.normalize('NFC', path) + def consoledecode(text): encoding = locale.getpreferredencoding() return text.decode(encoding) @@ -130,9 +136,7 @@ def parse_externals_xml(decoded_str, prefix=''): if event == 'START_ELEMENT' and node.nodeName == 'target': doc.expandNode(node) path = os.path.normpath(node.getAttribute('path')) - log.warn('') - log.warn('PRE: %s' % prefix) - log.warn('PTH: %s' % path) + if os.path.normcase(path).startswith(prefix): path = path[len(prefix)+1:] @@ -154,7 +158,7 @@ def parse_external_prop(lines): """ externals = [] for line in lines.splitlines(): - line = line.lstrip() #there might be a "\ " + line = line.lstrip() # there might be a "\ " if not line: continue @@ -178,6 +182,26 @@ def parse_external_prop(lines): return externals +def parse_prop_file(filename, key): + found = False + f = open(filename, 'rt') + data = '' + try: + for line in iter(f.readline, ''): # can't use direct iter! + parts = line.split() + if len(parts) == 2: + kind, length = parts + data = f.read(int(length)) + if kind == 'K' and data == key: + found = True + elif kind == 'V' and found: + break + finally: + f.close() + + return data + + class SvnInfo(object): ''' Generic svn_info object. No has little knowledge of how to extract @@ -203,14 +227,24 @@ class SvnInfo(object): @classmethod def load(cls, dirname=''): - code, data = _run_command(['svn', 'info', os.path.normpath(dirname)]) + normdir = os.path.normpath(dirname) + code, data = _run_command(['svn', 'info', normdir]) + has_svn = os.path.isdir(os.path.join(normdir, '.svn')) 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') + + try: + base_svn_version = tuple(int(x) for x in svn_version[:2]) + except ValueError: + base_svn_version = tuple() + + if has_svn and (code or not base_svn_version + or base_svn_version < (1, 3)): + log.warn('Fallback onto .svn parsing') + warnings.warn(("No SVN 1.3+ command found: falling back " + "on pre 1.7 .svn parsing"), DeprecationWarning) + return SvnFileInfo(dirname) + elif not has_svn: + log.warn('Not SVN Repository') return SvnInfo(dirname) elif base_svn_version < (1, 5): return Svn13Info(dirname) @@ -259,7 +293,7 @@ class SvnInfo(object): Iterate over the non-deleted file entries in the repository path ''' for item, kind in self.entries: - if kind.lower()=='file': + if kind.lower() == 'file': yield item def iter_dirs(self, include_root=True): @@ -269,7 +303,7 @@ class SvnInfo(object): if include_root: yield self.path for item, kind in self.entries: - if kind.lower()=='dir': + if kind.lower() == 'dir': yield item def get_entries(self): @@ -278,6 +312,7 @@ class SvnInfo(object): def get_externals(self): return [] + class Svn13Info(SvnInfo): def get_entries(self): code, data = _run_command(['svn', 'info', '-R', '--xml', self.path]) @@ -316,6 +351,73 @@ class Svn15Info(Svn13Info): return parse_externals_xml(lines, prefix=os.path.abspath(self.path)) +class SvnFileInfo(SvnInfo): + + def __init__(self, path=''): + super(SvnFileInfo, self).__init__(path) + self._directories = None + self._revision = None + + def _walk_svn(self, base): + entry_file = joinpath(base, '.svn', 'entries') + if os.path.isfile(entry_file): + entries = SVNEntriesFile.load(base) + yield (base, False, entries.parse_revision()) + for path in entries.get_undeleted_records(): + path = joinpath(base, path) + if os.path.isfile(path): + yield (path, True, None) + elif os.path.isdir(path): + for item in self._walk_svn(path): + yield item + + def _build_entries(self): + dirs = list() + files = list() + rev = 0 + for path, isfile, dir_rev in self._walk_svn(self.path): + if isfile: + files.append(path) + else: + dirs.append(path) + rev = max(rev, dir_rev) + + self._directories = dirs + self._entries = files + self._revision = rev + + def get_entries(self): + if self._entries is None: + self._build_entries() + return self._entries + + def get_revision(self): + if self._revision is None: + self._build_entries() + return self._revision + + def get_externals(self): + if self._directories is None: + self._build_entries() + + prop_files = [['.svn', 'dir-prop-base'], + ['.svn', 'dir-props']] + externals = [] + + for dirname in self._directories: + prop_file = None + for rel_parts in prop_files: + filename = joinpath(dirname, *rel_parts) + if os.path.isfile(filename): + prop_file = filename + + if prop_file is not None: + ext_prop = parse_prop_file(prop_file, 'svn:externals') + externals.extend(parse_external_prop(ext_prop)) + + return externals + + def svn_finder(dirname=''): #combined externals due to common interface #combined externals and entries due to lack of dir_props in 1.7 @@ -328,6 +430,110 @@ def svn_finder(dirname=''): for sub_path in sub_info.iter_files(): yield fsencode(sub_path) + +class SVNEntriesFile(object): + def __init__(self, data): + self.data = data + + @classmethod + def load(class_, base): + filename = os.path.join(base, '.svn', 'entries') + f = open(filename) + try: + result = SVNEntriesFile.read(f) + finally: + f.close() + return result + + @classmethod + def read(class_, fileobj): + data = fileobj.read() + is_xml = data.startswith(' revision_line_number + and section[revision_line_number]) + ] + return rev_numbers + + def get_undeleted_records(self): + undeleted = lambda s: s and s[0] and (len(s) < 6 or s[5] != 'delete') + result = [ + section[0] + for section in self.get_sections() + if undeleted(section) + ] + return result + + +class SVNEntriesFileXML(SVNEntriesFile): + def is_valid(self): + return True + + def get_url(self): + "Get repository URL" + urlre = re.compile('url="([^"]+)"') + return urlre.search(self.data).group(1) + + def parse_revision_numbers(self): + revre = re.compile(r'committed-rev="(\d+)"') + return [ + int(m.group(1)) + for m in revre.finditer(self.data) + ] + + def get_undeleted_records(self): + entries_pattern = \ + re.compile(r'name="([^"]+)"(?![^>]+deleted="true")', re.I) + results = [ + unescape(match.group(1)) + for match in entries_pattern.finditer(self.data) + ] + return results + + if __name__ == '__main__': for name in svn_finder(sys.argv[1]): - print(name) \ No newline at end of file + print(name) diff --git a/setuptools/tests/test_egg_info.py b/setuptools/tests/test_egg_info.py index 95667b69..7abafd71 100644 --- a/setuptools/tests/test_egg_info.py +++ b/setuptools/tests/test_egg_info.py @@ -48,6 +48,27 @@ class TestEggInfo(unittest.TestCase): rev = egg_info.egg_info.get_svn_revision() self.assertEqual(rev, '89000') + def test_version_10_format_legacy_parser(self): + """ + """ + path_variable = None + for env in os.environ: + if env.lower() == 'path': + path_variable = env + + if path_variable is None: + self.skipTest('Cannot figure out how to modify path') + + old_path = os.environ[path_variable] + os.environ[path_variable] = '' + try: + self._write_entries(ENTRIES_V10) + rev = egg_info.egg_info.get_svn_revision() + finally: + os.environ[path_variable] = old_path + + self.assertEqual(rev, '89000') + def test_suite(): return unittest.defaultTestLoader.loadTestsFromName(__name__) -- cgit v1.2.3