+++ /dev/null
-#! /usr/bin/env python\r
-\r
-"""Remote CVS -- command line interface"""\r
-\r
-# XXX To do:\r
-#\r
-# Bugs:\r
-# - if the remote file is deleted, "rcvs update" will fail\r
-#\r
-# Functionality:\r
-# - cvs rm\r
-# - descend into directories (alraedy done for update)\r
-# - conflict resolution\r
-# - other relevant commands?\r
-# - branches\r
-#\r
-# - Finesses:\r
-# - retain file mode's x bits\r
-# - complain when "nothing known about filename"\r
-# - edit log message the way CVS lets you edit it\r
-# - cvs diff -rREVA -rREVB\r
-# - send mail the way CVS sends it\r
-#\r
-# Performance:\r
-# - cache remote checksums (for every revision ever seen!)\r
-# - translate symbolic revisions to numeric revisions\r
-#\r
-# Reliability:\r
-# - remote locking\r
-#\r
-# Security:\r
-# - Authenticated RPC?\r
-\r
-\r
-from cvslib import CVS, File\r
-import md5\r
-import os\r
-import string\r
-import sys\r
-from cmdfw import CommandFrameWork\r
-\r
-\r
-DEF_LOCAL = 1 # Default -l\r
-\r
-\r
-class MyFile(File):\r
-\r
- def action(self):\r
- """Return a code indicating the update status of this file.\r
-\r
- The possible return values are:\r
-\r
- '=' -- everything's fine\r
- '0' -- file doesn't exist anywhere\r
- '?' -- exists locally only\r
- 'A' -- new locally\r
- 'R' -- deleted locally\r
- 'U' -- changed remotely, no changes locally\r
- (includes new remotely or deleted remotely)\r
- 'M' -- changed locally, no changes remotely\r
- 'C' -- conflict: changed locally as well as remotely\r
- (includes cases where the file has been added\r
- or removed locally and remotely)\r
- 'D' -- deleted remotely\r
- 'N' -- new remotely\r
- 'r' -- get rid of entry\r
- 'c' -- create entry\r
- 'u' -- update entry\r
-\r
- (and probably others :-)\r
- """\r
- if not self.lseen:\r
- self.getlocal()\r
- if not self.rseen:\r
- self.getremote()\r
- if not self.eseen:\r
- if not self.lsum:\r
- if not self.rsum: return '0' # Never heard of\r
- else:\r
- return 'N' # New remotely\r
- else: # self.lsum\r
- if not self.rsum: return '?' # Local only\r
- # Local and remote, but no entry\r
- if self.lsum == self.rsum:\r
- return 'c' # Restore entry only\r
- else: return 'C' # Real conflict\r
- else: # self.eseen\r
- if not self.lsum:\r
- if self.edeleted:\r
- if self.rsum: return 'R' # Removed\r
- else: return 'r' # Get rid of entry\r
- else: # not self.edeleted\r
- if self.rsum:\r
- print "warning:",\r
- print self.file,\r
- print "was lost"\r
- return 'U'\r
- else: return 'r' # Get rid of entry\r
- else: # self.lsum\r
- if not self.rsum:\r
- if self.enew: return 'A' # New locally\r
- else: return 'D' # Deleted remotely\r
- else: # self.rsum\r
- if self.enew:\r
- if self.lsum == self.rsum:\r
- return 'u'\r
- else:\r
- return 'C'\r
- if self.lsum == self.esum:\r
- if self.esum == self.rsum:\r
- return '='\r
- else:\r
- return 'U'\r
- elif self.esum == self.rsum:\r
- return 'M'\r
- elif self.lsum == self.rsum:\r
- return 'u'\r
- else:\r
- return 'C'\r
-\r
- def update(self):\r
- code = self.action()\r
- if code == '=': return\r
- print code, self.file\r
- if code in ('U', 'N'):\r
- self.get()\r
- elif code == 'C':\r
- print "%s: conflict resolution not yet implemented" % \\r
- self.file\r
- elif code == 'D':\r
- remove(self.file)\r
- self.eseen = 0\r
- elif code == 'r':\r
- self.eseen = 0\r
- elif code in ('c', 'u'):\r
- self.eseen = 1\r
- self.erev = self.rrev\r
- self.enew = 0\r
- self.edeleted = 0\r
- self.esum = self.rsum\r
- self.emtime, self.ectime = os.stat(self.file)[-2:]\r
- self.extra = ''\r
-\r
- def commit(self, message = ""):\r
- code = self.action()\r
- if code in ('A', 'M'):\r
- self.put(message)\r
- return 1\r
- elif code == 'R':\r
- print "%s: committing removes not yet implemented" % \\r
- self.file\r
- elif code == 'C':\r
- print "%s: conflict resolution not yet implemented" % \\r
- self.file\r
-\r
- def diff(self, opts = []):\r
- self.action() # To update lseen, rseen\r
- flags = ''\r
- rev = self.rrev\r
- # XXX should support two rev options too!\r
- for o, a in opts:\r
- if o == '-r':\r
- rev = a\r
- else:\r
- flags = flags + ' ' + o + a\r
- if rev == self.rrev and self.lsum == self.rsum:\r
- return\r
- flags = flags[1:]\r
- fn = self.file\r
- data = self.proxy.get((fn, rev))\r
- sum = md5.new(data).digest()\r
- if self.lsum == sum:\r
- return\r
- import tempfile\r
- tf = tempfile.NamedTemporaryFile()\r
- tf.write(data)\r
- tf.flush()\r
- print 'diff %s -r%s %s' % (flags, rev, fn)\r
- sts = os.system('diff %s %s %s' % (flags, tf.name, fn))\r
- if sts:\r
- print '='*70\r
-\r
- def commitcheck(self):\r
- return self.action() != 'C'\r
-\r
- def put(self, message = ""):\r
- print "Checking in", self.file, "..."\r
- data = open(self.file).read()\r
- if not self.enew:\r
- self.proxy.lock(self.file)\r
- messages = self.proxy.put(self.file, data, message)\r
- if messages:\r
- print messages\r
- self.setentry(self.proxy.head(self.file), self.lsum)\r
-\r
- def get(self):\r
- data = self.proxy.get(self.file)\r
- f = open(self.file, 'w')\r
- f.write(data)\r
- f.close()\r
- self.setentry(self.rrev, self.rsum)\r
-\r
- def log(self, otherflags):\r
- print self.proxy.log(self.file, otherflags)\r
-\r
- def add(self):\r
- self.eseen = 0 # While we're hacking...\r
- self.esum = self.lsum\r
- self.emtime, self.ectime = 0, 0\r
- self.erev = ''\r
- self.enew = 1\r
- self.edeleted = 0\r
- self.eseen = 1 # Done\r
- self.extra = ''\r
-\r
- def setentry(self, erev, esum):\r
- self.eseen = 0 # While we're hacking...\r
- self.esum = esum\r
- self.emtime, self.ectime = os.stat(self.file)[-2:]\r
- self.erev = erev\r
- self.enew = 0\r
- self.edeleted = 0\r
- self.eseen = 1 # Done\r
- self.extra = ''\r
-\r
-\r
-SENDMAIL = "/usr/lib/sendmail -t"\r
-MAILFORM = """To: %s\r
-Subject: CVS changes: %s\r
-\r
-...Message from rcvs...\r
-\r
-Committed files:\r
- %s\r
-\r
-Log message:\r
- %s\r
-"""\r
-\r
-\r
-class RCVS(CVS):\r
-\r
- FileClass = MyFile\r
-\r
- def __init__(self):\r
- CVS.__init__(self)\r
-\r
- def update(self, files):\r
- for e in self.whichentries(files, 1):\r
- e.update()\r
-\r
- def commit(self, files, message = ""):\r
- list = self.whichentries(files)\r
- if not list: return\r
- ok = 1\r
- for e in list:\r
- if not e.commitcheck():\r
- ok = 0\r
- if not ok:\r
- print "correct above errors first"\r
- return\r
- if not message:\r
- message = raw_input("One-liner: ")\r
- committed = []\r
- for e in list:\r
- if e.commit(message):\r
- committed.append(e.file)\r
- self.mailinfo(committed, message)\r
-\r
- def mailinfo(self, files, message = ""):\r
- towhom = "sjoerd@cwi.nl, jack@cwi.nl" # XXX\r
- mailtext = MAILFORM % (towhom, string.join(files),\r
- string.join(files), message)\r
- print '-'*70\r
- print mailtext\r
- print '-'*70\r
- ok = raw_input("OK to mail to %s? " % towhom)\r
- if string.lower(string.strip(ok)) in ('y', 'ye', 'yes'):\r
- p = os.popen(SENDMAIL, "w")\r
- p.write(mailtext)\r
- sts = p.close()\r
- if sts:\r
- print "Sendmail exit status %s" % str(sts)\r
- else:\r
- print "Mail sent."\r
- else:\r
- print "No mail sent."\r
-\r
- def report(self, files):\r
- for e in self.whichentries(files):\r
- e.report()\r
-\r
- def diff(self, files, opts):\r
- for e in self.whichentries(files):\r
- e.diff(opts)\r
-\r
- def add(self, files):\r
- if not files:\r
- raise RuntimeError, "'cvs add' needs at least one file"\r
- list = []\r
- for e in self.whichentries(files, 1):\r
- e.add()\r
-\r
- def rm(self, files):\r
- if not files:\r
- raise RuntimeError, "'cvs rm' needs at least one file"\r
- raise RuntimeError, "'cvs rm' not yet imlemented"\r
-\r
- def log(self, files, opts):\r
- flags = ''\r
- for o, a in opts:\r
- flags = flags + ' ' + o + a\r
- for e in self.whichentries(files):\r
- e.log(flags)\r
-\r
- def whichentries(self, files, localfilestoo = 0):\r
- if files:\r
- list = []\r
- for file in files:\r
- if self.entries.has_key(file):\r
- e = self.entries[file]\r
- else:\r
- e = self.FileClass(file)\r
- self.entries[file] = e\r
- list.append(e)\r
- else:\r
- list = self.entries.values()\r
- for file in self.proxy.listfiles():\r
- if self.entries.has_key(file):\r
- continue\r
- e = self.FileClass(file)\r
- self.entries[file] = e\r
- list.append(e)\r
- if localfilestoo:\r
- for file in os.listdir(os.curdir):\r
- if not self.entries.has_key(file) \\r
- and not self.ignored(file):\r
- e = self.FileClass(file)\r
- self.entries[file] = e\r
- list.append(e)\r
- list.sort()\r
- if self.proxy:\r
- for e in list:\r
- if e.proxy is None:\r
- e.proxy = self.proxy\r
- return list\r
-\r
-\r
-class rcvs(CommandFrameWork):\r
-\r
- GlobalFlags = 'd:h:p:qvL'\r
- UsageMessage = \\r
-"usage: rcvs [-d directory] [-h host] [-p port] [-q] [-v] [subcommand arg ...]"\r
- PostUsageMessage = \\r
- "If no subcommand is given, the status of all files is listed"\r
-\r
- def __init__(self):\r
- """Constructor."""\r
- CommandFrameWork.__init__(self)\r
- self.proxy = None\r
- self.cvs = RCVS()\r
-\r
- def close(self):\r
- if self.proxy:\r
- self.proxy._close()\r
- self.proxy = None\r
-\r
- def recurse(self):\r
- self.close()\r
- names = os.listdir(os.curdir)\r
- for name in names:\r
- if name == os.curdir or name == os.pardir:\r
- continue\r
- if name == "CVS":\r
- continue\r
- if not os.path.isdir(name):\r
- continue\r
- if os.path.islink(name):\r
- continue\r
- print "--- entering subdirectory", name, "---"\r
- os.chdir(name)\r
- try:\r
- if os.path.isdir("CVS"):\r
- self.__class__().run()\r
- else:\r
- self.recurse()\r
- finally:\r
- os.chdir(os.pardir)\r
- print "--- left subdirectory", name, "---"\r
-\r
- def options(self, opts):\r
- self.opts = opts\r
-\r
- def ready(self):\r
- import rcsclient\r
- self.proxy = rcsclient.openrcsclient(self.opts)\r
- self.cvs.setproxy(self.proxy)\r
- self.cvs.getentries()\r
-\r
- def default(self):\r
- self.cvs.report([])\r
-\r
- def do_report(self, opts, files):\r
- self.cvs.report(files)\r
-\r
- def do_update(self, opts, files):\r
- """update [-l] [-R] [file] ..."""\r
- local = DEF_LOCAL\r
- for o, a in opts:\r
- if o == '-l': local = 1\r
- if o == '-R': local = 0\r
- self.cvs.update(files)\r
- self.cvs.putentries()\r
- if not local and not files:\r
- self.recurse()\r
- flags_update = '-lR'\r
- do_up = do_update\r
- flags_up = flags_update\r
-\r
- def do_commit(self, opts, files):\r
- """commit [-m message] [file] ..."""\r
- message = ""\r
- for o, a in opts:\r
- if o == '-m': message = a\r
- self.cvs.commit(files, message)\r
- self.cvs.putentries()\r
- flags_commit = 'm:'\r
- do_com = do_commit\r
- flags_com = flags_commit\r
-\r
- def do_diff(self, opts, files):\r
- """diff [difflags] [file] ..."""\r
- self.cvs.diff(files, opts)\r
- flags_diff = 'cbitwcefhnlr:sD:S:'\r
- do_dif = do_diff\r
- flags_dif = flags_diff\r
-\r
- def do_add(self, opts, files):\r
- """add file ..."""\r
- if not files:\r
- print "'rcvs add' requires at least one file"\r
- return\r
- self.cvs.add(files)\r
- self.cvs.putentries()\r
-\r
- def do_remove(self, opts, files):\r
- """remove file ..."""\r
- if not files:\r
- print "'rcvs remove' requires at least one file"\r
- return\r
- self.cvs.remove(files)\r
- self.cvs.putentries()\r
- do_rm = do_remove\r
-\r
- def do_log(self, opts, files):\r
- """log [rlog-options] [file] ..."""\r
- self.cvs.log(files, opts)\r
- flags_log = 'bhLNRtd:s:V:r:'\r
-\r
-\r
-def remove(fn):\r
- try:\r
- os.unlink(fn)\r
- except os.error:\r
- pass\r
-\r
-\r
-def main():\r
- r = rcvs()\r
- try:\r
- r.run()\r
- finally:\r
- r.close()\r
-\r
-\r
-if __name__ == "__main__":\r
- main()\r