about summary refs log tree commit diff stats
path: root/ranger
diff options
context:
space:
mode:
authornfnty <git@nfnty.se>2015-12-18 01:47:21 +0100
committernfnty <git@nfnty.se>2016-02-08 04:43:04 +0100
commit34fec205ee153928b7b99d888f04be758041beed (patch)
treeef4478ccb6946618004c126085c3d85f560c6f69 /ranger
parentf24d97cc4dad56dd409bd1798b1c26d9a986ee63 (diff)
downloadranger-34fec205ee153928b7b99d888f04be758041beed.tar.gz
VCS: Major cleanup
Diffstat (limited to 'ranger')
-rwxr-xr-xranger/config/commands.py106
-rw-r--r--ranger/ext/vcs/__init__.py2
-rw-r--r--ranger/ext/vcs/bzr.py154
-rw-r--r--ranger/ext/vcs/git.py157
-rw-r--r--ranger/ext/vcs/hg.py128
-rw-r--r--ranger/ext/vcs/svn.py128
-rw-r--r--ranger/ext/vcs/vcs.py137
7 files changed, 100 insertions, 712 deletions
diff --git a/ranger/config/commands.py b/ranger/config/commands.py
index b2c704c7..6012ed4e 100755
--- a/ranger/config/commands.py
+++ b/ranger/config/commands.py
@@ -1324,9 +1324,31 @@ class grep(Command):
             action.extend(f.path for f in self.fm.thistab.get_selection())
             self.fm.execute_command(action, flags='p')
 
+class flat(Command):
+    """
+    :flat <level>
+
+    Flattens the directory view up to the specified level.
+
+        -1 fully flattened
+         0 remove flattened view
+    """
+
+    def execute(self):
+        try:
+            level = self.rest(1)
+            level = int(level)
+        except ValueError:
+            level = self.quantifier
+        if level < -1:
+            self.fm.notify("Need an integer number (-1, 0, 1, ...)", bad=True)
+        self.fm.thisdir.unload()
+        self.fm.thisdir.flat = level
+        self.fm.thisdir.load_content()
 
 # Version control commands
 # --------------------------------
+
 class stage(Command):
     """
     :stage
@@ -1336,14 +1358,13 @@ class stage(Command):
     def execute(self):
         from ranger.ext.vcs import VcsError
 
-
         if self.fm.thisdir.vcs and self.fm.thisdir.vcs.track:
             filelist = [f.path for f in self.fm.thistab.get_selection()]
             try:
                 self.fm.thisdir.vcs.add(filelist)
             except VcsError as error:
-                self.fm.notify('Unable to unstage files: {0:s}'.format(str(error)))
-            self.fm.reload_cwd()
+                self.fm.notify('Unable to stage files: {0:s}'.format(str(error)))
+            self.fm.ui.vcsthread.wakeup()
         else:
             self.fm.notify('Unable to stage files: Not in repository')
 
@@ -1356,93 +1377,16 @@ class unstage(Command):
     def execute(self):
         from ranger.ext.vcs import VcsError
 
-
         if self.fm.thisdir.vcs and self.fm.thisdir.vcs.track:
             filelist = [f.path for f in self.fm.thistab.get_selection()]
             try:
                 self.fm.thisdir.vcs.reset(filelist)
             except VcsError as error:
                 self.fm.notify('Unable to unstage files: {0:s}'.format(str(error)))
-            self.fm.reload_cwd()
+            self.fm.ui.vcsthread.wakeup()
         else:
             self.fm.notify('Unable to unstage files: Not in repository')
 
-
-class diff(Command):
-    """
-    :diff
-
-    Displays a diff of selected files against the last committed version
-    """
-    def execute(self):
-        from ranger.ext.vcs import VcsError
-        import tempfile
-
-        L = self.fm.thistab.get_selection()
-        if len(L) == 0: return
-
-        filelist = [f.path for f in L]
-        vcs = L[0].vcs
-
-        diff = vcs.get_raw_diff(filelist=filelist)
-        if len(diff.strip()) > 0:
-            tmp = tempfile.NamedTemporaryFile()
-            tmp.write(diff.encode('utf-8'))
-            tmp.flush()
-
-            pager = os.environ.get('PAGER', ranger.DEFAULT_PAGER)
-            self.fm.run([pager, tmp.name])
-        else:
-            raise Exception("diff is empty")
-
-
-class log(Command):
-    """
-    :log
-
-    Displays the log of the current repo or files
-    """
-    def execute(self):
-        from ranger.ext.vcs import VcsError
-        import tempfile
-
-        L = self.fm.thistab.get_selection()
-        if len(L) == 0: return
-
-        filelist = [f.path for f in L]
-        vcs = L[0].vcs
-
-        log = vcs.get_raw_log(filelist=filelist)
-        tmp = tempfile.NamedTemporaryFile()
-        tmp.write(log.encode('utf-8'))
-        tmp.flush()
-
-        pager = os.environ.get('PAGER', ranger.DEFAULT_PAGER)
-        self.fm.run([pager, tmp.name])
-
-class flat(Command):
-    """
-    :flat <level>
-
-    Flattens the directory view up to the specified level.
-
-        -1 fully flattened
-         0 remove flattened view
-    """
-
-    def execute(self):
-        try:
-            level = self.rest(1)
-            level = int(level)
-        except ValueError:
-            level = self.quantifier
-        if level < -1:
-            self.fm.notify("Need an integer number (-1, 0, 1, ...)", bad=True)
-        self.fm.thisdir.unload()
-        self.fm.thisdir.flat = level
-        self.fm.thisdir.load_content()
-
-
 # Metadata commands
 # --------------------------------
 
diff --git a/ranger/ext/vcs/__init__.py b/ranger/ext/vcs/__init__.py
index c6c691b8..3e62310c 100644
--- a/ranger/ext/vcs/__init__.py
+++ b/ranger/ext/vcs/__init__.py
@@ -1,3 +1,3 @@
-"""VCS Extension Package"""
+"""VCS Extension"""
 
 from .vcs import Vcs, VcsError, VcsThread
diff --git a/ranger/ext/vcs/bzr.py b/ranger/ext/vcs/bzr.py
index 7b870d7d..358db8ad 100644
--- a/ranger/ext/vcs/bzr.py
+++ b/ranger/ext/vcs/bzr.py
@@ -2,28 +2,25 @@
 
 import os
 import re
-import shutil
 from datetime import datetime
 
 from .vcs import Vcs, VcsError
 
-
 class Bzr(Vcs):
+    """VCS implementiation for Bzr"""
     HEAD="last:1"
 
-    # Auxiliar stuff
+    # Generic
     #---------------------------
 
     def _bzr(self, path, args, silent=True, catchout=False, bytes=False):
         return self._vcs(path, 'bzr', args, silent=silent, catchout=catchout, bytes=bytes)
 
-
     def _has_head(self):
         """Checks whether repo has head"""
         rnum = self._bzr(self.path, ['revno'], catchout=True)
         return rnum != '0'
 
-
     def _sanitize_rev(self, rev):
         if rev == None: return None
         rev = rev.strip()
@@ -31,7 +28,6 @@ class Bzr(Vcs):
 
         return rev
 
-
     def _log(self, refspec=None, filelist=None):
         """Gets a list of dicts containing revision info, for the revisions matching refspec"""
         args = ['log', '-n0', '--show-ids']
@@ -61,7 +57,6 @@ class Bzr(Vcs):
             log.append(dt)
         return log
 
-
     def _bzr_file_status(self, st):
         st = st.strip()
         if   st in "AM":     return 'staged'
@@ -69,76 +64,19 @@ class Bzr(Vcs):
         elif st in "?":      return 'untracked'
         else:                return 'unknown'
 
-
-
-    # Repo creation
-    #---------------------------
-
-    def init(self):
-        """Initializes a repo in current path"""
-        self._bzr(self.path, ['init'])
-        self.update()
-
-
-    def clone(self, src):
-        """Clones a repo from src"""
-        path = os.path.dirname(self.path)
-        name = os.path.basename(self.path)
-        try:
-            os.rmdir(self.path)
-        except OSError:
-            raise VcsError("Can't clone to %s. It is not an empty directory" % self.path)
-
-        self._bzr(path, ['branch', src, name])
-        self.update()
-
-
-
     # Action Interface
     #---------------------------
 
-    def commit(self, message):
-        """Commits with a given message"""
-        self._bzr(self.path, ['commit', '-m', message])
-
-
     def add(self, filelist=None):
         """Adds files to the index, preparing for commit"""
         if filelist != None: self._bzr(self.path, ['add'] + filelist)
         else:                self._bzr(self.path, ['add'])
 
-
     def reset(self, filelist=None):
         """Removes files from the index"""
         if filelist != None: self._bzr(self.path, ['remove', '--keep', '--new'] + filelist)
         else:                self._bzr(self.path, ['remove', '--keep', '--new'])
 
-
-    def pull(self):
-        """Pulls a git repo"""
-        self._bzr(self.path, ['pull'])
-
-
-    def push(self):
-        """Pushes a git repo"""
-        self._bzr(self.path, ['push'])
-
-
-    def checkout(self, rev):
-        """Checks out a branch or revision"""
-        self._bzr(self.path, ['update', '-r', rev])
-
-
-    def extract_file(self, rev, name, dest):
-        """Extracts a file from a given revision and stores it in dest dir"""
-        if rev == self.INDEX:
-            shutil.copyfile(os.path.join(self.path, name), dest)
-        else:
-            out = self._bzr(self.path, ['cat', '--r', rev, name], catchout=True, bytes=True)
-            with open(dest, 'wb') as fd: fd.write(out)
-
-
-
     # Data Interface
     #---------------------------
 
@@ -153,65 +91,7 @@ class Bzr(Vcs):
             ret[os.path.normpath(p.strip())] = sta
         return ret
 
-
-    def get_ignore_allfiles(self):
-        """Returns a set of all the ignored files in the repo. Strips trailing '/' from dirs."""
-        raw = self._bzr(self.path, ['ls', '--ignored'], catchout=True)
-        return set(os.path.normpath(p) for p in raw.split('\n'))
-
-
-    # TODO: slow due to net access
     def get_status_remote(self):
-        """Checks the status of the repo regarding sync state with remote branch"""
-        if self.get_remote() == None:
-            return "none"
-
-        ahead = behind = True
-        try:
-            self._bzr(self.path, ['missing', '--mine-only'], silent=True)
-        except:
-            ahead = False
-
-        try:
-            self._bzr(self.path, ['missing', '--theirs-only'], silent=True)
-        except:
-            behind = False
-
-        if       ahead and     behind: return "diverged"
-        elif     ahead and not behind: return "ahead"
-        elif not ahead and     behind: return "behind"
-        elif not ahead and not behind: return "sync"
-
-
-    def get_branch(self):
-        """Returns the current named branch, if this makes sense for the backend. None otherwise"""
-        branch = self._bzr(self.path, ['nick'], catchout=True)
-        return branch or None
-
-
-    def get_log(self, filelist=None, maxres=None):
-        """Get the entire log for the current HEAD"""
-        if not self._has_head(): return []
-        return self._log(refspec=None, filelist=filelist)
-
-
-    def get_raw_log(self, filelist=None):
-        """Gets the raw log as a string"""
-        if not self._has_head(): return []
-        args = ['log']
-        if filelist: args = args + filelist
-        return self._bzr(self.path, args, catchout=True)
-
-
-    def get_raw_diff(self, refspec=None, filelist=None):
-        """Gets the raw diff as a string"""
-        args = ['diff', '--git']
-        if refspec:  args = args + [refspec]
-        if filelist: args = args + filelist
-        return self._bzr(self.path, args, catchout=True)
-
-
-    def get_remote(self):
         """Returns the url for the remote repo attached to head"""
         try:
             remote = self._bzr(self.path, ['config', 'parent_location'], catchout=True)
@@ -220,19 +100,10 @@ class Bzr(Vcs):
 
         return remote.strip() or None
 
-
-    def get_revision_id(self, rev=None):
-        """Get a canonical key for the revision rev"""
-        if rev == None: rev = self.HEAD
-        elif rev == self.INDEX: return None
-        rev = self._sanitize_rev(rev)
-        try:
-            L = self._log(refspec=rev)
-        except VcsError:
-            L = []
-        if len(L) == 0: return None
-        else:           return L[0]['revid']
-
+    def get_branch(self):
+        """Returns the current named branch, if this makes sense for the backend. None otherwise"""
+        branch = self._bzr(self.path, ['nick'], catchout=True)
+        return branch or None
 
     def get_info(self, rev=None):
         """Gets info about the given revision rev"""
@@ -247,16 +118,3 @@ class Bzr(Vcs):
             raise VcsError("More than one instance of revision %s ?!?" % rev)
         else:
             return L[0]
-
-
-    def get_files(self, rev=None):
-        """Gets a list of files in revision rev"""
-        if rev == None: rev = self.HEAD
-        rev = self._sanitize_rev(rev)
-
-        if rev:
-            if rev == self.INDEX:  raw = self._bzr(self.path, ["ls"], catchout=True)
-            else:                  raw = self._bzr(self.path, ['ls', '--R', '-V', '-r', rev], catchout=True)
-            return raw.split('\n')
-        else:
-            return []
diff --git a/ranger/ext/vcs/git.py b/ranger/ext/vcs/git.py
index ab971423..ab7f516f 100644
--- a/ranger/ext/vcs/git.py
+++ b/ranger/ext/vcs/git.py
@@ -2,7 +2,6 @@
 
 import os
 import re
-import shutil
 from datetime import datetime
 import json
 
@@ -23,22 +22,14 @@ class Git(Vcs):
         ('!', '!', 'ignored'),
     )
 
-    # Auxiliar stuff
+    # Generic
     #---------------------------
 
     def _git(self, args, path=None, silent=True, catchout=False, bytes=False):
         """Call git"""
-        return self._vcs(path if path else self.path, 'git', args, silent=silent,
+        return self._vcs(path or self.path, 'git', args, silent=silent,
                          catchout=catchout, bytes=bytes)
 
-    def _has_head(self):
-        """Checks whether repo has head"""
-        try:
-            self._git(['rev-parse', 'HEAD'], silent=True)
-        except VcsError:
-            return False
-        return True
-
     def _head_ref(self):
         """Gets HEAD's ref"""
         return self._git(['symbolic-ref', self.HEAD], catchout=True, silent=True) or None
@@ -51,17 +42,15 @@ class Git(Vcs):
                          catchout=True, silent=True) \
             or None
 
-    def _sanitize_rev(self, rev):
-        """Sanitize revision string"""
-        if rev is None:
-            return None
-        return rev.strip()
-
     def _log(self, refspec=None, maxres=None, filelist=None):
         """Gets a list of dicts containing revision info, for the revisions matching refspec"""
         args = [
             '--no-pager', 'log',
-            '--pretty={%x00short%x00: %x00%h%x00, %x00revid%x00: %x00%H%x00, %x00author%x00: %x00%an <%ae>%x00, %x00date%x00: %ct, %x00summary%x00: %x00%s%x00}'
+            '--pretty={'
+            '%x00short%x00:%x00%h%x00,'
+            '%x00revid%x00: %x00%H%x00,'
+            '%x00author%x00: %x00%an <%ae>%x00, %x00date%x00: %ct, %x00summary%x00: %x00%s%x00'
+            '}'
         ]
         if refspec:
             args += ['-1', refspec]
@@ -70,9 +59,14 @@ class Git(Vcs):
         if filelist:
             args += ['--'] + filelist
 
+        try:
+            log_raw = self._git(args, catchout=True)\
+                .replace('\\', '\\\\').replace('"', '\\"').replace('\x00', '"').splitlines()
+        except VcsError:
+            return []
+
         log = []
-        for line in self._git(args, catchout=True)\
-                .replace('\\', '\\\\').replace('"', '\\"').replace('\x00', '"').splitlines():
+        for line in log_raw:
             line = json.loads(line)
             line['date'] = datetime.fromtimestamp(line['date'])
             log.append(line)
@@ -85,31 +79,9 @@ class Git(Vcs):
                 return status
         return 'unknown'
 
-    # Repo creation
-    #---------------------------
-
-    def init(self):
-        """Initializes a repo in current path"""
-        self._git(['init'])
-        self.update()
-
-    def clone(self, src):
-        """Clones a repo from src"""
-        try:
-            os.rmdir(self.path)
-        except OSError:
-            raise VcsError("Can't clone to {0:s}: Not an empty directory".format(self.path))
-
-        self._git(['clone', src, os.path.basename(self.path)], path=os.path.dirname(self.path))
-        self.update()
-
     # Action interface
     #---------------------------
 
-    def commit(self, message):
-        """Commits with a given message"""
-        self._git(['commit', '--message', message])
-
     def add(self, filelist=None):
         """Adds files to the index, preparing for commit"""
         if filelist:
@@ -124,30 +96,6 @@ class Git(Vcs):
         else:
             self._git(['reset'])
 
-    def pull(self, *args):
-        """Pulls from remote"""
-        self._git(['pull'] + list(args))
-
-    def push(self, *args):
-        """Pushes to remote"""
-        self._git(['push'] + list(args))
-
-    def checkout(self, rev):
-        """Checks out a branch or revision"""
-        self._git(['checkout', self._sanitize_rev(rev)])
-
-    def extract_file(self, rev, name, dest):
-        """Extracts a file from a given revision and stores it in dest dir"""
-        if rev == self.INDEX:
-            shutil.copyfile(os.path.join(self.path, name), dest)
-        else:
-            with open(dest, 'wb') as fd:
-                fd.write(
-                    self._git([
-                        '--no-pager', 'show', '{0:s}:{1:s}'.format(self._sanitize_rev(rev), name)
-                    ], catchout=True, bytes=True)
-                )
-
     # Data Interface
     #---------------------------
 
@@ -237,85 +185,18 @@ class Git(Vcs):
         else:
             return None
 
-    def get_log(self, filelist=None, maxres=None):
-        """Get the entire log for the current HEAD"""
-        if not self._has_head():
-            return []
-        return self._log(refspec=None, maxres=maxres, filelist=filelist)
-
-    def get_raw_log(self, filelist=None):
-        """Gets the raw log as a string"""
-        if not self._has_head():
-            return []
-        args = ['log']
-        if filelist:
-            args += ['--'] + filelist
-        return self._git(args, catchout=True)
-
-    def get_raw_diff(self, refspec=None, filelist=None):
-        """Gets the raw diff as a string"""
-        args = ['diff']
-        if refspec:
-            args += [refspec]
-        if filelist:
-            args += ['--'] + filelist
-        return self._git(args, catchout=True)
-
-    def get_remote(self):
-        """Returns the url for the remote repo attached to head"""
-        try:
-            ref = self._head_ref()
-            remote = self._remote_ref(ref)
-        except VcsError:
-            ref = remote = None
-        if not remote:
-            return None
-
-        match = re.match('refs/remotes/([^/]+)/', remote)
-        if match:
-            return self._git(['config', '--get', 'remote.{0:s}.url'.format(match.group(1))],
-                             catchout=True).strip() \
-                or None
-        return None
-
-
-    def get_revision_id(self, rev=None):
-        """Get a canonical key for the revision rev"""
-        if rev is None:
-            rev = self.HEAD
-        elif rev == self.INDEX:
-            return None
-        rev = self._sanitize_rev(rev)
-
-        return self._sanitize_rev(self._git(['rev-parse', rev], catchout=True))
-
     def get_info(self, rev=None):
         """Gets info about the given revision rev"""
         if rev is None:
             rev = self.HEAD
-        rev = self._sanitize_rev(rev)
-        if rev == self.HEAD and not self._has_head():
-            return None
 
         log = self._log(refspec=rev)
         if len(log) == 0:
-            raise VcsError("Revision {0:s} does not exist".format(rev))
+            if rev == self.HEAD:
+                return None
+            else:
+                raise VcsError('Revision {0:s} does not exist'.format(rev))
         elif len(log) > 1:
-            raise VcsError("More than one instance of revision {0:s} ?!?".format(rev))
+            raise VcsError('More than one instance of revision {0:s} ?!?'.format(rev))
         else:
             return log[0]
-
-    def get_files(self, rev=None):
-        """Gets a list of files in revision rev"""
-        if rev is None:
-            rev = self.HEAD
-        rev = self._sanitize_rev(rev)
-        if rev is None:
-            return []
-
-        if rev == self.INDEX:
-            return self._git(['ls-files', '-z'],
-                             catchout=True, bytes=True).decode('utf-8').split('\x00')
-        else:
-            return self._git(['ls-tree', '--name-only', '-r', '-z', rev],
-                             catchout=True, bytes=True).decode('utf-8').split('\x00')
diff --git a/ranger/ext/vcs/hg.py b/ranger/ext/vcs/hg.py
index 48e991bf..58651795 100644
--- a/ranger/ext/vcs/hg.py
+++ b/ranger/ext/vcs/hg.py
@@ -2,31 +2,24 @@
 
 import os
 import re
-import shutil
 from datetime import datetime
-try:
-    from configparser import RawConfigParser
-except ImportError:
-    from ConfigParser import RawConfigParser
 
 from .vcs import Vcs, VcsError
 
-
 class Hg(Vcs):
     HEAD = 'tip'
-    # Auxiliar stuff
+
+    # Generic
     #---------------------------
 
     def _hg(self, path, args, silent=True, catchout=False, bytes=False):
         return self._vcs(path, 'hg', args, silent=silent, catchout=catchout, bytes=bytes)
 
-
     def _has_head(self):
         """Checks whether repo has head"""
         rnum = self._hg(self.path, ['-q', 'identify', '--num', '-r', self.HEAD], catchout=True)
         return rnum != '-1'
 
-
     def _sanitize_rev(self, rev):
         if rev == None: return None
         rev = rev.strip()
@@ -40,7 +33,6 @@ class Hg(Vcs):
 
         return rev
 
-
     def _log(self, refspec=None, maxres=None, filelist=None):
 
         fmt = "changeset: {rev}:{node}\ntag: {tags}\nuser: {author}\ndate: {date}\nsummary: {desc}\n"
@@ -66,7 +58,6 @@ class Hg(Vcs):
             log.append(dt)
         return log
 
-
     def _hg_file_status(self, st):
         if len(st) != 1: raise VcsError("Wrong hg file status string: %s" % st)
         if   st in "ARM":    return 'staged'
@@ -77,74 +68,19 @@ class Hg(Vcs):
         elif st in "C":      return 'sync'
         else:                return 'unknown'
 
-
-
-    # Repo creation
-    #---------------------------
-
-    def init(self):
-        """Initializes a repo in current path"""
-        self._hg(self.path, ['init'])
-        self.update()
-
-
-    def clone(self, src):
-        """Clones a repo from src"""
-        name = os.path.basename(self.path)
-        path = os.path.dirname(self.path)
-        try:
-            os.rmdir(self.path)
-        except OSError:
-            raise VcsError("Can't clone to %s. It is not an empty directory" % self.path)
-
-        self._hg(path, ['clone', src, name])
-        self.update()
-
-
-
     # Action Interface
     #---------------------------
 
-    def commit(self, message):
-        """Commits with a given message"""
-        self._hg(self.path, ['commit', '-m', message])
-
-
     def add(self, filelist=None):
         """Adds files to the index, preparing for commit"""
         if filelist != None: self._hg(self.path, ['addremove'] + filelist)
         else:                self._hg(self.path, ['addremove'])
 
-
     def reset(self, filelist=None):
         """Removes files from the index"""
         if filelist == None: filelist = self.get_status_subpaths().keys()
         self._hg(self.path, ['forget'] + filelist)
 
-
-    def pull(self):
-        """Pulls a hg repo"""
-        self._hg(self.path, ['pull', '-u'])
-
-
-    def push(self):
-        """Pushes a hg repo"""
-        self._hg(self.path, ['push'])
-
-
-    def checkout(self, rev):
-        """Checks out a branch or revision"""
-        self._hg(self.path, ['update', rev])
-
-
-    def extract_file(self, rev, name, dest):
-        """Extracts a file from a given revision and stores it in dest dir"""
-        if rev == self.INDEX:
-            shutil.copyfile(os.path.join(self.path, name), dest)
-        else:
-            self._hg(self.path, ['cat', '--rev', rev, '--output', dest, name])
-
-
     # Data Interface
     #---------------------------
 
@@ -161,14 +97,6 @@ class Hg(Vcs):
             ret[os.path.normpath(p.strip())] = sta
         return ret
 
-
-    def get_ignore_allfiles(self):
-        """Returns a set of all the ignored files in the repo"""
-        raw = self._hg(self.path, ['status', '-i'], catchout=True, bytes=True)
-        L = re.findall('^I\s*(.*?)\s*$', raw.decode('utf-8'), re.MULTILINE)
-        return set(L)
-
-
     def get_status_remote(self):
         """Checks the status of the repo regarding sync state with remote branch"""
         if self.get_remote() == None:
@@ -190,50 +118,11 @@ class Hg(Vcs):
         elif not ahead and     behind: return "behind"
         elif not ahead and not behind: return "sync"
 
-
     def get_branch(self):
         """Returns the current named branch, if this makes sense for the backend. None otherwise"""
         branch = self._hg(self.path, ['branch'], catchout=True)
         return branch or None
 
-
-    def get_log(self, filelist=None, maxres=None):
-        """Get the entire log for the current HEAD"""
-        if not self._has_head(): return []
-        return self._log(refspec=None, maxres=maxres, filelist=filelist)
-
-
-    def get_raw_log(self, filelist=None):
-        """Gets the raw log as a string"""
-        if not self._has_head(): return []
-        args = ['log']
-        if filelist: args = args + filelist
-        return self._hg(self.path, args, catchout=True)
-
-
-    def get_raw_diff(self, refspec=None, filelist=None):
-        """Gets the raw diff as a string"""
-        args = ['diff', '--git']
-        if refspec:  args = args + [refspec]
-        if filelist: args = args + filelist
-        return self._hg(self.path, args, catchout=True)
-
-
-    def get_remote(self, rev=None):
-        """Returns the url for the remote repo attached to head"""
-        remote = self._hg(self.path, ['showconfig', 'paths.default'], catchout=True)
-        return remote or None
-
-
-    def get_revision_id(self, rev=None):
-        """Get a canonical key for the revision rev"""
-        if rev == None: rev = self.HEAD
-        elif rev == self.INDEX: return None
-        rev = self._sanitize_rev(rev)
-
-        return self._sanitize_rev(self._hg(self.path, ['-q', 'identify', '--id', '-r', rev], catchout=True))
-
-
     def get_info(self, rev=None):
         """Gets info about the given revision rev"""
         if rev == None: rev = self.HEAD
@@ -247,16 +136,3 @@ class Hg(Vcs):
             raise VcsError("More than one instance of revision %s ?!?" % rev)
         else:
             return L[0]
-
-
-    def get_files(self, rev=None):
-        """Gets a list of files in revision rev"""
-        if rev == None: rev = self.HEAD
-        rev = self._sanitize_rev(rev)
-
-        if rev:
-            if rev == self.INDEX: raw = self._hg(self.path, ['locate', "*"], catchout=True)
-            else:                 raw = self._hg(self.path, ['locate', '--rev', rev, "*"], catchout=True)
-            return raw.split('\n')
-        else:
-            return []
diff --git a/ranger/ext/vcs/svn.py b/ranger/ext/vcs/svn.py
index f7e4bbf4..94617b1c 100644
--- a/ranger/ext/vcs/svn.py
+++ b/ranger/ext/vcs/svn.py
@@ -3,28 +3,23 @@
 from __future__ import with_statement
 import os
 import re
-import shutil
 import logging
 from datetime import datetime
 from .vcs import Vcs, VcsError
 
-#logging.basicConfig(filename='rangersvn.log',level=logging.DEBUG,
-#                    filemode='w')
-
 class SVN(Vcs):
     HEAD = 'HEAD'
-    # Auxiliar stuff
+
+    # Generic
     #---------------------------
 
     def _svn(self, path, args, silent=True, catchout=False, bytes=False):
         return self._vcs(path, 'svn', args, silent=silent, catchout=catchout, bytes=bytes)
 
-
     def _has_head(self):
         """Checks whether repo has head"""
         return True
 
-
     def _sanitize_rev(self, rev):
         if rev == None: return None
         rev = rev.strip()
@@ -38,7 +33,6 @@ class SVN(Vcs):
 
         return rev
 
-
     def _log(self, refspec=None, maxres=None, filelist=None):
         """ Retrieves log message and parses it.
         """
@@ -77,7 +71,6 @@ class SVN(Vcs):
             logging.debug(log)
         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'
@@ -87,77 +80,19 @@ class SVN(Vcs):
         elif st in "C":  return 'conflict'
         else:            return 'unknown'
 
-
-
-    # Repo creation
-    #---------------------------
-
-    def init(self):
-        """Initializes a repo in current path"""
-        self._svn(self.path, ['init'])
-        self.update()
-
-
-    def clone(self, src):
-        """Checks out SVN repo"""
-        name = os.path.basename(self.path)
-        path = os.path.dirname(self.path)
-        try:
-            os.rmdir(self.path)
-        except OSError:
-            raise VcsError("Can't clone to %s. It is not an empty directory" % self.path)
-
-        self._svn(path, ['co', src, name])
-        self.update()
-
-
-
     # Action Interface
     #---------------------------
 
-    def commit(self, message):
-        """Commits with a given message"""
-        self._svn(self.path, ['commit', '-m', message])
-
-
     def 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'])
 
-
     def reset(self, filelist=None):
         """Equivalent to svn revert"""
         if filelist == None: filelist = self.get_status_subpaths().keys()
         self._svn(self.path, ['revert'] + filelist)
 
-
-    def pull(self):
-        """Executes SVN Update"""
-        self._svn(self.path, ['update'])
-
-
-    def push(self):
-        """Push doesn't have an SVN analog."""
-        raise NotImplementedError
-
-
-    def checkout(self, rev):
-        """Checks out a branch or revision"""
-        raise NotImplementedError
-        self._svn(self.path, ['update', rev])
-
-
-    def extract_file(self, rev, name, dest):
-        """Extracts a file from a given revision and stores it in dest dir"""
-        if rev == self.INDEX:
-            shutil.copyfile(os.path.join(self.path, name), dest)
-        else:
-            file_contents = self._svn(self.path, ['cat', '-r', rev, name], catchout=True)
-            with open(dest, 'w') as f:
-                f.write(file_contents)
-
-
     # Data Interface
     #---------------------------
 
@@ -175,15 +110,6 @@ class SVN(Vcs):
             ret[os.path.normpath(p.strip())] = sta
         return ret
 
-
-    def get_ignore_allfiles(self):
-        """Returns a set of all the ignored files in the repo"""
-        raw = self._svn(self.path, ['status'], catchout=True, bytes=True)
-#        logging.debug(raw)
-        L = re.findall(r'^I\s*(.*?)\s*$', raw.decode('utf-8'), re.MULTILINE)
-        return set(L)
-
-
     def get_status_remote(self):
         """Checks the status of the repo regarding sync state with remote branch.
 
@@ -196,47 +122,6 @@ class SVN(Vcs):
         branch = self._svn(self.path, ['branch'], catchout=True)
         return branch or None
 
-
-    def get_log(self, filelist=None, maxres=None):
-        """Get the entire log for the current HEAD"""
-        if not self._has_head(): return []
-        return self._log(refspec=None, maxres=maxres, filelist=filelist)
-
-
-    def get_raw_log(self, filelist=None):
-        """Gets the raw log as a string"""
-        if not self._has_head(): return []
-        args = ['log']
-        if filelist: args = args + filelist
-        return self._svn(self.path, args, catchout=True)
-
-
-    def get_raw_diff(self, refspec=None, filelist=None):
-        """Gets the raw diff as a string"""
-        args = ['diff', '--git']
-        if refspec:  args = args + [refspec]
-        if filelist: args = args + filelist
-        return self._svn(self.path, args, catchout=True)
-
-
-    def get_remote(self, rev=None):
-        """Returns the url for the remote repo attached to head"""
-        raw = self.get_info(rev=rev)
-        L = re.findall('URL: (.*)\n', raw.decode('utf-8'))
-
-        return L[0][0]
-
-
-    def get_revision_id(self, rev=None):
-        """Get a canonical key for the revision rev.
-
-        This is just returning the rev for SVN"""
-        if rev == None: rev = self.HEAD
-        elif rev == self.INDEX: return None
-        rev = self._sanitize_rev(rev)
-        return rev
-
-
     def get_info(self, rev=None):
         """Gets info about the given revision rev"""
         if rev == None: rev = self.HEAD
@@ -251,12 +136,3 @@ class SVN(Vcs):
             raise VcsError("More than one instance of revision %s ?!?" % rev)
         else:
             return L[0]
-
-
-    def get_files(self, rev=None):
-        """Gets a list of files in revision rev"""
-        if rev == None: rev = self.HEAD
-        rev = self._sanitize_rev(rev)
-
-        raw = self._svn(self.path, ['ls', "-r", rev], catchout=True)
-        return raw.split('\n')
diff --git a/ranger/ext/vcs/vcs.py b/ranger/ext/vcs/vcs.py
index 241038e0..69bfcf74 100644
--- a/ranger/ext/vcs/vcs.py
+++ b/ranger/ext/vcs/vcs.py
@@ -66,7 +66,7 @@ class Vcs(object):
         self.in_repodir = False
         self.track = False
 
-        self.root, self.repodir, self.repotype, self.links = self.find_root(self.path)
+        self.root, self.repodir, self.repotype, self.links = self._find_root(self.path)
         self.is_root = True if self.obj.path == self.root else False
 
         if self.root:
@@ -102,11 +102,11 @@ class Vcs(object):
 
                 self.track = self.rootvcs.track
 
-    # Auxiliar
+    # Generic
     #---------------------------
 
     def _vcs(self, path, cmd, args, silent=False, catchout=False, bytes=False):
-        """Executes a vcs command"""
+        """Executes a VCS command"""
         with open(os.devnull, 'w') as devnull:
             out = devnull if silent else None
             try:
@@ -121,10 +121,7 @@ class Vcs(object):
             except FileNotFoundError:
                 raise VcsError("{0:s} error on {1:s}: File not found".format(cmd, path))
 
-    # Generic
-    #---------------------------
-
-    def get_repotype(self, path):
+    def _get_repotype(self, path):
         """Returns the right repo type for path. None if no repo present in path"""
         for repotype in self.repotypes_settings:
             repodir = os.path.join(path, '.{0:s}'.format(repotype))
@@ -132,7 +129,7 @@ class Vcs(object):
                 return (repodir, repotype)
         return (None, None)
 
-    def find_root(self, path):
+    def _find_root(self, path):
         """Finds the repository root path. Otherwise returns none"""
         links = set()
         while True:
@@ -141,7 +138,7 @@ class Vcs(object):
                 relpath = os.path.relpath(self.path, path)
                 path = os.path.realpath(path)
                 self.path = os.path.normpath(os.path.join(path, relpath))
-            repodir, repotype = self.get_repotype(path)
+            repodir, repotype = self._get_repotype(path)
             if repodir:
                 return (path, repodir, repotype, links)
             if path == '/':
@@ -149,29 +146,6 @@ class Vcs(object):
             path = os.path.dirname(path)
         return (None, None, None, None)
 
-    def check(self):
-        """Check repository health"""
-        if not self.in_repodir \
-                and (not self.track or (not self.is_root and self.get_repotype(self.path)[0])):
-            self.__init__(self.obj)
-        elif self.track and not os.path.exists(self.repodir):
-            self.update_tree(purge=True)
-            return False
-        return True
-
-    def update_root(self):
-        """Update repository"""
-        try:
-            self.rootvcs.head = self.rootvcs.get_info(self.HEAD)
-            self.rootvcs.branch = self.rootvcs.get_branch()
-            self.rootvcs.status_subpaths = self.rootvcs.get_status_subpaths()
-            self.rootvcs.remotestatus = self.rootvcs.get_status_remote()
-            self.rootvcs.obj.vcspathstatus = self.rootvcs.get_status_root()
-        except VcsError:
-            self.update_tree(purge=True)
-            return False
-        return True
-
     def _update_walk(self, path, purge):
         """Update walk"""
         for wroot, wdirs, _ in os.walk(path):
@@ -231,43 +205,28 @@ class Vcs(object):
         if purge:
             self.rootvcs.__init__(self.rootvcs.obj)
 
-    # Action interface
-    #---------------------------
-
-    def commit(self, message):
-        """Commits with a given message"""
-        raise NotImplementedError
-
-    def add(self, filelist):
-        """Adds files to the index, preparing for commit"""
-        raise NotImplementedError
-
-    def reset(self, filelist):
-        """Removes files from the index"""
-        raise NotImplementedError
-
-    def pull(self, **kwargs):
-        """Pulls from remote"""
-        raise NotImplementedError
-
-    def push(self, **kwargs):
-        """Pushes to remote"""
-        raise NotImplementedError
-
-    def checkout(self, rev):
-        """Checks out a branch or revision"""
-        raise NotImplementedError
-
-    def extract_file(self, rev, name, dest):
-        """Extracts a file from a given revision and stores it in dest dir"""
-        raise NotImplementedError
-
-    # Data
-    #---------------------------
+    def update_root(self):
+        """Update repository"""
+        try:
+            self.rootvcs.head = self.rootvcs.get_info(self.HEAD)
+            self.rootvcs.branch = self.rootvcs.get_branch()
+            self.rootvcs.status_subpaths = self.rootvcs.get_status_subpaths()
+            self.rootvcs.remotestatus = self.rootvcs.get_status_remote()
+            self.rootvcs.obj.vcspathstatus = self.rootvcs.get_status_root()
+        except VcsError:
+            self.update_tree(purge=True)
+            return False
+        return True
 
-    def get_status_root_cheap(self):
-        """Returns the status of self.root, very cheap"""
-        raise NotImplementedError
+    def check(self):
+        """Check repository health"""
+        if not self.in_repodir \
+                and (not self.track or (not self.is_root and self._get_repotype(self.path)[0])):
+            self.__init__(self.obj)
+        elif self.track and not os.path.exists(self.repodir):
+            self.update_tree(purge=True)
+            return False
+        return True
 
     def get_status_root(self):
         """Returns the status of root"""
@@ -307,47 +266,41 @@ class Vcs(object):
                     return status
         return 'sync'
 
-    def get_status_subpaths(self):
-        """Returns a dict indexed by subpaths not in sync their status as values.
-           Paths are given relative to self.root.  Strips trailing '/' from dirs."""
-        raise NotImplementedError
+    # Action interface
+    #---------------------------
 
-    def get_status_remote(self):
-        """Checks the status of the entire repo"""
+    def add(self, filelist):
+        """Adds files to the index, preparing for commit"""
         raise NotImplementedError
 
-    def get_branch(self):
-        """Returns the current named branch, if this makes sense for the backend. None otherwise"""
+    def reset(self, filelist):
+        """Removes files from the index"""
         raise NotImplementedError
 
-    def get_log(self):
-        """Get the entire log for the current HEAD"""
-        raise NotImplementedError
+    # Data interface
+    #---------------------------
 
-    def get_raw_log(self, filelist=None):
-        """Gets the raw log as a string"""
+    def get_status_root_cheap(self):
+        """Returns the status of self.root cheaply"""
         raise NotImplementedError
 
-    def get_raw_diff(self, refspec=None, filelist=None):
-        """Gets the raw diff as a string"""
+    def get_status_subpaths(self):
+        """Returns a dict indexed by subpaths not in sync with their status as values.
+           Paths are given relative to self.root. Strips trailing '/' from dirs."""
         raise NotImplementedError
 
-    def get_remote(self):
-        """Returns the url for the remote repo attached to head"""
+    def get_status_remote(self):
+        """Checks the status of the entire repo"""
         raise NotImplementedError
 
-    def get_revision_id(self, rev=None):
-        """Get a canonical key for the revision rev"""
+    def get_branch(self):
+        """Returns the current named branch, if this makes sense for the backend. None otherwise"""
         raise NotImplementedError
 
     def get_info(self, rev=None):
         """Gets info about the given revision rev"""
         raise NotImplementedError
 
-    def get_files(self, rev=None):
-        """Gets a list of files in revision rev"""
-        raise NotImplementedError
-
 class VcsThread(threading.Thread):
     """Vcs thread"""
     def __init__(self, ui, idle_delay):
@@ -379,7 +332,7 @@ class VcsThread(threading.Thread):
                     # Redraw if tree is purged
                     if not target.vcs.check():
                         redraw = True
-                    if target.vcs.track and not target.vcs.root in roots:
+                    if target.vcs.track and target.vcs.root not in roots:
                         roots.add(target.vcs.root)
                         # Do not update repo when repodir is displayed (causes strobing)
                         if tuple(clmn for clmn in self.ui.browser.columns