diff options
-rwxr-xr-x | Demo/pdist/RCSProxy.py | 285 | ||||
-rw-r--r-- | Demo/pdist/README | 2 | ||||
-rwxr-xr-x | Demo/pdist/cvslib.py | 186 | ||||
-rwxr-xr-x | Demo/pdist/rcvs.py | 192 | ||||
-rwxr-xr-x | Demo/pdist/rrcs.py | 174 |
5 files changed, 839 insertions, 0 deletions
diff --git a/Demo/pdist/RCSProxy.py b/Demo/pdist/RCSProxy.py new file mode 100755 index 0000000..0a12157 --- /dev/null +++ b/Demo/pdist/RCSProxy.py @@ -0,0 +1,285 @@ +#! /usr/local/bin/python + +"""RCS Proxy. + +Provide a simplified interface on RCS files, locally or remotely. +The functionality is geared towards implementing some sort of +remote CVS like utility. It is modeled after the similar module +FSProxy. + +The module defines three classes: + +RCSProxyLocal -- used for local access +RCSProxyServer -- used on the server side of remote access +RCSProxyClient -- used on the client side of remote access + +The remote classes are instantiated with an IP address and an optional +verbosity flag. +""" + +import server +import client +import md5 +import os +import fnmatch +import string +import tempfile + + +okchars = string.letters + string.digits + '-_=+.' + + +class RCSProxyLocal: + + def __init__(self): + self._dirstack = [] + + def _close(self): + while self._dirstack: + self.back() + + def pwd(self): + return os.getcwd() + + def cd(self, name): + save = os.getcwd() + os.chdir(name) + self._dirstack.append(save) + + def back(self): + if not self._dirstack: + raise os.error, "empty directory stack" + dir = self._dirstack[-1] + os.chdir(dir) + del self._dirstack[-1] + + def _filter(self, files, pat = None): + if pat: + def keep(name, pat = pat): + return fnmatch.fnmatch(name, pat) + files = filter(keep, files) + files.sort() + return files + + def isfile(self, name): + namev = name + ',v' + return os.path.isfile(namev) or \ + os.path.isfile(os.path.join('RCS', namev)) + + def _unmangle(self, name): + if type(name) == type(''): + rev = '' + else: + name, rev = name + return name, rev + + def checkfile(self, name): + name, rev = self._unmangle(name) + if not self.isfile(name): + raise os.error, 'not an rcs file %s' % `name` + for c in rev: + if c not in okchars: + raise ValueError, "bad char in rev" + return name, rev + + def listfiles(self, pat = None): + def isrcs(name): return name[-2:] == ',v' + def striprcs(name): return name[:-2] + files = os.listdir(os.curdir) + files = filter(isrcs, files) + if os.path.isdir('RCS'): + files2 = os.listdir('RCS') + files2 = filter(isrcs, files2) + files = files + files2 + files = map(striprcs, files) + return self._filter(files, pat) + + def listsubdirs(self, pat = None): + files = os.listdir(os.curdir) + files = filter(os.path.isdir, files) + return self._filter(files, pat) + + def isdir(self, name): + return os.path.isdir(name) + + def _open(self, name, cmd = 'co -p'): + name, rev = self.checkfile(name) + namev = name + ',v' + if rev: + cmd = cmd + ' -r' + rev + return os.popen('%s %s' % (cmd, `namev`)) + + def _closepipe(self, f): + sts = f.close() + if sts: + raise IOError, "Exit status %d" % sts + + def _remove(self, fn): + try: + os.unlink(fn) + except os.error: + pass + + def sum(self, name): + f = self._open(name) + BUFFERSIZE = 1024*8 + sum = md5.new() + while 1: + buffer = f.read(BUFFERSIZE) + if not buffer: + break + sum.update(buffer) + self._closepipe(f) + return sum.digest() + + def _list(self, function, list): + if list is None: + list = self.listfiles() + res = [] + for name in list: + try: + res.append((name, function(name))) + except (os.error, IOError): + res.append((name, None)) + return res + + def sumlist(self, list = None): + return self.list(self.sum, list) + + def _dict(self, function, list): + if list is None: + list = self.listfiles() + dict = {} + for name in list: + try: + dict[name] = function(name) + except (os.error, IOError): + pass + return dict + + def sumdict(self, list = None): + return self.dict(self.sum, list) + + def get(self, name): + f = self._open(name) + data = f.read() + self._closepipe(f) + return data + + def info(self, name): + f = self._open(name, 'rlog -h') + dict = {} + while 1: + line = f.readline() + if not line: break + if line[0] == '\t': + continue # XXX lock details, later + i = string.find(line, ':') + if i > 0: + key, value = line[:i], string.strip(line[i+1:]) + dict[key] = value + self._closepipe(f) + return dict + + def head(self, name): + dict = self.info(name) + return dict['head'] + + def log(self, name, flags = ''): + f = self._open(name, 'rlog %s 2>&1' % flags) + log = f.read() + self._closepipe(f) + return log + + def put(self, fullname, data, message = ""): + if message and message[-1] != '\n': + message = message + '\n' + name, rev = self._unmangle(fullname) + new = not self.isfile(name) + if new: + for c in name: + if c not in okchars: + raise ValueError, "bad char in name" + else: + self._remove(name) + f = open(name, 'w') + f.write(data) + f.close() + tf = tempfile.mktemp() + try: + if not new: + cmd = "rcs -l%s %s >>%s 2>&1" % (rev, name, tf) + sts = os.system(cmd) + if sts: + raise IOError, "rcs -l exit status %d" % sts + cmd = "ci -r%s %s >>%s 2>&1" % (rev, name, tf) + p = os.popen(cmd, 'w') + p.write(message) + sts = p.close() + if sts: + raise IOError, "ci exit status %d" % sts + messages = open(tf).read() + return messages or None + finally: + self._remove(tf) + + def mkdir(self, name): + os.mkdir(name, 0777) + + def rmdir(self, name): + os.rmdir(name) + + +class RCSProxyServer(RCSProxyLocal, server.Server): + + def __init__(self, address, verbose = server.VERBOSE): + RCSProxyLocal.__init__(self) + server.Server.__init__(self, address, verbose) + + def _close(self): + server.Server._close(self) + RCSProxyLocal._close(self) + + def _serve(self): + server.Server._serve(self) + # Retreat into start directory + while self._dirstack: self.back() + + +class RCSProxyClient(client.Client): + + def __init__(self, address, verbose = client.VERBOSE): + client.Client.__init__(self, address, verbose) + + +def test_server(): + import string + import sys + if sys.argv[1:]: + port = string.atoi(sys.argv[1]) + else: + port = 4127 + proxy = RCSProxyServer(('', port)) + proxy._serverloop() + + +def test(): + import sys + if not sys.argv[1:] or sys.argv[1] and sys.argv[1][0] in '0123456789': + test_server() + sys.exit(0) + proxy = RCSProxyLocal() + what = sys.argv[1] + if hasattr(proxy, what): + attr = getattr(proxy, what) + if callable(attr): + print apply(attr, tuple(sys.argv[2:])) + else: + print `attr` + else: + print "%s: no such attribute" % what + sys.exit(2) + + +if __name__ == '__main__': + test() diff --git a/Demo/pdist/README b/Demo/pdist/README new file mode 100644 index 0000000..738126d --- /dev/null +++ b/Demo/pdist/README @@ -0,0 +1,2 @@ +This directory contains various modules and classes that support +remote file system operations diff --git a/Demo/pdist/cvslib.py b/Demo/pdist/cvslib.py new file mode 100755 index 0000000..c98f0ba --- /dev/null +++ b/Demo/pdist/cvslib.py @@ -0,0 +1,186 @@ +"""Utilities to read and write CVS admin files (esp. CVS/Entries)""" + +import string +import os +import time + + +class Entry: + + """Class representing one (parsed) line from CVS/Entries""" + + def __init__(self, line): + words = string.splitfields(line, '/') + self.file = words[1] + self.rev = words[2] + dates = words[3] # ctime, mtime + if dates[:7] == 'Initial': + self.ctime = None + self.mtime = None + self.new = 1 + else: + self.ctime = unctime(dates[:24]) + self.mtime = unctime(dates[25:]) + self.new = 0 + self.extra = words[4] + self.sum = None + + def unparse(self): + if self.new: + dates = "Initial %s" % self.file + else: + dates = gmctime(self.ctime) + ' ' + gmctime(self.mtime) + return "/%s/%s/%s/%s/\n" % ( + self.file, + self.rev, + dates, + self.extra) + + def setsum(self, sum): + self.sum = sum + + def getsum(self): + return self.sum + + def sethexsum(self, hexsum): + self.setsum(unhexify(hexsum)) + + def gethexsum(self): + if self.sum: + return hexify(self.sum) + else: + return None + + +class CVS: + + """Class representing the contents of CVS/Entries (and CVS/Sums)""" + + def __init__(self): + self.readentries() + + def readentries(self): + self.entries = {} + f = self.cvsopen("Entries") + while 1: + line = f.readline() + if not line: break + e = Entry(line) + self.entries[e.file] = e + f.close() + + def readsums(self): + try: + f = self.cvsopen("Sums") + except IOError: + return + while 1: + line = f.readline() + if not line: break + words = string.split(line) + [file, rev, hexsum] = words + e = self.entries[file] + if e.rev == rev: + e.sethexsum(hexsum) + f.close() + + def writeentries(self): + f = self.cvsopen("Entries", 'w') + for file in self.keys(): + f.write(self.entries[file].unparse()) + f.close() + + def writesums(self): + if self.cvsexists("Sums"): + f = self.cvsopen("Sums", 'w') + else: + f = None + for file in self.keys(): + e = self.entries[file] + hexsum = e.gethexsum() + if hexsum: + if not f: + f = self.cvsopen("Sums", 'w') + f.write("%s %s %s\n" % (file, e.rev, hexsum)) + if f: + f.close() + + def keys(self): + keys = self.entries.keys() + keys.sort() + return keys + + def cvsexists(self, file): + file = os.path.join("CVS", file) + return os.path.exists(file) + + def cvsopen(self, file, mode = 'r'): + file = os.path.join("CVS", file) + if 'r' not in mode: + self.backup(file) + return open(file, mode) + + def backup(self, file): + if os.path.isfile(file): + bfile = file + '~' + os.rename(file, bfile) + + +hexify_format = '%02x' * 16 +def hexify(sum): + "Return a hex representation of a 16-byte string (e.g. an MD5 digest)" + return hexify_format % tuple(map(ord, sum)) + +def unhexify(hexsum): + "Return the original from a hexified string" + sum = '' + for i in range(0, len(hexsum), 2): + sum = sum + chr(string.atoi(hexsum[i:i+2], 16)) + return sum + + +unctime_monthmap = {} +def unctime(date): + if not unctime_monthmap: + months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', + 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'] + i = 0 + for m in months: + i = i+1 + unctime_monthmap[m] = i + words = string.split(date) # Day Mon DD HH:MM:SS YEAR + year = string.atoi(words[4]) + month = unctime_monthmap[words[1]] + day = string.atoi(words[2]) + [hh, mm, ss] = map(string.atoi, string.splitfields(words[3], ':')) + ss = ss - time.timezone + return time.mktime((year, month, day, hh, mm, ss, 0, 0, 0)) + +def gmctime(t): + return time.asctime(time.gmtime(t)) + +def test_unctime(): + now = int(time.time()) + t = time.gmtime(now) + at = time.asctime(t) + print 'GMT', now, at + print 'timezone', time.timezone + print 'local', time.ctime(now) + u = unctime(at) + print 'unctime()', u + gu = time.gmtime(u) + print '->', gu + print time.asctime(gu) + +def test(): + x = CVS() + keys = x.entries.keys() + keys.sort() + for file in keys: + e = x.entries[file] + print file, e.rev, gmctime(e.ctime), gmctime(e.mtime), e.extra, + print e.gethexsum() + + +if __name__ == "__main__": + test() diff --git a/Demo/pdist/rcvs.py b/Demo/pdist/rcvs.py new file mode 100755 index 0000000..eb43bb8 --- /dev/null +++ b/Demo/pdist/rcvs.py @@ -0,0 +1,192 @@ +from cvslib import CVS, Entry +import RCSProxy +import client +import md5 +import os +import string +import sys +import time +import fnmatch + + +ignored_patterns = ['*.pyc', '.*', '*~', '@*'] +def ignored(file): + if os.path.isdir(file): return 1 + for pat in ignored_patterns: + if fnmatch.fnmatch(file, pat): return 1 + return 0 + + +class PCVS(CVS): + + def __init__(self, proxy): + CVS.__init__(self) + self.proxy = proxy + self.readsums() + self.calcsums() + + def calcsums(self): + for file in self.keys(): + e = self.entries[file] + if not e.new and e.sum is None: + sum = self.proxy.sum((file, e.rev)) + e.setsum(sum) + + def fullcheck(self): + ok = 1 + for file in self.keys(): + e = self.entries[file] + if e.new: + if self.proxy.isfile(file): + print "%s: created by someone else!" + ok = 0 + continue + rrev = self.proxy.head(file) + if rrev != e.rev: + print "%s: out of date (%s vs. %s)" % \ + (file, e.rev, rrev) + ok = 0 + return ok + + def update(self): + for file in self.keys(): + e = self.entries[file] + if e.new: + print 'A', file + continue + rrev = self.proxy.head(file) + lsum = sumfile(file) + if rrev == e.rev: + if lsum == e.sum: + print '=', file + else: + print 'M', file + continue + if e.sum != lsum: + print "%s: conflict -- not updated" % file + continue + print "%s: getting ..." % file + data = self.proxy.get(file) + f = open(file, 'w') + f.write(data) + f.close() + nsum = md5.new(data).digest() + e.setsum(nsum) + e.rev = rrev + print 'U', file + self.writeentries() + self.writesums() + + def commit(self): + if not self.fullcheck(): + print "correct above errors first" + return + needed = [] + for file in self.keys(): + e = self.entries[file] + if e.new: + needed.append(file) + continue + lsum = sumfile(file) + if lsum != e.sum: + needed.append(file) + continue + if not needed: + print "no changes need committing" + return + message = raw_input("One-liner: ") + for file in needed: + print "%s: putting ..." % file + e = self.entries[file] + data = open(file).read() + self.proxy.put(file, data, message) + e.rev = self.proxy.head(file) + e.setsum(self.proxy.sum(file)) + # XXX get it? + mtime, ctime = os.stat(file)[-2:] + e.mtime = mtime + e.ctime = ctime + self.writeentries() + self.writesums() + + def report(self): + keys = self.keys() + files = os.listdir(os.curdir) + allfiles = files + for file in keys: + if file not in allfiles: + allfiles.append(file) + allfiles.sort() + for file in allfiles: + if file not in keys: + if not ignored(file): + print '?', file + continue + if file not in files: + print file, ': lost' + continue + e = self.entries[file] + if not os.path.exists(file): + print "%s: lost" % file + continue + if e.new: + print 'A', file + continue + lsum = sumfile(file) + rrev = self.proxy.head(file) + if rrev == e.rev: + if lsum == e.sum: + print '=', file + else: + print 'M', file + else: + if lsum == e.sum: + print 'U', file + else: + print 'C', file + + def add(self, file): + if self.entries.has_key(file): + print "%s: already known" + else: + self.entries[file] = Entry('/%s/0/Initial %s//\n' % + (file, file)) + + +def sumfile(file): + return md5.new(open(file).read()).digest() + + +def test(): + proxy = RCSProxy.RCSProxyClient(('voorn.cwi.nl', 4127)) + proxy.cd('/ufs/guido/voorn/python-RCS/Demo/pdist') + x = PCVS(proxy) + args = sys.argv[1:] + if args: + cmd = args[0] + files = args[1:] + if cmd == 'add': + if not files: + print "add needs at least one file argument" + else: + for file in files: + x.add(file) + x.writeentries() + elif cmd in ('update', 'up'): + if files: + print "updates wants no file arguments" + else: + x.update() + elif cmd in ('commit', 'com'): + if files: + print "commit wants no file arguments" + else: + x.commit() + else: + print "Unknown command", cmd + else: + x.report() + if sys.argv[1:]: x.writesums() + +if __name__ == "__main__": + test() diff --git a/Demo/pdist/rrcs.py b/Demo/pdist/rrcs.py new file mode 100755 index 0000000..74bce56 --- /dev/null +++ b/Demo/pdist/rrcs.py @@ -0,0 +1,174 @@ +#! /usr/local/bin/python + +import sys +import os +import getopt +import string +import md5 +import tempfile + +def main(): + sys.stdout = sys.stderr + try: + opts, rest = getopt.getopt(sys.argv[1:], 'h:p:qv') + if not rest: + raise getopt.error, "missing command" + cmd, rest = rest[0], rest[1:] + if not commands.has_key(cmd): + raise getopt.error, "unknown command" + coptset, func = commands[cmd] + copts, files = getopt.getopt(rest, coptset) + except getopt.error, msg: + print msg + print "usage: rrcs [options] command [options] [file] ..." + print "where command can be:" + print " ci|put # checkin the given files" + print " co|get # checkout" + print " info # print header info" + print " head # print revision of head branch" + print " list # list filename if valid" + print " log # print full log" + print " diff # diff rcs file and work file" + print "if no files are given, all remote rcs files are assumed" + sys.exit(2) + x = openclient(opts) + if not files: + files = x.listfiles() + for fn in files: + try: + func(x, copts, fn) + except (IOError, os.error), msg: + print "%s: %s" % (fn, msg) + +def openclient(opts): + import client + import RCSProxy + host = 'spam' + port = 4127 + verbose = client.VERBOSE + for o, a in opts: + if o == '-h': + host = a + if ':' in host: + i = string.find(host, ':') + host, p = host[:i], host[i+1:] + if p: + port = string.atoi(p) + if o == '-p': + port = string.atoi(a) + if o == '-v': + verbose = verbose + 1 + if o == '-q': + verbose = 0 + address = (host, port) + x = RCSProxy.RCSProxyClient(address, verbose) + return x + +def checkin(x, copts, fn): + f = open(fn) + data = f.read() + f.close() + new = not x.isfile(fn) + if not new and same(x, copts, fn, data): + print "%s: unchanged since last checkin" % fn + return + message = asklogmessage(new) + messages = x.put(fn, data, message) + if messages: + print messages + +def checkout(x, copts, fn): + data = x.get(fn) + f = open(fn, 'w') + f.write(data) + f.close() + +def info(x, copts, fn): + dict = x.info(fn) + keys = dict.keys() + keys.sort() + for key in keys: + print key + ':', dict[key] + print '='*70 + +def head(x, copts, fn): + head = x.head(fn) + print fn, head + +def list(x, copts, fn): + if x.isfile(fn): + print fn + +def log(x, copts, fn): + flags = '' + for o, a in copts: + flags = flags + ' ' + o + a + flags = flags[1:] + messages = x.log(fn, flags) + print messages + +def diff(x, copts, fn): + if same(x, copts, fn): + return + flags = '' + for o, a in copts: + flags = flags + ' ' + o + a + flags = flags[1:] + data = x.get(fn) + tfn = tempfile.mktemp() + try: + tf = open(tfn, 'w') + tf.write(data) + tf.close() + print 'diff %s -r%s %s' % (flags, x.head(fn), fn) + sts = os.system('diff %s %s %s' % (flags, tfn, fn)) + if sts: + print '='*70 + finally: + remove(tfn) + +def same(x, copts, fn, data = None): + if data is None: + f = open(fn) + data = f.read() + f.close() + lsum = md5.new(data).digest() + rsum = x.sum(fn) + return lsum == rsum + +def asklogmessage(new): + if new: + print "enter description,", + else: + print "enter log message,", + print "terminate with single '.' or end of file:" + if new: + print "NOTE: This is NOT the log message!" + message = "" + while 1: + sys.stderr.write(">> ") + sys.stderr.flush() + line = sys.stdin.readline() + if not line or line == '.\n': break + message = message + line + return message + +def remove(fn): + try: + os.unlink(fn) + except os.error: + pass + +commands = { + 'ci': ('', checkin), + 'put': ('', checkin), + 'co': ('', checkout), + 'get': ('', checkout), + 'info': ('', info), + 'head': ('', head), + 'list': ('', list), + 'log': ('bhLRtd:l:r:s:w:V:', log), + 'diff': ('c', diff), + } + +main() |