about summary refs log tree commit diff stats
diff options
context:
space:
mode:
authorRyan Burns <rdburns@gmail.com>2015-08-16 21:16:22 -0400
committerRyan Burns <rdburns@gmail.com>2015-08-16 21:16:22 -0400
commitd734fc8ea38cbac13a9fbd1807a9552495734e3b (patch)
tree182abde329aa46df7c9a85b7836c686a968a3f53
parent2344714fcbaa3697f77dd0cb4477a8f8019c7dc4 (diff)
downloadranger-d734fc8ea38cbac13a9fbd1807a9552495734e3b.tar.gz
Implemented Subversion VCS support.
-rw-r--r--ranger/container/fsobject.py2
-rw-r--r--ranger/container/settings.py1
-rw-r--r--ranger/ext/vcs/svn.py279
-rw-r--r--ranger/ext/vcs/vcs.py3
4 files changed, 284 insertions, 1 deletions
diff --git a/ranger/container/fsobject.py b/ranger/container/fsobject.py
index a8c70e03..334cc2f9 100644
--- a/ranger/container/fsobject.py
+++ b/ranger/container/fsobject.py
@@ -281,6 +281,8 @@ class FileSystemObject(FileManagerAware, SettingsAware):
                 backend_state = self.settings.vcs_backend_hg
             elif self.vcs.vcsname == 'bzr':
                 backend_state = self.settings.vcs_backend_bzr
+            elif self.vcs.vcsname == 'svn':
+                backend_state = self.settings.vcs_backend_svn
             else:
                 backend_state = 'disabled'
 
diff --git a/ranger/container/settings.py b/ranger/container/settings.py
index 1d91495f..db8ca45c 100644
--- a/ranger/container/settings.py
+++ b/ranger/container/settings.py
@@ -60,6 +60,7 @@ ALLOWED_SETTINGS = {
     'vcs_backend_bzr': str,
     'vcs_backend_git': str,
     'vcs_backend_hg': str,
+    'vcs_backend_svn': str,
     'xterm_alt_key': bool,
 }
 
diff --git a/ranger/ext/vcs/svn.py b/ranger/ext/vcs/svn.py
new file mode 100644
index 00000000..f673d66b
--- /dev/null
+++ b/ranger/ext/vcs/svn.py
@@ -0,0 +1,279 @@
+# -*- 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
+#         Ryan Burns <rdburns@gmail.com>, 2015
+#
+# R. Burns start with Abdó's Hg module and modified it for Subversion.
+#
+# vcs - a python module to handle various version control systems
+
+from __future__ import with_statement
+import os
+import re
+import shutil
+import logging
+#logging.basicConfig(filename='rangersvn.log',level=logging.DEBUG,
+#                    filemode='w')
+from datetime import datetime
+try:
+    from configparser import RawConfigParser
+except ImportError:
+    from ConfigParser import RawConfigParser
+
+from .vcs import Vcs, VcsError
+
+
+class SVN(Vcs):
+    vcsname  = 'svn'
+    HEAD = 'HEAD'
+    # Auxiliar stuff
+    #---------------------------
+
+    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()
+        if len(rev) == 0: return None
+        if rev[-1] == '+': rev = rev[:-1]
+
+        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("""^[-]*\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)
+
+        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)
+        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'
+
+
+
+    # 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_allfiles().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
+    #---------------------------
+
+    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._svn(self.path, ['status'], catchout=True, bytes=True)
+#        logging.debug(raw)
+        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._svn_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._svn(self.path, ['status'], catchout=True, bytes=True)
+#        logging.debug(raw)
+        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.
+
+        I'm not sure this make sense for SVN so we're just going to return 'sync'"""
+        return 'sync'
+
+    def get_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 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
+        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)
+        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')
+
+
+# vim: expandtab:shiftwidth=4:tabstop=4:softtabstop=4:textwidth=80
diff --git a/ranger/ext/vcs/vcs.py b/ranger/ext/vcs/vcs.py
index 9bbb1779..3f7236f5 100644
--- a/ranger/ext/vcs/vcs.py
+++ b/ranger/ext/vcs/vcs.py
@@ -51,7 +51,8 @@ class Vcs(object):
         from .git import Git
         from .hg  import Hg
         from .bzr import Bzr
-        self.repo_types  = {'git': Git, 'hg': Hg, 'bzr': Bzr}
+        from .svn import SVN
+        self.repo_types  = {'git': Git, 'hg': Hg, 'bzr': Bzr, 'svn': SVN}
 
         self.path = os.path.expanduser(path)
         self.status = {}
4de10b31a82664364364c911e1d8a08c0e5'>46d4c4d ^
37f1313 ^



46d4c4d ^
37f1313 ^
46d4c4d ^














37f1313 ^
46d4c4d ^





1a6f533 ^
46d4c4d ^

1a6f533 ^
46d4c4d ^



1a6f533 ^
46d4c4d ^



1a6f533 ^
f49e2fd ^
26033f4 ^

46d4c4d ^





37f1313 ^
46d4c4d ^
9a41c7c ^






d1dd3e4 ^
9a41c7c ^









1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205