summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rwxr-xr-xranger/config/commands.py4
-rw-r--r--ranger/container/directory.py12
-rw-r--r--ranger/ext/vcs/vcs.py199
3 files changed, 134 insertions, 81 deletions
diff --git a/ranger/config/commands.py b/ranger/config/commands.py
index 4197ca7b..04b71f8a 100755
--- a/ranger/config/commands.py
+++ b/ranger/config/commands.py
@@ -1364,7 +1364,7 @@ class stage(Command):
                 self.fm.thisdir.vcs.action_add(filelist)
             except VcsError as error:
                 self.fm.notify('Unable to stage files: {0:s}'.format(str(error)))
-            self.fm.ui.vcsthread.wakeup()
+            self.fm.ui.vcsthread.wakeup(self.fm.thisdir)
         else:
             self.fm.notify('Unable to stage files: Not in repository')
 
@@ -1383,7 +1383,7 @@ class unstage(Command):
                 self.fm.thisdir.vcs.action_reset(filelist)
             except VcsError as error:
                 self.fm.notify('Unable to unstage files: {0:s}'.format(str(error)))
-            self.fm.ui.vcsthread.wakeup()
+            self.fm.ui.vcsthread.wakeup(self.fm.thisdir)
         else:
             self.fm.notify('Unable to unstage files: Not in repository')
 
diff --git a/ranger/container/directory.py b/ranger/container/directory.py
index 9e88d265..cd814cb1 100644
--- a/ranger/container/directory.py
+++ b/ranger/container/directory.py
@@ -332,19 +332,15 @@ class Directory(FileSystemObject, Accumulator, Loadable):
                             else:
                                 item.relative_path = item.basename
                             item.relative_path_lower = item.relative_path.lower()
-                        if item.vcs and item.vcs.track and item.is_link:
-                            if os.path.realpath(item.path) == item.vcs.root:
-                                item.vcsstatus = item.vcs.rootvcs.obj.vcsstatus
-                                item.vcsremotestatus = item.vcs.rootvcs.obj.vcsremotestatus
-                            else:
-                                item.vcsstatus = item.vcs.status_subpath(item.path)
+                        if item.vcs and item.vcs.track and not item.vcs.is_root:
+                            item.vcsstatus = item.vcs.rootvcs.status_subpath(item.path)
                     else:
                         item = File(name, preload=stats, path_is_abs=True,
                                     basename_is_rel_to=basename_is_rel_to)
                         item.load()
                         disk_usage += item.size
                         if self.vcs and self.vcs.track:
-                            item.vcsstatus = self.vcs.status_subpath(item.path)
+                            item.vcsstatus = self.vcs.rootvcs.status_subpath(item.path)
 
                     files.append(item)
                     self.percent = 100 * len(files) // len(filenames)
@@ -383,7 +379,7 @@ class Directory(FileSystemObject, Accumulator, Loadable):
             self.loading = False
             self.fm.signal_emit("finished_loading_dir", directory=self)
             if self.vcs:
-                self.fm.ui.vcsthread.wakeup()
+                self.fm.ui.vcsthread.wakeup(self)
 
     def unload(self):
         self.loading = False
diff --git a/ranger/ext/vcs/vcs.py b/ranger/ext/vcs/vcs.py
index 6e207b09..b4009d62 100644
--- a/ranger/ext/vcs/vcs.py
+++ b/ranger/ext/vcs/vcs.py
@@ -7,6 +7,12 @@ import os
 import subprocess
 import threading
 import time
+
+# Python2 compatibility
+try:
+    import queue
+except ImportError:
+    import Queue as queue
 try:
     FileNotFoundError
 except NameError:
@@ -46,8 +52,8 @@ class Vcs(object):
         'svn': {'class': 'SVN', 'setting': 'vcs_backend_svn', 'lazy': True},
     }
 
-    # Possible directory statuses in order of importance with statuses that
-    # don't make sense disabled
+    # Possible directory statuses in order of importance
+    # statuses that should not be inherited from subpaths are disabled
     DIRSTATUSES = (
         'conflict',
         'untracked',
@@ -60,52 +66,47 @@ class Vcs(object):
         'unknown',
     )
 
-    def __init__(self, directoryobject):
-        self.obj = directoryobject
-        self.path = self.obj.path
+    def __init__(self, dirobj):
+        self.obj = dirobj
+        self.path = dirobj.path
         self.repotypes_settings = set(
             repotype for repotype, values in self.REPOTYPES.items()
-            if getattr(self.obj.settings, values['setting']) in ('enabled', 'local')
+            if getattr(dirobj.settings, values['setting']) in ('enabled', 'local')
         )
-        self.in_repodir = False
-        self.track = False
 
         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
 
+        self.rootvcs = None
+        self.rootinit = False
+        self.head = None
+        self.branch = None
+        self.updatetime = None
+        self.track = False
+        self.in_repodir = False
+        self.status_subpaths = None
+
         if self.root:
             if self.is_root:
                 self.rootvcs = self
                 self.__class__ = getattr(getattr(ranger.ext.vcs, self.repotype),
                                          self.REPOTYPES[self.repotype]['class'])
-                self.status_subpaths = None
 
                 if not os.access(self.repodir, os.R_OK):
                     self.obj.vcsremotestatus = 'unknown'
                     self.obj.vcsstatus = 'unknown'
                     return
 
-                try:
-                    self.head = self.data_info(self.HEAD)
-                    self.branch = self.data_branch()
-                    self.obj.vcsremotestatus = self.data_status_remote()
-                    self.obj.vcsstatus = self.data_status_root()
-                except VcsError:
-                    return
-
-                self.timestamp = time.time()
                 self.track = True
             else:
-                self.rootvcs = directoryobject.fm.get_directory(self.root).vcs
+                self.rootvcs = dirobj.fm.get_directory(self.root).vcs
                 self.rootvcs.links |= self.links
                 self.__class__ = self.rootvcs.__class__
+                self.track = self.rootvcs.track
 
-                # Do not track self.repodir or its subpaths
                 if self.path == self.repodir or self.path.startswith(self.repodir + '/'):
                     self.in_repodir = True
-                    return
-
-                self.track = self.rootvcs.track
+                    self.track = False
 
     # Generic
     #---------------------------
@@ -125,7 +126,7 @@ class Vcs(object):
     def _get_repotype(self, path):
         """Get type for path"""
         for repotype in self.repotypes_settings:
-            repodir = os.path.join(path, '.{0:s}'.format(repotype))
+            repodir = os.path.join(path, '.' + repotype)
             if os.path.exists(repodir):
                 return (repodir, repotype)
         return (None, None)
@@ -151,18 +152,31 @@ class Vcs(object):
 
         return (None, None, None, None)
 
+    def init_root(self):
+        """Initialize root cheaply"""
+        try:
+            self.head = self.data_info(self.HEAD)
+            self.branch = self.data_branch()
+            self.obj.vcsremotestatus = self.data_status_remote()
+            self.obj.vcsstatus = self.data_status_root()
+        except VcsError:
+            self.update_tree(purge=True)
+            return False
+        self.rootinit = True
+        return True
+
     def _update_walk(self, path, purge):
         """Update walk"""
         for wroot, wdirs, _ in os.walk(path):
             # Only update loaded directories
             try:
-                wroot_obj = self.obj.fm.directories[wroot]
+                wrootobj = self.obj.fm.directories[wroot]
             except KeyError:
                 wdirs[:] = []
                 continue
-            if wroot_obj.content_loaded:
+            if wrootobj.content_loaded:
                 has_vcschild = False
-                for fileobj in wroot_obj.files_all:
+                for fileobj in wrootobj.files_all:
                     if purge:
                         if fileobj.is_directory:
                             fileobj.vcsstatus = None
@@ -178,11 +192,11 @@ class Vcs(object):
                         if fileobj.vcs.is_root:
                             has_vcschild = True
                         else:
-                            fileobj.vcsstatus = wroot_obj.vcs.status_subpath(
+                            fileobj.vcsstatus = self.status_subpath(
                                 fileobj.path, is_directory=True)
                     else:
-                        fileobj.vcsstatus = wroot_obj.vcs.status_subpath(fileobj.path)
-                wroot_obj.has_vcschild = has_vcschild
+                        fileobj.vcsstatus = self.status_subpath(fileobj.path)
+                wrootobj.has_vcschild = has_vcschild
 
             # Remove dead directories
             for wdir in list(wdirs):
@@ -196,35 +210,39 @@ class Vcs(object):
 
     def update_tree(self, purge=False):
         """Update tree state"""
-        self._update_walk(self.root, purge)
-        for path in list(self.rootvcs.links):
+        self._update_walk(self.path, purge)
+        for path in list(self.links):
             self._update_walk(path, purge)
             try:
                 dirobj = self.obj.fm.directories[path]
             except KeyError:
+                self.links.remove(path)
                 continue
             if purge:
                 dirobj.vcsstatus = None
                 dirobj.vcs.__init__(dirobj)
-            elif dirobj.vcs.path == self.root:
-                dirobj.vcsstatus = self.rootvcs.status_root()
+            elif dirobj.vcs.path == self.path:
+                dirobj.vcsremotestatus = self.obj.vcsremotestatus
+                dirobj.vcsstatus = self.obj.vcsstatus
             else:
-                dirobj.vcsstatus = dirobj.vcs.status_subpath(dirobj.path, is_directory=True)
+                dirobj.vcsstatus = self.status_subpath(
+                    os.path.realpath(dirobj.path), is_directory=True)
         if purge:
-            self.rootvcs.__init__(self.rootvcs.obj)
+            self.__init__(self.obj)
 
     def update_root(self):
         """Update root state"""
         try:
-            self.rootvcs.head = self.rootvcs.data_info(self.HEAD)
-            self.rootvcs.branch = self.rootvcs.data_branch()
-            self.rootvcs.status_subpaths = self.rootvcs.data_status_subpaths()
-            self.rootvcs.obj.vcsremotestatus = self.rootvcs.data_status_remote()
-            self.rootvcs.obj.vcsstatus = self.rootvcs.status_root()
+            self.head = self.data_info(self.HEAD)
+            self.branch = self.data_branch()
+            self.status_subpaths = self.data_status_subpaths()
+            self.obj.vcsremotestatus = self.data_status_remote()
+            self.obj.vcsstatus = self.status_root()
         except VcsError:
             self.update_tree(purge=True)
             return False
-        self.timestamp = time.time()
+        self.rootinit = True
+        self.updatetime = time.time()
         return True
 
     def check(self):
@@ -233,12 +251,15 @@ class Vcs(object):
                 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)
+            self.rootvcs.update_tree(purge=True)
             return False
         return True
 
     def check_outdated(self):
-        """Check if outdated"""
+        """Check if root is outdated"""
+        if self.updatetime is None:
+            return True
+
         for wroot, wdirs, _ in os.walk(self.path):
             wrootobj = self.obj.fm.get_directory(wroot)
             wrootobj.load_if_outdated()
@@ -246,18 +267,18 @@ class Vcs(object):
                 wdirs[:] = []
                 continue
 
-            if wrootobj.stat and self.timestamp < wrootobj.stat.st_mtime:
+            if wrootobj.stat and self.updatetime < wrootobj.stat.st_mtime:
                 return True
             if wrootobj.files_all:
                 for wfile in wrootobj.files_all:
-                    if wfile.stat and self.timestamp < wfile.stat.st_mtime:
+                    if wfile.stat and self.updatetime < wfile.stat.st_mtime:
                         return True
         return False
 
     def status_root(self):
         """Returns root status"""
-        if self.rootvcs.status_subpaths is None:
-            return 'unknown'
+        if self.status_subpaths is None:
+            return 'none'
 
         statuses = set(status for path, status in self.status_subpaths.items())
         for status in self.DIRSTATUSES:
@@ -271,27 +292,21 @@ class Vcs(object):
 
         path needs to be self.obj.path or subpath thereof
         """
-        if self.rootvcs.status_subpaths is None:
-            return 'unknown'
+        if self.status_subpaths is None:
+            return 'none'
 
-        if path == self.obj.path:
-            relpath = os.path.relpath(self.path, self.root)
-        else:
-            relpath = os.path.relpath(
-                os.path.join(self.path, os.path.relpath(path, self.obj.path)),
-                self.root,
-            )
+        relpath = os.path.relpath(path, self.path)
 
         # check if relpath or its parents has a status
         tmppath = relpath
         while tmppath:
-            if tmppath in self.rootvcs.status_subpaths:
-                return self.rootvcs.status_subpaths[tmppath]
+            if tmppath in self.status_subpaths:
+                return self.status_subpaths[tmppath]
             tmppath = os.path.dirname(tmppath)
 
         # check if path contains some file in status
         if is_directory:
-            statuses = set(status for subpath, status in self.rootvcs.status_subpaths.items()
+            statuses = set(status for subpath, status in self.status_subpaths.items()
                            if subpath.startswith(relpath + '/'))
             for status in self.DIRSTATUSES:
                 if status in statuses:
@@ -336,14 +351,37 @@ class Vcs(object):
         """Returns info string about revision rev. None in special cases"""
         raise NotImplementedError
 
+def init_subroots(dirobj):
+    """Initialize roots under dirobj"""
+    redraw = False
+    for fileobj in dirobj.files_all:
+        if not fileobj.is_directory or not fileobj.vcs or not fileobj.vcs.track:
+            continue
+        if fileobj.vcs.is_root and not fileobj.vcs.rootinit:
+            if fileobj.vcs.init_root():
+                redraw = True
+        elif fileobj.is_link:
+            if os.path.realpath(fileobj.path) == fileobj.vcs.root:
+                if not fileobj.vcs.rootvcs.rootinit:
+                    fileobj.vcs.rootvcs.init_root()
+                fileobj.vcsstatus = fileobj.vcs.rootvcs.obj.vcsstatus
+                fileobj.vcsremotestatus = fileobj.vcs.rootvcs.obj.vcsremotestatus
+            else:
+                fileobj.vcsstatus = fileobj.vcs.rootvcs.status_subpath(
+                    os.path.realpath(fileobj.path))
+            redraw = True
+    return redraw
+
 class VcsThread(threading.Thread):
-    """Vcs thread"""
+    """VCS thread"""
     def __init__(self, ui, idle_delay):
         super(VcsThread, self).__init__()
         self.daemon = True
         self.ui = ui
         self.delay = idle_delay
+        self.queue = queue.Queue()
         self.wake = threading.Event()
+        self.awoken = False
 
     def _check(self):
         """Check for hinders"""
@@ -365,14 +403,31 @@ class VcsThread(threading.Thread):
         return None
 
     def run(self):
-        roots = set() # already updated roots
+        roots = set() # Handled roots
         redraw = False
+
         while True:
             if self._check():
                 self.wake.wait(timeout=self.delay)
-                self.wake.clear()
+                if self.wake.is_set():
+                    self.awoken = True
+                    self.wake.clear()
                 continue
 
+            while True:
+                try:
+                    dirobj = self.queue.get(block=False)
+                except queue.Empty:
+                    break
+                # Update if root
+                if dirobj.vcs.track and dirobj.vcs.is_root:
+                    roots.add(dirobj.vcs.path)
+                    if dirobj.vcs.update_root():
+                        dirobj.vcs.update_tree()
+                        redraw = True
+                if dirobj.files_all and init_subroots(dirobj):
+                    redraw = True
+
             # Exclude root if repodir in the rightmost column (causes strobing)
             target = self._targeted_directory_rightmost()
             if target and target.vcs and target.vcs.in_repodir:
@@ -387,11 +442,9 @@ class VcsThread(threading.Thread):
                     if target.vcs.track and target.vcs.root not in roots:
                         roots.add(target.vcs.root)
                         lazy = target.vcs.REPOTYPES[target.vcs.repotype]['lazy']
-                        if (target.vcs.rootvcs.status_subpaths is None \
-                                or (lazy and target.vcs.check_outdated()) \
-                                or not lazy) \
-                                and target.vcs.update_root():
-                            target.vcs.update_tree()
+                        if ((lazy and target.vcs.rootvcs.check_outdated()) or not lazy) \
+                                and target.vcs.rootvcs.update_root():
+                            target.vcs.rootvcs.update_tree()
                             redraw = True
             roots.clear()
 
@@ -401,14 +454,18 @@ class VcsThread(threading.Thread):
                     if column.target and column.target.is_directory:
                         column.need_redraw = True
                 self.ui.status.need_redraw = True
-                if self.wake.is_set():
+                if self.awoken:
                     self.ui.redraw()
 
-            self.wake.clear()
+            self.awoken = False
             self.wake.wait(timeout=self.delay)
+            if self.wake.is_set():
+                self.awoken = True
+                self.wake.clear()
 
-    def wakeup(self):
+    def wakeup(self, dirobj):
         """Wakeup thread"""
+        self.queue.put(dirobj)
         self.wake.set()
 
 # Backend imports