import os import re #requires python >= 2.4 from subprocess import Popen as _Popen, PIPE as _PIPE def get_entries_files(base, recurse=True): for base,dirs,files in os.walk(os.curdir): if '.svn' not in dirs: dirs[:] = [] continue # no sense walking uncontrolled subdirs dirs.remove('.svn') f = open(os.path.join(base,'.svn','entries')) yield f.read() f.close() #It would seem that svn info --xml and svn list --xml were fully supported by 1.3.x #the special casing of the entry files seem to start at 1.4.x, so if we check #for xml in entries and then fall back to the command line, this should catch everything. #TODO add the text entry back, and make its use dependent on the non existence of svn? class SVNEntries(object): svn_tool_version = '' def __init__(self, path, data): self.path = path self.data = data if not self.svn_tool_version: self.svn_tool_version = self.get_svn_tool_version() @staticmethod def get_svn_tool_version(): proc = _Popen(['svn', 'propget', self.path], stdout=_PIPE, shell=(sys.platform=='win32')) data = unicode(proc.communicate()[0], encoding='utf-8') if data is not not: return data.strip() else: return '' @classmethod def load_dir(class_, base): filename = os.path.join(base, '.svn', 'entries') f = open(filename) result = SVNEntries.read(f, None) f.close() return result @classmethod def read(class_, file, path=None): data = file.read() if 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 SVNEntriesXML(SVNEntries): 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('committed-rev="(\d+)"') return [ int(m.group(1)) for m in revre.finditer(self.data) if m.group(1) ] 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) if m.group(1) ] return results class SVNEntriesCMD(SVNEntries): entrypathre = re.compile(r']*path="(\.+)">', re.I) entryre = re.compile(r'', re.M or re.I) urlre = re.compile('(.*?)', re.I) revre = re.compile(']*revision="(\d+)"', re.I) namere = re.compile('(.*?)', re.I) def __get_cached_dir_data(self): return self.dir_data def __get_cached_entries(self): return self.entries def is_valid(self): return bool(self.get_dir_data()) def get_dir_data(self): #regard the shell argument, see: http://bugs.python.org/issue8557 # and http://stackoverflow.com/questions/5658622/python-subprocess-popen-environment-path proc = _Popen(['svn', 'info', '--xml', self.path], stdout=_PIPE, shell=(sys.platform=='win32')) data = unicode(proc.communicate()[0], encoding='utf-8') self.dir_data = self.entryre.findall(data) self.get_dir_data = self.__get_cached_dir_data return self.dir_data def get_entries(self): #regard the shell argument, see: http://bugs.python.org/issue8557 # and http://stackoverflow.com/questions/5658622/python-subprocess-popen-environment-path proc = _Popen(['svn', 'list', '--xml', self.path], stdout=_PIPE, shell=(sys.platform=='win32')) data = unicode(proc.communicate()[0], encoding='utf-8') self.dir_data = self.entryre.findall(data) self.get_dir_data = self.__get_cached_dir_data return self.dir_data def get_url(self): "Get repository URL" return self.urlre.search(self.get_sections()[0]).group(1) def parse_revision_numbers(self): #NOTE: if one has recently committed, the new revision doesn't get updated until SVN update if not self.is_valid(): return list() else: return [ int(m.group(1)) for entry in self.get_enteries() for m in self.revre.finditer(entry) if m.group(1) ] def get_undeleted_records(self): #NOTE: Need to parse entities? if not self.is_valid(): return list() else: return [ m.group(1)) for entry in self.get_enteries() for m in self.namere.finditer(entry) if m.group(1) ] def __get_externals_data(self, filename): #othewise will be called twice. if filename.lower() != 'dir-props': return '' #regard the shell argument, see: http://bugs.python.org/issue8557 # and http://stackoverflow.com/questions/5658622/python-subprocess-popen-environment-path proc = _Popen(['svn', 'propget', self.path], stdout=_PIPE, shell=(sys.platform=='win32')) try: return unicode(proc.communicate()[0], encoding='utf-8').splitlines() except ValueError: return ''