Guido van Rossum | 5f07b84 | 1995-04-26 22:57:11 +0000 | [diff] [blame] | 1 | #! /usr/local/bin/python |
| 2 | |
| 3 | """RCS Proxy. |
| 4 | |
| 5 | Provide a simplified interface on RCS files, locally or remotely. |
| 6 | The functionality is geared towards implementing some sort of |
| 7 | remote CVS like utility. It is modeled after the similar module |
| 8 | FSProxy. |
| 9 | |
| 10 | The module defines three classes: |
| 11 | |
| 12 | RCSProxyLocal -- used for local access |
| 13 | RCSProxyServer -- used on the server side of remote access |
| 14 | RCSProxyClient -- used on the client side of remote access |
| 15 | |
| 16 | The remote classes are instantiated with an IP address and an optional |
| 17 | verbosity flag. |
| 18 | """ |
| 19 | |
| 20 | import server |
| 21 | import client |
| 22 | import md5 |
| 23 | import os |
| 24 | import fnmatch |
| 25 | import string |
| 26 | import tempfile |
| 27 | |
| 28 | |
| 29 | okchars = string.letters + string.digits + '-_=+.' |
| 30 | |
| 31 | |
| 32 | class RCSProxyLocal: |
| 33 | |
| 34 | def __init__(self): |
| 35 | self._dirstack = [] |
| 36 | |
| 37 | def _close(self): |
| 38 | while self._dirstack: |
| 39 | self.back() |
| 40 | |
| 41 | def pwd(self): |
| 42 | return os.getcwd() |
| 43 | |
| 44 | def cd(self, name): |
| 45 | save = os.getcwd() |
| 46 | os.chdir(name) |
| 47 | self._dirstack.append(save) |
| 48 | |
| 49 | def back(self): |
| 50 | if not self._dirstack: |
| 51 | raise os.error, "empty directory stack" |
| 52 | dir = self._dirstack[-1] |
| 53 | os.chdir(dir) |
| 54 | del self._dirstack[-1] |
| 55 | |
| 56 | def _filter(self, files, pat = None): |
| 57 | if pat: |
| 58 | def keep(name, pat = pat): |
| 59 | return fnmatch.fnmatch(name, pat) |
| 60 | files = filter(keep, files) |
| 61 | files.sort() |
| 62 | return files |
| 63 | |
| 64 | def isfile(self, name): |
| 65 | namev = name + ',v' |
| 66 | return os.path.isfile(namev) or \ |
| 67 | os.path.isfile(os.path.join('RCS', namev)) |
| 68 | |
| 69 | def _unmangle(self, name): |
| 70 | if type(name) == type(''): |
| 71 | rev = '' |
| 72 | else: |
| 73 | name, rev = name |
| 74 | return name, rev |
| 75 | |
| 76 | def checkfile(self, name): |
| 77 | name, rev = self._unmangle(name) |
| 78 | if not self.isfile(name): |
| 79 | raise os.error, 'not an rcs file %s' % `name` |
| 80 | for c in rev: |
| 81 | if c not in okchars: |
| 82 | raise ValueError, "bad char in rev" |
| 83 | return name, rev |
| 84 | |
| 85 | def listfiles(self, pat = None): |
| 86 | def isrcs(name): return name[-2:] == ',v' |
| 87 | def striprcs(name): return name[:-2] |
| 88 | files = os.listdir(os.curdir) |
| 89 | files = filter(isrcs, files) |
| 90 | if os.path.isdir('RCS'): |
| 91 | files2 = os.listdir('RCS') |
| 92 | files2 = filter(isrcs, files2) |
| 93 | files = files + files2 |
| 94 | files = map(striprcs, files) |
| 95 | return self._filter(files, pat) |
| 96 | |
| 97 | def listsubdirs(self, pat = None): |
| 98 | files = os.listdir(os.curdir) |
| 99 | files = filter(os.path.isdir, files) |
| 100 | return self._filter(files, pat) |
| 101 | |
| 102 | def isdir(self, name): |
| 103 | return os.path.isdir(name) |
| 104 | |
| 105 | def _open(self, name, cmd = 'co -p'): |
| 106 | name, rev = self.checkfile(name) |
| 107 | namev = name + ',v' |
| 108 | if rev: |
| 109 | cmd = cmd + ' -r' + rev |
| 110 | return os.popen('%s %s' % (cmd, `namev`)) |
| 111 | |
| 112 | def _closepipe(self, f): |
| 113 | sts = f.close() |
| 114 | if sts: |
| 115 | raise IOError, "Exit status %d" % sts |
| 116 | |
| 117 | def _remove(self, fn): |
| 118 | try: |
| 119 | os.unlink(fn) |
| 120 | except os.error: |
| 121 | pass |
| 122 | |
| 123 | def sum(self, name): |
| 124 | f = self._open(name) |
| 125 | BUFFERSIZE = 1024*8 |
| 126 | sum = md5.new() |
| 127 | while 1: |
| 128 | buffer = f.read(BUFFERSIZE) |
| 129 | if not buffer: |
| 130 | break |
| 131 | sum.update(buffer) |
| 132 | self._closepipe(f) |
| 133 | return sum.digest() |
| 134 | |
| 135 | def _list(self, function, list): |
| 136 | if list is None: |
| 137 | list = self.listfiles() |
| 138 | res = [] |
| 139 | for name in list: |
| 140 | try: |
| 141 | res.append((name, function(name))) |
| 142 | except (os.error, IOError): |
| 143 | res.append((name, None)) |
| 144 | return res |
| 145 | |
| 146 | def sumlist(self, list = None): |
| 147 | return self.list(self.sum, list) |
| 148 | |
| 149 | def _dict(self, function, list): |
| 150 | if list is None: |
| 151 | list = self.listfiles() |
| 152 | dict = {} |
| 153 | for name in list: |
| 154 | try: |
| 155 | dict[name] = function(name) |
| 156 | except (os.error, IOError): |
| 157 | pass |
| 158 | return dict |
| 159 | |
| 160 | def sumdict(self, list = None): |
| 161 | return self.dict(self.sum, list) |
| 162 | |
| 163 | def get(self, name): |
| 164 | f = self._open(name) |
| 165 | data = f.read() |
| 166 | self._closepipe(f) |
| 167 | return data |
| 168 | |
| 169 | def info(self, name): |
| 170 | f = self._open(name, 'rlog -h') |
| 171 | dict = {} |
| 172 | while 1: |
| 173 | line = f.readline() |
| 174 | if not line: break |
| 175 | if line[0] == '\t': |
| 176 | continue # XXX lock details, later |
| 177 | i = string.find(line, ':') |
| 178 | if i > 0: |
| 179 | key, value = line[:i], string.strip(line[i+1:]) |
| 180 | dict[key] = value |
| 181 | self._closepipe(f) |
| 182 | return dict |
| 183 | |
| 184 | def head(self, name): |
| 185 | dict = self.info(name) |
| 186 | return dict['head'] |
| 187 | |
| 188 | def log(self, name, flags = ''): |
| 189 | f = self._open(name, 'rlog %s 2>&1' % flags) |
| 190 | log = f.read() |
| 191 | self._closepipe(f) |
| 192 | return log |
| 193 | |
| 194 | def put(self, fullname, data, message = ""): |
| 195 | if message and message[-1] != '\n': |
| 196 | message = message + '\n' |
| 197 | name, rev = self._unmangle(fullname) |
| 198 | new = not self.isfile(name) |
| 199 | if new: |
| 200 | for c in name: |
| 201 | if c not in okchars: |
| 202 | raise ValueError, "bad char in name" |
| 203 | else: |
| 204 | self._remove(name) |
| 205 | f = open(name, 'w') |
| 206 | f.write(data) |
| 207 | f.close() |
| 208 | tf = tempfile.mktemp() |
| 209 | try: |
| 210 | if not new: |
| 211 | cmd = "rcs -l%s %s >>%s 2>&1" % (rev, name, tf) |
| 212 | sts = os.system(cmd) |
| 213 | if sts: |
| 214 | raise IOError, "rcs -l exit status %d" % sts |
| 215 | cmd = "ci -r%s %s >>%s 2>&1" % (rev, name, tf) |
| 216 | p = os.popen(cmd, 'w') |
| 217 | p.write(message) |
| 218 | sts = p.close() |
| 219 | if sts: |
| 220 | raise IOError, "ci exit status %d" % sts |
| 221 | messages = open(tf).read() |
| 222 | return messages or None |
| 223 | finally: |
| 224 | self._remove(tf) |
| 225 | |
| 226 | def mkdir(self, name): |
| 227 | os.mkdir(name, 0777) |
| 228 | |
| 229 | def rmdir(self, name): |
| 230 | os.rmdir(name) |
| 231 | |
| 232 | |
| 233 | class RCSProxyServer(RCSProxyLocal, server.Server): |
| 234 | |
| 235 | def __init__(self, address, verbose = server.VERBOSE): |
| 236 | RCSProxyLocal.__init__(self) |
| 237 | server.Server.__init__(self, address, verbose) |
| 238 | |
| 239 | def _close(self): |
| 240 | server.Server._close(self) |
| 241 | RCSProxyLocal._close(self) |
| 242 | |
| 243 | def _serve(self): |
| 244 | server.Server._serve(self) |
| 245 | # Retreat into start directory |
| 246 | while self._dirstack: self.back() |
| 247 | |
| 248 | |
| 249 | class RCSProxyClient(client.Client): |
| 250 | |
| 251 | def __init__(self, address, verbose = client.VERBOSE): |
| 252 | client.Client.__init__(self, address, verbose) |
| 253 | |
| 254 | |
| 255 | def test_server(): |
| 256 | import string |
| 257 | import sys |
| 258 | if sys.argv[1:]: |
| 259 | port = string.atoi(sys.argv[1]) |
| 260 | else: |
| 261 | port = 4127 |
| 262 | proxy = RCSProxyServer(('', port)) |
| 263 | proxy._serverloop() |
| 264 | |
| 265 | |
| 266 | def test(): |
| 267 | import sys |
| 268 | if not sys.argv[1:] or sys.argv[1] and sys.argv[1][0] in '0123456789': |
| 269 | test_server() |
| 270 | sys.exit(0) |
| 271 | proxy = RCSProxyLocal() |
| 272 | what = sys.argv[1] |
| 273 | if hasattr(proxy, what): |
| 274 | attr = getattr(proxy, what) |
| 275 | if callable(attr): |
| 276 | print apply(attr, tuple(sys.argv[2:])) |
| 277 | else: |
| 278 | print `attr` |
| 279 | else: |
| 280 | print "%s: no such attribute" % what |
| 281 | sys.exit(2) |
| 282 | |
| 283 | |
| 284 | if __name__ == '__main__': |
| 285 | test() |