From 1ca99394fccbfe1db8678530b1b7ab1c017d2310 Mon Sep 17 00:00:00 2001 From: nfnty Date: Fri, 9 Oct 2015 00:40:34 +0200 Subject: VCS: Implement cheap subpath repo updating --- ranger/colorschemes/default.py | 2 +- ranger/container/directory.py | 9 +++----- ranger/ext/vcs/git.py | 19 ++++++++++++++++ ranger/ext/vcs/vcs.py | 50 ++++++++++++++++++++---------------------- ranger/gui/context.py | 2 +- ranger/gui/widgets/__init__.py | 2 +- 6 files changed, 49 insertions(+), 35 deletions(-) diff --git a/ranger/colorschemes/default.py b/ranger/colorschemes/default.py index 80fa9e39..7a248f2e 100644 --- a/ranger/colorschemes/default.py +++ b/ranger/colorschemes/default.py @@ -137,7 +137,7 @@ class Default(ColorScheme): elif context.vcsremote and not context.selected: attr &= ~bold - if context.vcssync: + if context.vcssync or context.vcslocal: fg = green elif context.vcsbehind: fg = red diff --git a/ranger/container/directory.py b/ranger/container/directory.py index 0cf2b528..379e0830 100644 --- a/ranger/container/directory.py +++ b/ranger/container/directory.py @@ -307,7 +307,6 @@ class Directory(FileSystemObject, Accumulator, Loadable): disk_usage = 0 if self.settings.vcs_aware and self.vcs.root: - self.has_vcschild = True self.vcs.update(self) for name in filenames: @@ -334,19 +333,17 @@ class Directory(FileSystemObject, Accumulator, Loadable): except: item = Directory(name, preload=stats, path_is_abs=True) item.load() - if item.settings.vcs_aware: + if item.settings.vcs_aware and item.vcs.root: + item.vcs.update(item, child=True) if item.vcs.is_root: self.has_vcschild = True - item.vcs.update(item) - elif item.vcs.root: - item.vcs.update_child(item) 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.settings.vcs_aware and self.vcs.root: - item.vcspathstatus = self.vcs.get_path_status(item.path) + item.vcspathstatus = self.vcs.get_status_subpath(item.path) files.append(item) self.percent = 100 * len(files) // len(filenames) diff --git a/ranger/ext/vcs/git.py b/ranger/ext/vcs/git.py index 54250026..d10c5428 100644 --- a/ranger/ext/vcs/git.py +++ b/ranger/ext/vcs/git.py @@ -156,6 +156,25 @@ class Git(Vcs): # Data Interface #--------------------------- + def get_status_root_child(self): + """Returns the status of a child root, very cheap""" + statuses = set() + # Paths with status + skip = False + for line in self._git(['status', '--porcelain', '-z'], + catchout=True, bytes=True).decode('utf-8').split('\x00')[:-1]: + if skip: + skip = False + continue + statuses.add(self._git_status_translate(line[:2])) + if line.startswith('R'): + skip = True + + for status in self.DIR_STATUS: + if status in statuses: + return status + return 'sync' + def get_status_subpaths(self): """Returns a dict (path: status) for paths not in sync. Strips trailing '/' from dirs""" statuses = {} diff --git a/ranger/ext/vcs/vcs.py b/ranger/ext/vcs/vcs.py index 7dc171e8..ebd18385 100644 --- a/ranger/ext/vcs/vcs.py +++ b/ranger/ext/vcs/vcs.py @@ -81,8 +81,7 @@ class Vcs(object): if setting in ('enabled', 'local') ] - self.status = {} - self.ignored = set() + self.status_subpaths = {} self.head = None self.remotestatus = None self.branch = None @@ -142,32 +141,27 @@ class Vcs(object): path = os.path.dirname(path) return (None, None) - def update(self, directoryobject): + def update(self, directoryobject, child=False): """Update repository""" root = self if self.is_root else directoryobject.fm.get_directory(self.root).vcs - root.head = root.get_info(root.HEAD) - root.branch = root.get_branch() - root.remotestatus = root.get_status_remote() - root.status = root.get_status_subpaths() + if child and self.is_root: + directoryobject.vcspathstatus = self.get_status_root_child() + elif not child: + root.head = root.get_info(root.HEAD) + root.branch = root.get_branch() + root.status_subpaths = root.get_status_subpaths() + if self.is_root: + directoryobject.vcspathstatus = self.get_status_root() if self.is_root: - directoryobject.vcspathstatus = self.get_root_status() + root.remotestatus = root.get_status_remote() else: self.head = root.head self.branch = root.branch - self.status = root.status - directoryobject.vcspathstatus = root.get_path_status( + self.status_subpaths = root.status_subpaths + directoryobject.vcspathstatus = root.get_status_subpath( self.path, is_directory=True) - def update_child(self, directoryobject): - """After update() for subdirectories""" - root = directoryobject.fm.get_directory(self.root).vcs - self.head = root.head - self.branch = root.branch - self.status = root.status - directoryobject.vcspathstatus = root.get_path_status( - self.path, is_directory=True) - # Repo creation #--------------------------- @@ -241,28 +235,32 @@ class Vcs(object): """Checks whether HEAD is tracking a remote repo""" return self.get_remote(self.HEAD) is not None - def get_root_status(self): + def get_status_root_child(self): + """Returns the status of a child root, very cheap""" + raise NotImplementedError + + def get_status_root(self): """Returns the status of root""" - statuses = set(status for path, status in self.status.items()) + statuses = set(status for path, status in self.status_subpaths.items()) for status in self.DIR_STATUS: if status in statuses: return status return 'sync' - def get_path_status(self, path, is_directory=False): + def get_status_subpath(self, path, is_directory=False): """Returns the status of path""" relpath = os.path.relpath(path, self.root) # check if relpath or its parents has a status tmppath = relpath while tmppath: - if tmppath in self.status: - return self.status[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.status.items() + statuses = set(status for subpath, status in self.status_subpaths.items() if subpath.startswith(relpath + '/')) for status in self.DIR_STATUS: if status in statuses: @@ -270,7 +268,7 @@ class Vcs(object): return 'sync' def get_status_subpaths(self): - """Returns a dict indexed by files not in sync their status as values. + """Returns a dict indexed by subpaths not in sync their status as values. Paths are given relative to the root. Strips trailing '/' from dirs.""" raise NotImplementedError diff --git a/ranger/gui/context.py b/ranger/gui/context.py index e5aef06c..66d43d1a 100644 --- a/ranger/gui/context.py +++ b/ranger/gui/context.py @@ -19,7 +19,7 @@ CONTEXT_KEYS = ['reset', 'error', 'badinfo', 'infostring', 'vcsfile', 'vcsremote', 'vcsinfo', 'vcscommit', 'vcsconflict', 'vcschanged', 'vcsunknown', 'vcsignored', - 'vcsstaged', 'vcssync', 'vcsbehind', 'vcsahead', 'vcsdiverged'] + 'vcsstaged', 'vcssync', 'vcslocal', 'vcsbehind', 'vcsahead', 'vcsdiverged'] class Context(object): def __init__(self, keys): diff --git a/ranger/gui/widgets/__init__.py b/ranger/gui/widgets/__init__.py index a1b4c289..4dd28c6d 100644 --- a/ranger/gui/widgets/__init__.py +++ b/ranger/gui/widgets/__init__.py @@ -15,7 +15,7 @@ class Widget(Displayable): 'none': (' ', []), 'unknown': ('?', ["vcsunknown"])} - vcsremotestatus_symb = {'none': (' ', []), + vcsremotestatus_symb = {'none': ('⌂', ["vcslocal"]), 'sync': ('=', ["vcssync"]), 'behind': ('<', ["vcsbehind"]), 'ahead': ('>', ["vcsahead"]), -- cgit 1.4.1-2-gfad0