changes by Barry, e.g. font lock & email addresses
[python/dscho.git] / Demo / pdist / cvslib.py
blob96cc785d0daf8037ffc6ddd687210edd4c0da810
1 """Utilities for CVS administration."""
3 import string
4 import os
5 import time
6 import md5
7 import fnmatch
10 class File:
12 """Represent a file's status.
14 Instance variables:
16 file -- the filename (no slashes), None if uninitialized
17 lseen -- true if the data for the local file is up to date
18 eseen -- true if the data from the CVS/Entries entry is up to date
19 (this implies that the entry must be written back)
20 rseen -- true if the data for the remote file is up to date
21 proxy -- RCSProxy instance used to contact the server, or None
23 Note that lseen and rseen don't necessary mean that a local
24 or remote file *exists* -- they indicate that we've checked it.
25 However, eseen means that this instance corresponds to an
26 entry in the CVS/Entries file.
28 If lseen is true:
30 lsum -- checksum of the local file, None if no local file
31 lctime -- ctime of the local file, None if no local file
32 lmtime -- mtime of the local file, None if no local file
34 If eseen is true:
36 erev -- revision, None if this is a no revision (not '0')
37 enew -- true if this is an uncommitted added file
38 edeleted -- true if this is an uncommitted removed file
39 ectime -- ctime of last local file corresponding to erev
40 emtime -- mtime of last local file corresponding to erev
41 extra -- 5th string from CVS/Entries file
43 If rseen is true:
45 rrev -- revision of head, None if non-existent
46 rsum -- checksum of that revision, Non if non-existent
48 If eseen and rseen are both true:
50 esum -- checksum of revision erev, None if no revision
52 Note
53 """
55 def __init__(self, file = None):
56 if file and '/' in file:
57 raise ValueError, "no slash allowed in file"
58 self.file = file
59 self.lseen = self.eseen = self.rseen = 0
60 self.proxy = None
62 def __cmp__(self, other):
63 return cmp(self.file, other.file)
65 def getlocal(self):
66 try:
67 self.lmtime, self.lctime = os.stat(self.file)[-2:]
68 except os.error:
69 self.lmtime = self.lctime = self.lsum = None
70 else:
71 self.lsum = md5.md5(open(self.file).read()).digest()
72 self.lseen = 1
74 def getentry(self, line):
75 words = string.splitfields(line, '/')
76 if self.file and words[1] != self.file:
77 raise ValueError, "file name mismatch"
78 self.file = words[1]
79 self.erev = words[2]
80 self.edeleted = 0
81 self.enew = 0
82 self.ectime = self.emtime = None
83 if self.erev[:1] == '-':
84 self.edeleted = 1
85 self.erev = self.erev[1:]
86 if self.erev == '0':
87 self.erev = None
88 self.enew = 1
89 else:
90 dates = words[3]
91 self.ectime = unctime(dates[:24])
92 self.emtime = unctime(dates[25:])
93 self.extra = words[4]
94 if self.rseen:
95 self.getesum()
96 self.eseen = 1
98 def getremote(self, proxy = None):
99 if proxy:
100 self.proxy = proxy
101 try:
102 self.rrev = self.proxy.head(self.file)
103 except (os.error, IOError):
104 self.rrev = None
105 if self.rrev:
106 self.rsum = self.proxy.sum(self.file)
107 else:
108 self.rsum = None
109 if self.eseen:
110 self.getesum()
111 self.rseen = 1
113 def getesum(self):
114 if self.erev == self.rrev:
115 self.esum = self.rsum
116 elif self.erev:
117 name = (self.file, self.erev)
118 self.esum = self.proxy.sum(name)
119 else:
120 self.esum = None
122 def putentry(self):
123 """Return a line suitable for inclusion in CVS/Entries.
125 The returned line is terminated by a newline.
126 If no entry should be written for this file,
127 return "".
129 if not self.eseen:
130 return ""
132 rev = self.erev or '0'
133 if self.edeleted:
134 rev = '-' + rev
135 if self.enew:
136 dates = 'Initial ' + self.file
137 else:
138 dates = gmctime(self.ectime) + ' ' + \
139 gmctime(self.emtime)
140 return "/%s/%s/%s/%s/\n" % (
141 self.file,
142 rev,
143 dates,
144 self.extra)
146 def report(self):
147 print '-'*50
148 def r(key, repr=repr, self=self):
149 try:
150 value = repr(getattr(self, key))
151 except AttributeError:
152 value = "?"
153 print "%-15s:" % key, value
154 r("file")
155 if self.lseen:
156 r("lsum", hexify)
157 r("lctime", gmctime)
158 r("lmtime", gmctime)
159 if self.eseen:
160 r("erev")
161 r("enew")
162 r("edeleted")
163 r("ectime", gmctime)
164 r("emtime", gmctime)
165 if self.rseen:
166 r("rrev")
167 r("rsum", hexify)
168 if self.eseen:
169 r("esum", hexify)
172 class CVS:
174 """Represent the contents of a CVS admin file (and more).
176 Class variables:
178 FileClass -- the class to be instantiated for entries
179 (this should be derived from class File above)
180 IgnoreList -- shell patterns for local files to be ignored
182 Instance variables:
184 entries -- a dictionary containing File instances keyed by
185 their file name
186 proxy -- an RCSProxy instance, or None
189 FileClass = File
191 IgnoreList = ['.*', '@*', ',*', '*~', '*.o', '*.a', '*.so', '*.pyc']
193 def __init__(self):
194 self.entries = {}
195 self.proxy = None
197 def setproxy(self, proxy):
198 if proxy is self.proxy:
199 return
200 self.proxy = proxy
201 for e in self.entries.values():
202 e.rseen = 0
204 def getentries(self):
205 """Read the contents of CVS/Entries"""
206 self.entries = {}
207 f = self.cvsopen("Entries")
208 while 1:
209 line = f.readline()
210 if not line: break
211 e = self.FileClass()
212 e.getentry(line)
213 self.entries[e.file] = e
214 f.close()
216 def putentries(self):
217 """Write CVS/Entries back"""
218 f = self.cvsopen("Entries", 'w')
219 for e in self.values():
220 f.write(e.putentry())
221 f.close()
223 def getlocalfiles(self):
224 list = self.entries.keys()
225 addlist = os.listdir(os.curdir)
226 for name in addlist:
227 if name in list:
228 continue
229 if not self.ignored(name):
230 list.append(name)
231 list.sort()
232 for file in list:
233 try:
234 e = self.entries[file]
235 except KeyError:
236 e = self.entries[file] = self.FileClass(file)
237 e.getlocal()
239 def getremotefiles(self, proxy = None):
240 if proxy:
241 self.proxy = proxy
242 if not self.proxy:
243 raise RuntimeError, "no RCS proxy"
244 addlist = self.proxy.listfiles()
245 for file in addlist:
246 try:
247 e = self.entries[file]
248 except KeyError:
249 e = self.entries[file] = self.FileClass(file)
250 e.getremote(self.proxy)
252 def report(self):
253 for e in self.values():
254 e.report()
255 print '-'*50
257 def keys(self):
258 keys = self.entries.keys()
259 keys.sort()
260 return keys
262 def values(self):
263 def value(key, self=self):
264 return self.entries[key]
265 return map(value, self.keys())
267 def items(self):
268 def item(key, self=self):
269 return (key, self.entries[key])
270 return map(item, self.keys())
272 def cvsexists(self, file):
273 file = os.path.join("CVS", file)
274 return os.path.exists(file)
276 def cvsopen(self, file, mode = 'r'):
277 file = os.path.join("CVS", file)
278 if 'r' not in mode:
279 self.backup(file)
280 return open(file, mode)
282 def backup(self, file):
283 if os.path.isfile(file):
284 bfile = file + '~'
285 os.rename(file, bfile)
287 def ignored(self, file):
288 if os.path.isdir(file): return 1
289 for pat in self.IgnoreList:
290 if fnmatch.fnmatch(file, pat): return 1
291 return 0
294 # hexify and unhexify are useful to print MD5 checksums in hex format
296 hexify_format = '%02x' * 16
297 def hexify(sum):
298 "Return a hex representation of a 16-byte string (e.g. an MD5 digest)"
299 if sum is None:
300 return "None"
301 return hexify_format % tuple(map(ord, sum))
303 def unhexify(hexsum):
304 "Return the original from a hexified string"
305 if hexsum == "None":
306 return None
307 sum = ''
308 for i in range(0, len(hexsum), 2):
309 sum = sum + chr(string.atoi(hexsum[i:i+2], 16))
310 return sum
313 unctime_monthmap = {}
314 def unctime(date):
315 if date == "None": return None
316 if not unctime_monthmap:
317 months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
318 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
319 i = 0
320 for m in months:
321 i = i+1
322 unctime_monthmap[m] = i
323 words = string.split(date) # Day Mon DD HH:MM:SS YEAR
324 year = string.atoi(words[4])
325 month = unctime_monthmap[words[1]]
326 day = string.atoi(words[2])
327 [hh, mm, ss] = map(string.atoi, string.splitfields(words[3], ':'))
328 ss = ss - time.timezone
329 return time.mktime((year, month, day, hh, mm, ss, 0, 0, 0))
331 def gmctime(t):
332 if t is None: return "None"
333 return time.asctime(time.gmtime(t))
335 def test_unctime():
336 now = int(time.time())
337 t = time.gmtime(now)
338 at = time.asctime(t)
339 print 'GMT', now, at
340 print 'timezone', time.timezone
341 print 'local', time.ctime(now)
342 u = unctime(at)
343 print 'unctime()', u
344 gu = time.gmtime(u)
345 print '->', gu
346 print time.asctime(gu)
348 def test():
349 x = CVS()
350 x.getentries()
351 x.getlocalfiles()
352 ## x.report()
353 import rcsclient
354 proxy = rcsclient.openrcsclient()
355 x.getremotefiles(proxy)
356 x.report()
359 if __name__ == "__main__":
360 test()