summary refs log tree commit diff stats
path: root/ranger/ext/vcs/hg.py
diff options
context:
space:
mode:
Diffstat (limited to 'ranger/ext/vcs/hg.py')
-rw-r--r--ranger/ext/vcs/hg.py358
1 files changed, 111 insertions, 247 deletions
diff --git a/ranger/ext/vcs/hg.py b/ranger/ext/vcs/hg.py
index b8731dbf..cf15e35e 100644
--- a/ranger/ext/vcs/hg.py
+++ b/ranger/ext/vcs/hg.py
@@ -1,271 +1,135 @@
-# -*- coding: utf-8 -*-
 # This file is part of ranger, the console file manager.
 # License: GNU GPL version 3, see the file "AUTHORS" for details.
-# Author: Abdó Roig-Maranges <abdo.roig@gmail.com>, 2011-2012
-#
-# vcs - a python module to handle various version control systems
 
-import os
-import re
-import shutil
+"""Mercurial module"""
+
 from datetime import datetime
-try:
-    from configparser import RawConfigParser
-except ImportError:
-    from ConfigParser import RawConfigParser
+import json
+import os
 
 from .vcs import Vcs, VcsError
 
 
 class Hg(Vcs):
-    vcsname  = 'hg'
+    """VCS implementation for Mercurial"""
     HEAD = 'tip'
-    # Auxiliar stuff
-    #---------------------------
-
-    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()
-        if len(rev) == 0: return None
-        if rev[-1] == '+': rev = rev[:-1]
-
-        try:
-            if int(rev) == 0: return None
-        except:
-            pass
+    _status_translations = (
+        ('AR', 'staged'),
+        ('M', 'changed'),
+        ('!', 'deleted'),
 
-        return rev
+        ('?', 'untracked'),
+        ('I', 'ignored'),
+    )
 
+    # Generic
 
     def _log(self, refspec=None, maxres=None, filelist=None):
 
-        fmt = "changeset: {rev}:{node}\ntag: {tags}\nuser: {author}\ndate: {date}\nsummary: {desc}\n"
-        args = ['log', '--template', fmt]
+        args = ['log', '--template', 'json']
+        if refspec:
+            args += ['--limit', '1', '--rev', refspec]
+        elif maxres:
+            args += ['--limit', str(maxres)]
+        if filelist:
+            args += ['--'] + filelist
 
-        if refspec:  args = args + ['--limit', '1', '-r', refspec]
-        elif maxres: args = args + ['--limit', str(maxres)]
-
-        if filelist: args = args + filelist
-
-        raw = self._hg(self.path, args, catchout=True)
-        L = re.findall('^changeset:\s*([0-9]*):([0-9a-zA-Z]*)\s*$\s*^tag:\s*(.*)\s*$\s*^user:\s*(.*)\s*$\s*^date:\s*(.*)$\s*^summary:\s*(.*)\s*$', raw, re.MULTILINE)
+        try:
+            output = self._run(args).rstrip('\n')
+        except VcsError:
+            return None
+        if not output:
+            return None
 
         log = []
-        for t in L:
-            dt = {}
-            dt['short'] = t[0].strip()
-            dt['revid'] = self._sanitize_rev(t[1].strip())
-            dt['author'] = t[3].strip()
-            m = re.match('\d+(\.\d+)?', t[4].strip())
-            dt['date'] = datetime.fromtimestamp(float(m.group(0)))
-            dt['summary'] = t[5].strip()
-            log.append(dt)
+        for entry in json.loads(output):
+            new = {}
+            new['short'] = entry['rev']
+            new['revid'] = entry['node']
+            new['author'] = entry['user']
+            new['date'] = datetime.fromtimestamp(entry['date'][0])
+            new['summary'] = entry['desc']
+            log.append(new)
         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'
-        elif st in "!":      return 'deleted'
-        elif st in "I":      return 'ignored'
-        elif st in "?":      return 'untracked'
-        elif st in "X":      return 'conflict'
-        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_allfiles().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
-    #---------------------------
-
-    def get_status_allfiles(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._hg(self.path, ['status'], catchout=True, bytes=True)
-        L = re.findall('^(.)\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('^.*\.orig\s*$', p):  st = 'X'
-            sta = self._hg_file_status(st)
-            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_remote_status(self):
-        """Checks the status of the repo regarding sync state with remote branch"""
-        if self.get_remote() == None:
-            return "none"
-
-        ahead = behind = True
+    def _remote_url(self):
+        """Remote url"""
         try:
-            self._hg(self.path, ['outgoing'], silent=True)
-        except:
-            ahead = False
-
-        try:
-            self._hg(self.path, ['incoming'], 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._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
-        rev = self._sanitize_rev(rev)
-        if rev == self.HEAD and not self._has_head(): return None
-
-        L = self._log(refspec=rev)
-        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)
+            return self._run(['showconfig', 'paths.default']).rstrip('\n') or None
+        except VcsError:
+            return None
+
+    def _status_translate(self, code):
+        """Translate status code"""
+        for code_x, status in self._status_translations:  # pylint: disable=invalid-name
+            if code in code_x:
+                return status
+        return 'unknown'
+
+    # Action interface
+
+    def action_add(self, filelist=None):
+        args = ['add']
+        if filelist:
+            args += ['--'] + filelist
+        self._run(args, catchout=False)
+
+    def action_reset(self, filelist=None):
+        args = ['forget', '--']
+        if filelist:
+            args += filelist
         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')
+            args += self.rootvcs.status_subpaths.keys()
+        self._run(args, catchout=False)
+
+    # Data interface
+
+    def data_status_root(self):
+        statuses = set()
+
+        # Paths with status
+        for entry in json.loads(self._run(['status', '--all', '--template', 'json'])):
+            if entry['status'] == 'C':
+                continue
+            statuses.add(self._status_translate(entry['status']))
+
+        if statuses:
+            for status in self.DIRSTATUSES:
+                if status in statuses:
+                    return status
+        return 'sync'
+
+    def data_status_subpaths(self):
+        statuses = {}
+
+        # Paths with status
+        for entry in json.loads(self._run(['status', '--all', '--template', 'json'])):
+            if entry['status'] == 'C':
+                continue
+            statuses[os.path.normpath(entry['path'])] = self._status_translate(entry['status'])
+
+        return statuses
+
+    def data_status_remote(self):
+        if self._remote_url() is None:
+            return 'none'
+        return 'unknown'
+
+    def data_branch(self):
+        return self._run(['branch']).rstrip('\n') or None
+
+    def data_info(self, rev=None):
+        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 []
-
-
-# vim: expandtab:shiftwidth=4:tabstop=4:softtabstop=4:textwidth=80
+            raise VcsError('More than one instance of revision {0:s}'.format(rev))