blob: 0f689c498b4941cd29f7648c2a0ab2a3bc21486d [file] [log] [blame]
Guido van Rossum00bbf091995-04-27 21:24:16 +00001"""Utilities for CVS administration."""
Guido van Rossum5f07b841995-04-26 22:57:11 +00002
3import string
4import os
5import time
Guido van Rossum00bbf091995-04-27 21:24:16 +00006import md5
7import fnmatch
Guido van Rossum5f07b841995-04-26 22:57:11 +00008
Guido van Rossumf6d69281995-10-07 19:25:25 +00009if not hasattr(time, 'timezone'):
10 time.timezone = 0
Guido van Rossum5f07b841995-04-26 22:57:11 +000011
Guido van Rossum00bbf091995-04-27 21:24:16 +000012class File:
Guido van Rossum5f07b841995-04-26 22:57:11 +000013
Guido van Rossum00bbf091995-04-27 21:24:16 +000014 """Represent a file's status.
15
16 Instance variables:
17
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)
Guido van Rossum00bbf091995-04-27 21:24:16 +000022 rseen -- true if the data for the remote file is up to date
Guido van Rossum1c653bd1995-05-01 20:06:06 +000023 proxy -- RCSProxy instance used to contact the server, or None
Guido van Rossum00bbf091995-04-27 21:24:16 +000024
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.
29
30 If lseen is true:
Guido van Rossum5f07b841995-04-26 22:57:11 +000031
Guido van Rossum00bbf091995-04-27 21:24:16 +000032 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
35
36 If eseen is true:
37
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
Guido van Rossum29cd62b1995-06-21 01:00:46 +000043 extra -- 5th string from CVS/Entries file
Guido van Rossum00bbf091995-04-27 21:24:16 +000044
45 If rseen is true:
46
Guido van Rossum00bbf091995-04-27 21:24:16 +000047 rrev -- revision of head, None if non-existent
48 rsum -- checksum of that revision, Non if non-existent
49
50 If eseen and rseen are both true:
51
52 esum -- checksum of revision erev, None if no revision
53
54 Note
55 """
56
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
Guido van Rossum1c653bd1995-05-01 20:06:06 +000062 self.proxy = None
63
64 def __cmp__(self, other):
65 return cmp(self.file, other.file)
Guido van Rossum00bbf091995-04-27 21:24:16 +000066
67 def getlocal(self):
68 try:
69 self.lmtime, self.lctime = os.stat(self.file)[-2:]
70 except os.error:
Guido van Rossum1c653bd1995-05-01 20:06:06 +000071 self.lmtime = self.lctime = self.lsum = None
Guido van Rossum00bbf091995-04-27 21:24:16 +000072 else:
Andrew M. Kuchlinga90a3b42002-04-17 21:53:21 +000073 self.lsum = md5.new(open(self.file).read()).digest()
Guido van Rossum00bbf091995-04-27 21:24:16 +000074 self.lseen = 1
75
76 def getentry(self, line):
Guido van Rossum5f07b841995-04-26 22:57:11 +000077 words = string.splitfields(line, '/')
Guido van Rossum00bbf091995-04-27 21:24:16 +000078 if self.file and words[1] != self.file:
79 raise ValueError, "file name mismatch"
Guido van Rossum5f07b841995-04-26 22:57:11 +000080 self.file = words[1]
Guido van Rossum00bbf091995-04-27 21:24:16 +000081 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
Guido van Rossum5f07b841995-04-26 22:57:11 +000091 else:
Guido van Rossum00bbf091995-04-27 21:24:16 +000092 dates = words[3]
93 self.ectime = unctime(dates[:24])
94 self.emtime = unctime(dates[25:])
Guido van Rossum5f07b841995-04-26 22:57:11 +000095 self.extra = words[4]
Guido van Rossum00bbf091995-04-27 21:24:16 +000096 if self.rseen:
97 self.getesum()
98 self.eseen = 1
99
Guido van Rossum1c653bd1995-05-01 20:06:06 +0000100 def getremote(self, proxy = None):
101 if proxy:
102 self.proxy = proxy
Guido van Rossum00bbf091995-04-27 21:24:16 +0000103 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)
Guido van Rossum5f07b841995-04-26 22:57:11 +0000109 else:
Guido van Rossum00bbf091995-04-27 21:24:16 +0000110 self.rsum = None
111 if self.eseen:
112 self.getesum()
113 self.rseen = 1
114
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
123
124 def putentry(self):
125 """Return a line suitable for inclusion in CVS/Entries.
126
127 The returned line is terminated by a newline.
128 If no entry should be written for this file,
129 return "".
130 """
131 if not self.eseen:
132 return ""
133
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)
Guido van Rossum5f07b841995-04-26 22:57:11 +0000142 return "/%s/%s/%s/%s/\n" % (
143 self.file,
Guido van Rossum00bbf091995-04-27 21:24:16 +0000144 rev,
Guido van Rossum5f07b841995-04-26 22:57:11 +0000145 dates,
146 self.extra)
Guido van Rossum00bbf091995-04-27 21:24:16 +0000147
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")
Guido van Rossum81be17b1995-04-28 14:33:08 +0000163 r("enew")
164 r("edeleted")
Guido van Rossum00bbf091995-04-27 21:24:16 +0000165 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)
Guido van Rossum5f07b841995-04-26 22:57:11 +0000172
173
174class CVS:
Guido van Rossum00bbf091995-04-27 21:24:16 +0000175
176 """Represent the contents of a CVS admin file (and more).
Guido van Rossum5f07b841995-04-26 22:57:11 +0000177
Guido van Rossum00bbf091995-04-27 21:24:16 +0000178 Class variables:
179
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
183
184 Instance variables:
185
186 entries -- a dictionary containing File instances keyed by
187 their file name
188 proxy -- an RCSProxy instance, or None
189 """
190
191 FileClass = File
192
193 IgnoreList = ['.*', '@*', ',*', '*~', '*.o', '*.a', '*.so', '*.pyc']
Guido van Rossum5f07b841995-04-26 22:57:11 +0000194
195 def __init__(self):
Guido van Rossum00bbf091995-04-27 21:24:16 +0000196 self.entries = {}
197 self.proxy = None
Guido van Rossum5f07b841995-04-26 22:57:11 +0000198
Guido van Rossum00bbf091995-04-27 21:24:16 +0000199 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
205
206 def getentries(self):
207 """Read the contents of CVS/Entries"""
Guido van Rossum5f07b841995-04-26 22:57:11 +0000208 self.entries = {}
209 f = self.cvsopen("Entries")
210 while 1:
211 line = f.readline()
212 if not line: break
Guido van Rossum00bbf091995-04-27 21:24:16 +0000213 e = self.FileClass()
214 e.getentry(line)
Guido van Rossum5f07b841995-04-26 22:57:11 +0000215 self.entries[e.file] = e
216 f.close()
217
Guido van Rossum00bbf091995-04-27 21:24:16 +0000218 def putentries(self):
219 """Write CVS/Entries back"""
Guido van Rossum5f07b841995-04-26 22:57:11 +0000220 f = self.cvsopen("Entries", 'w')
Guido van Rossum00bbf091995-04-27 21:24:16 +0000221 for e in self.values():
Guido van Rossumb07d7291995-04-28 15:25:44 +0000222 f.write(e.putentry())
Guido van Rossum5f07b841995-04-26 22:57:11 +0000223 f.close()
Guido van Rossum00bbf091995-04-27 21:24:16 +0000224
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()
240
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)
253
254 def report(self):
255 for e in self.values():
256 e.report()
257 print '-'*50
Guido van Rossum5f07b841995-04-26 22:57:11 +0000258
259 def keys(self):
260 keys = self.entries.keys()
261 keys.sort()
262 return keys
263
Guido van Rossum00bbf091995-04-27 21:24:16 +0000264 def values(self):
265 def value(key, self=self):
266 return self.entries[key]
267 return map(value, self.keys())
268
269 def items(self):
270 def item(key, self=self):
271 return (key, self.entries[key])
272 return map(item, self.keys())
273
Guido van Rossum5f07b841995-04-26 22:57:11 +0000274 def cvsexists(self, file):
275 file = os.path.join("CVS", file)
276 return os.path.exists(file)
277
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)
283
284 def backup(self, file):
285 if os.path.isfile(file):
286 bfile = file + '~'
Guido van Rossumf6d69281995-10-07 19:25:25 +0000287 try: os.unlink(bfile)
288 except os.error: pass
Guido van Rossum5f07b841995-04-26 22:57:11 +0000289 os.rename(file, bfile)
290
Guido van Rossum00bbf091995-04-27 21:24:16 +0000291 def ignored(self, file):
Tim Petersbc0e9102002-04-04 22:55:58 +0000292 if os.path.isdir(file): return True
Guido van Rossum00bbf091995-04-27 21:24:16 +0000293 for pat in self.IgnoreList:
Tim Petersbc0e9102002-04-04 22:55:58 +0000294 if fnmatch.fnmatch(file, pat): return True
Guido van Rossum72118e52002-04-04 23:03:47 +0000295 return False
Guido van Rossum00bbf091995-04-27 21:24:16 +0000296
297
298# hexify and unhexify are useful to print MD5 checksums in hex format
Guido van Rossum5f07b841995-04-26 22:57:11 +0000299
300hexify_format = '%02x' * 16
301def hexify(sum):
302 "Return a hex representation of a 16-byte string (e.g. an MD5 digest)"
Guido van Rossum00bbf091995-04-27 21:24:16 +0000303 if sum is None:
304 return "None"
Guido van Rossum5f07b841995-04-26 22:57:11 +0000305 return hexify_format % tuple(map(ord, sum))
306
307def unhexify(hexsum):
308 "Return the original from a hexified string"
Guido van Rossum00bbf091995-04-27 21:24:16 +0000309 if hexsum == "None":
310 return None
Guido van Rossum5f07b841995-04-26 22:57:11 +0000311 sum = ''
312 for i in range(0, len(hexsum), 2):
313 sum = sum + chr(string.atoi(hexsum[i:i+2], 16))
314 return sum
315
316
317unctime_monthmap = {}
318def unctime(date):
Guido van Rossum00bbf091995-04-27 21:24:16 +0000319 if date == "None": return None
Guido van Rossum5f07b841995-04-26 22:57:11 +0000320 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))
334
335def gmctime(t):
Guido van Rossum00bbf091995-04-27 21:24:16 +0000336 if t is None: return "None"
Guido van Rossum5f07b841995-04-26 22:57:11 +0000337 return time.asctime(time.gmtime(t))
338
339def 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)
351
352def test():
353 x = CVS()
Guido van Rossum00bbf091995-04-27 21:24:16 +0000354 x.getentries()
355 x.getlocalfiles()
356## x.report()
357 import rcsclient
358 proxy = rcsclient.openrcsclient()
359 x.getremotefiles(proxy)
360 x.report()
Guido van Rossum5f07b841995-04-26 22:57:11 +0000361
362
363if __name__ == "__main__":
364 test()