+++ /dev/null
-"""Utilities for CVS administration."""\r
-\r
-import string\r
-import os\r
-import time\r
-import md5\r
-import fnmatch\r
-\r
-if not hasattr(time, 'timezone'):\r
- time.timezone = 0\r
-\r
-class File:\r
-\r
- """Represent a file's status.\r
-\r
- Instance variables:\r
-\r
- file -- the filename (no slashes), None if uninitialized\r
- lseen -- true if the data for the local file is up to date\r
- eseen -- true if the data from the CVS/Entries entry is up to date\r
- (this implies that the entry must be written back)\r
- rseen -- true if the data for the remote file is up to date\r
- proxy -- RCSProxy instance used to contact the server, or None\r
-\r
- Note that lseen and rseen don't necessary mean that a local\r
- or remote file *exists* -- they indicate that we've checked it.\r
- However, eseen means that this instance corresponds to an\r
- entry in the CVS/Entries file.\r
-\r
- If lseen is true:\r
-\r
- lsum -- checksum of the local file, None if no local file\r
- lctime -- ctime of the local file, None if no local file\r
- lmtime -- mtime of the local file, None if no local file\r
-\r
- If eseen is true:\r
-\r
- erev -- revision, None if this is a no revision (not '0')\r
- enew -- true if this is an uncommitted added file\r
- edeleted -- true if this is an uncommitted removed file\r
- ectime -- ctime of last local file corresponding to erev\r
- emtime -- mtime of last local file corresponding to erev\r
- extra -- 5th string from CVS/Entries file\r
-\r
- If rseen is true:\r
-\r
- rrev -- revision of head, None if non-existent\r
- rsum -- checksum of that revision, Non if non-existent\r
-\r
- If eseen and rseen are both true:\r
-\r
- esum -- checksum of revision erev, None if no revision\r
-\r
- Note\r
- """\r
-\r
- def __init__(self, file = None):\r
- if file and '/' in file:\r
- raise ValueError, "no slash allowed in file"\r
- self.file = file\r
- self.lseen = self.eseen = self.rseen = 0\r
- self.proxy = None\r
-\r
- def __cmp__(self, other):\r
- return cmp(self.file, other.file)\r
-\r
- def getlocal(self):\r
- try:\r
- self.lmtime, self.lctime = os.stat(self.file)[-2:]\r
- except os.error:\r
- self.lmtime = self.lctime = self.lsum = None\r
- else:\r
- self.lsum = md5.new(open(self.file).read()).digest()\r
- self.lseen = 1\r
-\r
- def getentry(self, line):\r
- words = string.splitfields(line, '/')\r
- if self.file and words[1] != self.file:\r
- raise ValueError, "file name mismatch"\r
- self.file = words[1]\r
- self.erev = words[2]\r
- self.edeleted = 0\r
- self.enew = 0\r
- self.ectime = self.emtime = None\r
- if self.erev[:1] == '-':\r
- self.edeleted = 1\r
- self.erev = self.erev[1:]\r
- if self.erev == '0':\r
- self.erev = None\r
- self.enew = 1\r
- else:\r
- dates = words[3]\r
- self.ectime = unctime(dates[:24])\r
- self.emtime = unctime(dates[25:])\r
- self.extra = words[4]\r
- if self.rseen:\r
- self.getesum()\r
- self.eseen = 1\r
-\r
- def getremote(self, proxy = None):\r
- if proxy:\r
- self.proxy = proxy\r
- try:\r
- self.rrev = self.proxy.head(self.file)\r
- except (os.error, IOError):\r
- self.rrev = None\r
- if self.rrev:\r
- self.rsum = self.proxy.sum(self.file)\r
- else:\r
- self.rsum = None\r
- if self.eseen:\r
- self.getesum()\r
- self.rseen = 1\r
-\r
- def getesum(self):\r
- if self.erev == self.rrev:\r
- self.esum = self.rsum\r
- elif self.erev:\r
- name = (self.file, self.erev)\r
- self.esum = self.proxy.sum(name)\r
- else:\r
- self.esum = None\r
-\r
- def putentry(self):\r
- """Return a line suitable for inclusion in CVS/Entries.\r
-\r
- The returned line is terminated by a newline.\r
- If no entry should be written for this file,\r
- return "".\r
- """\r
- if not self.eseen:\r
- return ""\r
-\r
- rev = self.erev or '0'\r
- if self.edeleted:\r
- rev = '-' + rev\r
- if self.enew:\r
- dates = 'Initial ' + self.file\r
- else:\r
- dates = gmctime(self.ectime) + ' ' + \\r
- gmctime(self.emtime)\r
- return "/%s/%s/%s/%s/\n" % (\r
- self.file,\r
- rev,\r
- dates,\r
- self.extra)\r
-\r
- def report(self):\r
- print '-'*50\r
- def r(key, repr=repr, self=self):\r
- try:\r
- value = repr(getattr(self, key))\r
- except AttributeError:\r
- value = "?"\r
- print "%-15s:" % key, value\r
- r("file")\r
- if self.lseen:\r
- r("lsum", hexify)\r
- r("lctime", gmctime)\r
- r("lmtime", gmctime)\r
- if self.eseen:\r
- r("erev")\r
- r("enew")\r
- r("edeleted")\r
- r("ectime", gmctime)\r
- r("emtime", gmctime)\r
- if self.rseen:\r
- r("rrev")\r
- r("rsum", hexify)\r
- if self.eseen:\r
- r("esum", hexify)\r
-\r
-\r
-class CVS:\r
-\r
- """Represent the contents of a CVS admin file (and more).\r
-\r
- Class variables:\r
-\r
- FileClass -- the class to be instantiated for entries\r
- (this should be derived from class File above)\r
- IgnoreList -- shell patterns for local files to be ignored\r
-\r
- Instance variables:\r
-\r
- entries -- a dictionary containing File instances keyed by\r
- their file name\r
- proxy -- an RCSProxy instance, or None\r
- """\r
-\r
- FileClass = File\r
-\r
- IgnoreList = ['.*', '@*', ',*', '*~', '*.o', '*.a', '*.so', '*.pyc']\r
-\r
- def __init__(self):\r
- self.entries = {}\r
- self.proxy = None\r
-\r
- def setproxy(self, proxy):\r
- if proxy is self.proxy:\r
- return\r
- self.proxy = proxy\r
- for e in self.entries.values():\r
- e.rseen = 0\r
-\r
- def getentries(self):\r
- """Read the contents of CVS/Entries"""\r
- self.entries = {}\r
- f = self.cvsopen("Entries")\r
- while 1:\r
- line = f.readline()\r
- if not line: break\r
- e = self.FileClass()\r
- e.getentry(line)\r
- self.entries[e.file] = e\r
- f.close()\r
-\r
- def putentries(self):\r
- """Write CVS/Entries back"""\r
- f = self.cvsopen("Entries", 'w')\r
- for e in self.values():\r
- f.write(e.putentry())\r
- f.close()\r
-\r
- def getlocalfiles(self):\r
- list = self.entries.keys()\r
- addlist = os.listdir(os.curdir)\r
- for name in addlist:\r
- if name in list:\r
- continue\r
- if not self.ignored(name):\r
- list.append(name)\r
- list.sort()\r
- for file in list:\r
- try:\r
- e = self.entries[file]\r
- except KeyError:\r
- e = self.entries[file] = self.FileClass(file)\r
- e.getlocal()\r
-\r
- def getremotefiles(self, proxy = None):\r
- if proxy:\r
- self.proxy = proxy\r
- if not self.proxy:\r
- raise RuntimeError, "no RCS proxy"\r
- addlist = self.proxy.listfiles()\r
- for file in addlist:\r
- try:\r
- e = self.entries[file]\r
- except KeyError:\r
- e = self.entries[file] = self.FileClass(file)\r
- e.getremote(self.proxy)\r
-\r
- def report(self):\r
- for e in self.values():\r
- e.report()\r
- print '-'*50\r
-\r
- def keys(self):\r
- keys = self.entries.keys()\r
- keys.sort()\r
- return keys\r
-\r
- def values(self):\r
- def value(key, self=self):\r
- return self.entries[key]\r
- return map(value, self.keys())\r
-\r
- def items(self):\r
- def item(key, self=self):\r
- return (key, self.entries[key])\r
- return map(item, self.keys())\r
-\r
- def cvsexists(self, file):\r
- file = os.path.join("CVS", file)\r
- return os.path.exists(file)\r
-\r
- def cvsopen(self, file, mode = 'r'):\r
- file = os.path.join("CVS", file)\r
- if 'r' not in mode:\r
- self.backup(file)\r
- return open(file, mode)\r
-\r
- def backup(self, file):\r
- if os.path.isfile(file):\r
- bfile = file + '~'\r
- try: os.unlink(bfile)\r
- except os.error: pass\r
- os.rename(file, bfile)\r
-\r
- def ignored(self, file):\r
- if os.path.isdir(file): return True\r
- for pat in self.IgnoreList:\r
- if fnmatch.fnmatch(file, pat): return True\r
- return False\r
-\r
-\r
-# hexify and unhexify are useful to print MD5 checksums in hex format\r
-\r
-hexify_format = '%02x' * 16\r
-def hexify(sum):\r
- "Return a hex representation of a 16-byte string (e.g. an MD5 digest)"\r
- if sum is None:\r
- return "None"\r
- return hexify_format % tuple(map(ord, sum))\r
-\r
-def unhexify(hexsum):\r
- "Return the original from a hexified string"\r
- if hexsum == "None":\r
- return None\r
- sum = ''\r
- for i in range(0, len(hexsum), 2):\r
- sum = sum + chr(string.atoi(hexsum[i:i+2], 16))\r
- return sum\r
-\r
-\r
-unctime_monthmap = {}\r
-def unctime(date):\r
- if date == "None": return None\r
- if not unctime_monthmap:\r
- months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',\r
- 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']\r
- i = 0\r
- for m in months:\r
- i = i+1\r
- unctime_monthmap[m] = i\r
- words = string.split(date) # Day Mon DD HH:MM:SS YEAR\r
- year = string.atoi(words[4])\r
- month = unctime_monthmap[words[1]]\r
- day = string.atoi(words[2])\r
- [hh, mm, ss] = map(string.atoi, string.splitfields(words[3], ':'))\r
- ss = ss - time.timezone\r
- return time.mktime((year, month, day, hh, mm, ss, 0, 0, 0))\r
-\r
-def gmctime(t):\r
- if t is None: return "None"\r
- return time.asctime(time.gmtime(t))\r
-\r
-def test_unctime():\r
- now = int(time.time())\r
- t = time.gmtime(now)\r
- at = time.asctime(t)\r
- print 'GMT', now, at\r
- print 'timezone', time.timezone\r
- print 'local', time.ctime(now)\r
- u = unctime(at)\r
- print 'unctime()', u\r
- gu = time.gmtime(u)\r
- print '->', gu\r
- print time.asctime(gu)\r
-\r
-def test():\r
- x = CVS()\r
- x.getentries()\r
- x.getlocalfiles()\r
-## x.report()\r
- import rcsclient\r
- proxy = rcsclient.openrcsclient()\r
- x.getremotefiles(proxy)\r
- x.report()\r
-\r
-\r
-if __name__ == "__main__":\r
- test()\r