summary refs log tree commit diff stats
diff options
context:
space:
mode:
authornfnty <git@nfnty.se>2016-02-08 04:39:34 +0100
committernfnty <git@nfnty.se>2016-02-08 04:43:05 +0100
commit61a6dcf23a9bb8e78b7807a5099b7e046f9fa0c7 (patch)
treea719bc501ceacb4fc465d08100993c34390b451b
parentf68763ad11774290279763b30fed37bcf5e0da1a (diff)
downloadranger-61a6dcf23a9bb8e78b7807a5099b7e046f9fa0c7.tar.gz
VCS: Implement Subversion
-rw-r--r--ranger/ext/vcs/hg.py2
-rw-r--r--ranger/ext/vcs/svn.py212
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))