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