diff options
author | nfnty <git@nfnty.se> | 2016-02-08 04:39:34 +0100 |
---|---|---|
committer | nfnty <git@nfnty.se> | 2016-02-08 04:43:05 +0100 |
commit | 61a6dcf23a9bb8e78b7807a5099b7e046f9fa0c7 (patch) | |
tree | a719bc501ceacb4fc465d08100993c34390b451b | |
parent | f68763ad11774290279763b30fed37bcf5e0da1a (diff) | |
download | ranger-61a6dcf23a9bb8e78b7807a5099b7e046f9fa0c7.tar.gz |
VCS: Implement Subversion
-rw-r--r-- | ranger/ext/vcs/hg.py | 2 | ||||
-rw-r--r-- | ranger/ext/vcs/svn.py | 212 |
2 files changed, 109 insertions, 105 deletions
diff --git a/ranger/ext/vcs/hg.py b/ranger/ext/vcs/hg.py index 2d047de3..c4b04282 100644 --- a/ranger/ext/vcs/hg.py +++ b/ranger/ext/vcs/hg.py @@ -144,7 +144,7 @@ class Hg(Vcs): return 'behind' if behind else 'sync' def data_branch(self): - return self._run(['branch'], catchout=True).rstrip('\n') or None + return self._run(['branch']).rstrip('\n') or None def data_info(self, rev=None): if rev is None: diff --git a/ranger/ext/vcs/svn.py b/ranger/ext/vcs/svn.py index 14e82db0..dd4270d8 100644 --- a/ranger/ext/vcs/svn.py +++ b/ranger/ext/vcs/svn.py @@ -3,142 +3,146 @@ """Subversion module""" -from __future__ import with_statement - from datetime import datetime -import logging import os -import re +from xml.etree import ElementTree as etree from .vcs import Vcs, VcsError + class SVN(Vcs): """VCS implementation for Subversion""" - HEAD = 'HEAD' - # Generic - #--------------------------- + _status_translations = ( + ('ADR', 'staged'), + ('C', 'conflict'), + ('I', 'ignored'), + ('M~', 'changed'), + ('X', 'none'), + ('?', 'untracked'), + ('!', 'deleted'), + ) - def _svn(self, path, args, silent=True, catchout=False, retbytes=False): - return self._vcs(path, 'svn', args, silent=silent, catchout=catchout, retbytes=retbytes) + def _log(self, refspec=None, maxres=None, filelist=None): + """ Retrieves log message and parses it""" + args = ['log', '--xml'] - def _has_head(self): - """Checks whether repo has head""" - return True + if refspec: + args += ['--limit', '1', '--revision', refspec] + elif maxres: + args += ['--limit', str(maxres)] - def _sanitize_rev(self, rev): - if rev == None: return None - rev = rev.strip() - if len(rev) == 0: return None - if rev[-1] == '+': rev = rev[:-1] + if filelist: + args += ['--'] + filelist try: - if int(rev) == 0: return None - except: - pass - - return rev - - def _log(self, refspec=None, maxres=None, filelist=None): - """ Retrieves log message and parses it. - """ - args = ['log'] - - if refspec: args = args + ['--limit', '1', '-r', refspec] - elif maxres: args = args + ['--limit', str(maxres)] - - if filelist: args = args + filelist - logging.debug('Running svn log') - logging.debug(args) - - raw = self._svn(self.path, args, catchout=True) - logging.debug(raw) - L = re.findall(r"""^[-]*\s* # Dash line - r([0-9]*)\s\|\s # Revision [0] - (.*)\s\|\s # Author [1] - (.*?)\s # Date [2] - (.*?)\s # Time [3] - .*?\|\s*? # Dump rest of date string - ([0-9])+\sline(?:s)?\s* # Number of line(s) [4] - (.*)\s # Log Message [5] - [-]+\s* # Dash line - $""", raw, re.MULTILINE | re.VERBOSE) + output = self._run(args).rstrip('\n') + except VcsError: + return None + if not output: + return None log = [] - for t in L: - logging.debug(t) - dt = {} - dt['short'] = t[0].strip() - dt['revid'] = t[0].strip() - dt['author'] = t[1].strip() - dt['date'] = datetime.strptime(t[2]+'T'+t[3], "%Y-%m-%dT%H:%M:%S") - dt['summary'] = t[5].strip() - log.append(dt) - logging.debug(log) + for entry in etree.fromstring(output).findall('./logentry'): + new = {} + new['short'] = entry.get('revision') + new['revid'] = entry.get('revision') + new['author'] = entry.find('./author').text + new['date'] = datetime.strptime( + entry.find('./date').text, + '%Y-%m-%dT%H:%M:%S.%fZ', + ) + new['summary'] = entry.find('./msg').text.split('\n')[0] + log.append(new) return log - def _svn_file_status(self, st): - if len(st) != 1: raise VcsError("Wrong hg file status string: %s" % st) - if st in "A": return 'staged' - elif st in "D": return 'deleted' - elif st in "I": return 'ignored' - elif st in "?": return 'untracked' - elif st in "C": return 'conflict' - else: return 'unknown' + def _status_translate(self, code): + """Translate status code""" + for code_x, status in self._status_translations: + if code in code_x: + return status + return 'unknown' + + def _remote_url(self): + """Remote url""" + try: + output = self._run(['info', '--xml']).rstrip('\n') + except VcsError: + return None + if not output: + return None + return etree.fromstring(output).find('./entry/url').text or None # Action Interface - #--------------------------- def action_add(self, filelist=None): - """Adds files to the index, preparing for commit""" - if filelist != None: self._svn(self.path, ['add'] + filelist) - else: self._svn(self.path, ['add']) + args = ['add'] + if filelist: + args += ['--'] + filelist + self._run(args, catchout=False) def action_reset(self, filelist=None): - """Equivalent to svn revert""" - if filelist == None: filelist = self.data_status_subpaths().keys() - self._svn(self.path, ['revert'] + filelist) + args = ['revert', '--'] + if filelist: + args += filelist + else: + args += self.rootvcs.status_subpaths.keys() + self._run(args, catchout=False) # Data Interface - #--------------------------- + + def data_status_root(self): + statuses = set() + + # Paths with status + output = self._run(['status']).rstrip('\n') + if not output: + return 'sync' + for line in output.split('\n'): + code = line[0] + if code == ' ': + continue + statuses.add(self._status_translate(code)) + + for status in self.DIRSTATUSES: + if status in statuses: + return status + return 'sync' def data_status_subpaths(self): - """Returns a dict indexed by files not in sync their status as values. - Paths are given relative to the root. Strips trailing '/' from dirs.""" - raw = self._svn(self.path, ['status'], catchout=True, retbytes=True) -# logging.debug(raw) - L = re.findall(r'^(.)\s*(.*?)\s*$', raw.decode('utf-8'), re.MULTILINE) - ret = {} - for st, p in L: - # Detect conflict by the existence of .orig files - if st == '?' and re.match(r'^.*\.orig\s*$', p): st = 'X' - sta = self._svn_file_status(st) - ret[os.path.normpath(p.strip())] = sta - return ret + statuses = {} - def data_status_remote(self): - """Checks the status of the repo regarding sync state with remote branch. + # Paths with status + output = self._run(['status']).rstrip('\n') + if output: + for line in output.split('\n'): + code, path = line[0], line[8:] + if code == ' ': + continue + statuses[os.path.normpath(path)] = self._status_translate(code) - I'm not sure this make sense for SVN so we're just going to return 'sync'""" - return 'sync' + return statuses + + def data_status_remote(self): + remote = self._remote_url() + if remote is None or remote.startswith('file://'): + return 'none' + return 'unknown' def data_branch(self): - """Returns the current named branch, if this makes sense for the backend. None otherwise""" return None - branch = self._svn(self.path, ['branch'], catchout=True) - return branch or None def data_info(self, rev=None): - """Gets info about the given revision rev""" - if rev == None: rev = self.HEAD - rev = self._sanitize_rev(rev) - if rev == self.HEAD and not self._has_head(): return None - logging.debug('refspec is ' + str(rev)) - L = self._log(refspec=rev) - logging.debug(len(L)) - if len(L) == 0: - raise VcsError("Revision %s does not exist" % rev) - elif len(L) > 1: - raise VcsError("More than one instance of revision %s ?!?" % rev) + if rev is None: + rev = self.HEAD + + log = self._log(refspec=rev) + if not log: + if rev == self.HEAD: + return None + else: + raise VcsError('Revision {0:s} does not exist'.format(rev)) + elif len(log) == 1: + return log[0] else: - return L[0] + raise VcsError('More than one instance of revision {0:s}'.format(rev)) |