blob: 96cc785d0daf8037ffc6ddd687210edd4c0da810 [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
9
Guido van Rossum00bbf091995-04-27 21:24:16 +000010class File:
Guido van Rossum5f07b841995-04-26 22:57:11 +000011
Guido van Rossum00bbf091995-04-27 21:24:16 +000012 """Represent a file's status.
13
14 Instance variables:
15
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)
Guido van Rossum00bbf091995-04-27 21:24:16 +000020 rseen -- true if the data for the remote file is up to date
Guido van Rossum1c653bd1995-05-01 20:06:06 +000021 proxy -- RCSProxy instance used to contact the server, or None
Guido van Rossum00bbf091995-04-27 21:24:16 +000022
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.
27
28 If lseen is true:
Guido van Rossum5f07b841995-04-26 22:57:11 +000029
Guido van Rossum00bbf091995-04-27 21:24:16 +000030 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
33
34 If eseen is true:
35
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
Guido van Rossum29cd62b1995-06-21 01:00:46 +000041 extra -- 5th string from CVS/Entries file
Guido van Rossum00bbf091995-04-27 21:24:16 +000042
43 If rseen is true:
44
Guido van Rossum00bbf091995-04-27 21:24:16 +000045 rrev -- revision of head, None if non-existent
46 rsum -- checksum of that revision, Non if non-existent
47
48 If eseen and rseen are both true:
49
50 esum -- checksum of revision erev, None if no revision
51
52 Note
53 """
54
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
Guido van Rossum1c653bd1995-05-01 20:06:06 +000060 self.proxy = None
61
62 def __cmp__(self, other):
63 return cmp(self.file, other.file)
Guido van Rossum00bbf091995-04-27 21:24:16 +000064
65 def getlocal(self):
66 try:
67 self.lmtime, self.lctime = os.stat(self.file)[-2:]
68 except os.error:
Guido van Rossum1c653bd1995-05-01 20:06:06 +000069 self.lmtime = self.lctime = self.lsum = None
Guido van Rossum00bbf091995-04-27 21:24:16 +000070 else:
Guido van Rossum1c653bd1995-05-01 20:06:06 +000071 self.lsum = md5.md5(open(self.file).read()).digest()
Guido van Rossum00bbf091995-04-27 21:24:16 +000072 self.lseen = 1
73
74 def getentry(self, line):
Guido van Rossum5f07b841995-04-26 22:57:11 +000075 words = string.splitfields(line, '/')
Guido van Rossum00bbf091995-04-27 21:24:16 +000076 if self.file and words[1] != self.file:
77 raise ValueError, "file name mismatch"
Guido van Rossum5f07b841995-04-26 22:57:11 +000078 self.file = words[1]
Guido van Rossum00bbf091995-04-27 21:24:16 +000079 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
Guido van Rossum5f07b841995-04-26 22:57:11 +000089 else:
Guido van Rossum00bbf091995-04-27 21:24:16 +000090 dates = words[3]
91 self.ectime = unctime(dates[:24])
92 self.emtime = unctime(dates[25:])
Guido van Rossum5f07b841995-04-26 22:57:11 +000093 self.extra = words[4]
Guido van Rossum00bbf091995-04-27 21:24:16 +000094 if self.rseen:
95 self.getesum()
96 self.eseen = 1
97
Guido van Rossum1c653bd1995-05-01 20:06:06 +000098 def getremote(self, proxy = None):
99 if proxy:
100 self.proxy = proxy
Guido van Rossum00bbf091995-04-27 21:24:16 +0000101 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)
Guido van Rossum5f07b841995-04-26 22:57:11 +0000107 else:
Guido van Rossum00bbf091995-04-27 21:24:16 +0000108 self.rsum = None
109 if self.eseen:
110 self.getesum()
111 self.rseen = 1
112
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
121
122 def putentry(self):
123 """Return a line suitable for inclusion in CVS/Entries.
124
125 The returned line is terminated by a newline.
126 If no entry should be written for this file,
127 return "".
128 """
129 if not self.eseen:
130 return ""
131
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)
Guido van Rossum5f07b841995-04-26 22:57:11 +0000140 return "/%s/%s/%s/%s/\n" % (
141 self.file,
Guido van Rossum00bbf091995-04-27 21:24:16 +0000142 rev,
Guido van Rossum5f07b841995-04-26 22:57:11 +0000143 dates,
144 self.extra)
Guido van Rossum00bbf091995-04-27 21:24:16 +0000145
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")
Guido van Rossum81be17b1995-04-28 14:33:08 +0000161 r("enew")
162 r("edeleted")
Guido van Rossum00bbf091995-04-27 21:24:16 +0000163 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)
Guido van Rossum5f07b841995-04-26 22:57:11 +0000170
171
172class CVS:
Guido van Rossum00bbf091995-04-27 21:24:16 +0000173
174 """Represent the contents of a CVS admin file (and more).
Guido van Rossum5f07b841995-04-26 22:57:11 +0000175
Guido van Rossum00bbf091995-04-27 21:24:16 +0000176 Class variables:
177
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
181
182 Instance variables:
183
184 entries -- a dictionary containing File instances keyed by
185 their file name
186 proxy -- an RCSProxy instance, or None
187 """
188
189 FileClass = File
190
191 IgnoreList = ['.*', '@*', ',*', '*~', '*.o', '*.a', '*.so', '*.pyc']
Guido van Rossum5f07b841995-04-26 22:57:11 +0000192
193 def __init__(self):
Guido van Rossum00bbf091995-04-27 21:24:16 +0000194 self.entries = {}
195 self.proxy = None
Guido van Rossum5f07b841995-04-26 22:57:11 +0000196
Guido van Rossum00bbf091995-04-27 21:24:16 +0000197 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
203
204 def getentries(self):
205 """Read the contents of CVS/Entries"""
Guido van Rossum5f07b841995-04-26 22:57:11 +0000206 self.entries = {}
207 f = self.cvsopen("Entries")
208 while 1:
209 line = f.readline()
210 if not line: break
Guido van Rossum00bbf091995-04-27 21:24:16 +0000211 e = self.FileClass()
212 e.getentry(line)
Guido van Rossum5f07b841995-04-26 22:57:11 +0000213 self.entries[e.file] = e
214 f.close()
215
Guido van Rossum00bbf091995-04-27 21:24:16 +0000216 def putentries(self):
217 """Write CVS/Entries back"""
Guido van Rossum5f07b841995-04-26 22:57:11 +0000218 f = self.cvsopen("Entries", 'w')
Guido van Rossum00bbf091995-04-27 21:24:16 +0000219 for e in self.values():
Guido van Rossumb07d7291995-04-28 15:25:44 +0000220 f.write(e.putentry())
Guido van Rossum5f07b841995-04-26 22:57:11 +0000221 f.close()
Guido van Rossum00bbf091995-04-27 21:24:16 +0000222
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()
238
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)
251
252 def report(self):
253 for e in self.values():
254 e.report()
255 print '-'*50
Guido van Rossum5f07b841995-04-26 22:57:11 +0000256
257 def keys(self):
258 keys = self.entries.keys()
259 keys.sort()
260 return keys
261
Guido van Rossum00bbf091995-04-27 21:24:16 +0000262 def values(self):
263 def value(key, self=self):
264 return self.entries[key]
265 return map(value, self.keys())
266
267 def items(self):
268 def item(key, self=self):
269 return (key, self.entries[key])
270 return map(item, self.keys())
271
Guido van Rossum5f07b841995-04-26 22:57:11 +0000272 def cvsexists(self, file):
273 file = os.path.join("CVS", file)
274 return os.path.exists(file)
275
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)
281
282 def backup(self, file):
283 if os.path.isfile(file):
284 bfile = file + '~'
285 os.rename(file, bfile)
286
Guido van Rossum00bbf091995-04-27 21:24:16 +0000287 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
292
293
294# hexify and unhexify are useful to print MD5 checksums in hex format
Guido van Rossum5f07b841995-04-26 22:57:11 +0000295
296hexify_format = '%02x' * 16
297def hexify(sum):
298 "Return a hex representation of a 16-byte string (e.g. an MD5 digest)"
Guido van Rossum00bbf091995-04-27 21:24:16 +0000299 if sum is None:
300 return "None"
Guido van Rossum5f07b841995-04-26 22:57:11 +0000301 return hexify_format % tuple(map(ord, sum))
302
303def unhexify(hexsum):
304 "Return the original from a hexified string"
Guido van Rossum00bbf091995-04-27 21:24:16 +0000305 if hexsum == "None":
306 return None
Guido van Rossum5f07b841995-04-26 22:57:11 +0000307 sum = ''
308 for i in range(0, len(hexsum), 2):
309 sum = sum + chr(string.atoi(hexsum[i:i+2], 16))
310 return sum
311
312
313unctime_monthmap = {}
314def unctime(date):
Guido van Rossum00bbf091995-04-27 21:24:16 +0000315 if date == "None": return None
Guido van Rossum5f07b841995-04-26 22:57:11 +0000316 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))
330
331def gmctime(t):
Guido van Rossum00bbf091995-04-27 21:24:16 +0000332 if t is None: return "None"
Guido van Rossum5f07b841995-04-26 22:57:11 +0000333 return time.asctime(time.gmtime(t))
334
335def 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)
347
348def test():
349 x = CVS()
Guido van Rossum00bbf091995-04-27 21:24:16 +0000350 x.getentries()
351 x.getlocalfiles()
352## x.report()
353 import rcsclient
354 proxy = rcsclient.openrcsclient()
355 x.getremotefiles(proxy)
356 x.report()
Guido van Rossum5f07b841995-04-26 22:57:11 +0000357
358
359if __name__ == "__main__":
360 test()