blob: 78e4fbb4c9df4404a77e4ee9df2001417b98b2c7 [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'):
Tim Peterse6ddc8b2004-07-18 05:56:09 +000010 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
Tim Peterse6ddc8b2004-07-18 05:56:09 +000014 """Represent a file's status.
Guido van Rossum00bbf091995-04-27 21:24:16 +000015
Tim Peterse6ddc8b2004-07-18 05:56:09 +000016 Instance variables:
Guido van Rossum00bbf091995-04-27 21:24:16 +000017
Tim Peterse6ddc8b2004-07-18 05:56:09 +000018 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
Guido van Rossum00bbf091995-04-27 21:24:16 +000024
Tim Peterse6ddc8b2004-07-18 05:56:09 +000025 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.
Guido van Rossum00bbf091995-04-27 21:24:16 +000029
Tim Peterse6ddc8b2004-07-18 05:56:09 +000030 If lseen is true:
Guido van Rossum00bbf091995-04-27 21:24:16 +000031
Tim Peterse6ddc8b2004-07-18 05:56:09 +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
Guido van Rossum00bbf091995-04-27 21:24:16 +000035
Tim Peterse6ddc8b2004-07-18 05:56:09 +000036 If eseen is true:
Guido van Rossum00bbf091995-04-27 21:24:16 +000037
Tim Peterse6ddc8b2004-07-18 05:56:09 +000038 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
Guido van Rossum00bbf091995-04-27 21:24:16 +000044
Tim Peterse6ddc8b2004-07-18 05:56:09 +000045 If rseen is true:
Guido van Rossum00bbf091995-04-27 21:24:16 +000046
Tim Peterse6ddc8b2004-07-18 05:56:09 +000047 rrev -- revision of head, None if non-existent
48 rsum -- checksum of that revision, Non if non-existent
Guido van Rossum00bbf091995-04-27 21:24:16 +000049
Tim Peterse6ddc8b2004-07-18 05:56:09 +000050 If eseen and rseen are both true:
Guido van Rossum00bbf091995-04-27 21:24:16 +000051
Tim Peterse6ddc8b2004-07-18 05:56:09 +000052 esum -- checksum of revision erev, None if no revision
Guido van Rossum1c653bd1995-05-01 20:06:06 +000053
Tim Peterse6ddc8b2004-07-18 05:56:09 +000054 Note
55 """
Guido van Rossum00bbf091995-04-27 21:24:16 +000056
Tim Peterse6ddc8b2004-07-18 05:56:09 +000057 def __init__(self, file = None):
58 if file and '/' in file:
Collin Winter6f2df4d2007-07-17 20:59:35 +000059 raise ValueError("no slash allowed in file")
Tim Peterse6ddc8b2004-07-18 05:56:09 +000060 self.file = file
61 self.lseen = self.eseen = self.rseen = 0
62 self.proxy = None
Guido van Rossum00bbf091995-04-27 21:24:16 +000063
Tim Peterse6ddc8b2004-07-18 05:56:09 +000064 def __cmp__(self, other):
65 return cmp(self.file, other.file)
Guido van Rossum00bbf091995-04-27 21:24:16 +000066
Tim Peterse6ddc8b2004-07-18 05:56:09 +000067 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
Guido van Rossum00bbf091995-04-27 21:24:16 +000075
Tim Peterse6ddc8b2004-07-18 05:56:09 +000076 def getentry(self, line):
77 words = string.splitfields(line, '/')
78 if self.file and words[1] != self.file:
Collin Winter6f2df4d2007-07-17 20:59:35 +000079 raise ValueError("file name mismatch")
Tim Peterse6ddc8b2004-07-18 05:56:09 +000080 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
Guido van Rossum00bbf091995-04-27 21:24:16 +000099
Tim Peterse6ddc8b2004-07-18 05:56:09 +0000100 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
Guido van Rossum00bbf091995-04-27 21:24:16 +0000114
Tim Peterse6ddc8b2004-07-18 05:56:09 +0000115 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
Guido van Rossum00bbf091995-04-27 21:24:16 +0000123
Tim Peterse6ddc8b2004-07-18 05:56:09 +0000124 def putentry(self):
125 """Return a line suitable for inclusion in CVS/Entries.
Guido van Rossum00bbf091995-04-27 21:24:16 +0000126
Tim Peterse6ddc8b2004-07-18 05:56:09 +0000127 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)
142 return "/%s/%s/%s/%s/\n" % (
143 self.file,
144 rev,
145 dates,
146 self.extra)
147
148 def report(self):
Collin Winter6f2df4d2007-07-17 20:59:35 +0000149 print('-'*50)
Tim Peterse6ddc8b2004-07-18 05:56:09 +0000150 def r(key, repr=repr, self=self):
151 try:
152 value = repr(getattr(self, key))
153 except AttributeError:
154 value = "?"
Collin Winter6f2df4d2007-07-17 20:59:35 +0000155 print("%-15s:" % key, value)
Tim Peterse6ddc8b2004-07-18 05:56:09 +0000156 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)
Guido van Rossum5f07b841995-04-26 22:57:11 +0000172
173
174class CVS:
175
Tim Peterse6ddc8b2004-07-18 05:56:09 +0000176 """Represent the contents of a CVS admin file (and more).
Guido van Rossum00bbf091995-04-27 21:24:16 +0000177
Tim Peterse6ddc8b2004-07-18 05:56:09 +0000178 Class variables:
Guido van Rossum00bbf091995-04-27 21:24:16 +0000179
Tim Peterse6ddc8b2004-07-18 05:56:09 +0000180 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
Guido van Rossum00bbf091995-04-27 21:24:16 +0000183
Tim Peterse6ddc8b2004-07-18 05:56:09 +0000184 Instance variables:
Guido van Rossum00bbf091995-04-27 21:24:16 +0000185
Tim Peterse6ddc8b2004-07-18 05:56:09 +0000186 entries -- a dictionary containing File instances keyed by
187 their file name
188 proxy -- an RCSProxy instance, or None
189 """
Guido van Rossum00bbf091995-04-27 21:24:16 +0000190
Tim Peterse6ddc8b2004-07-18 05:56:09 +0000191 FileClass = File
Guido van Rossum00bbf091995-04-27 21:24:16 +0000192
Tim Peterse6ddc8b2004-07-18 05:56:09 +0000193 IgnoreList = ['.*', '@*', ',*', '*~', '*.o', '*.a', '*.so', '*.pyc']
Guido van Rossum00bbf091995-04-27 21:24:16 +0000194
Tim Peterse6ddc8b2004-07-18 05:56:09 +0000195 def __init__(self):
196 self.entries = {}
197 self.proxy = None
Guido van Rossum5f07b841995-04-26 22:57:11 +0000198
Tim Peterse6ddc8b2004-07-18 05:56:09 +0000199 def setproxy(self, proxy):
200 if proxy is self.proxy:
201 return
202 self.proxy = proxy
Collin Winter6f2df4d2007-07-17 20:59:35 +0000203 for e in list(self.entries.values()):
Tim Peterse6ddc8b2004-07-18 05:56:09 +0000204 e.rseen = 0
Guido van Rossum00bbf091995-04-27 21:24:16 +0000205
Tim Peterse6ddc8b2004-07-18 05:56:09 +0000206 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()
Guido van Rossum00bbf091995-04-27 21:24:16 +0000217
Tim Peterse6ddc8b2004-07-18 05:56:09 +0000218 def putentries(self):
219 """Write CVS/Entries back"""
220 f = self.cvsopen("Entries", 'w')
Collin Winter6f2df4d2007-07-17 20:59:35 +0000221 for e in list(self.values()):
Tim Peterse6ddc8b2004-07-18 05:56:09 +0000222 f.write(e.putentry())
223 f.close()
Guido van Rossum5f07b841995-04-26 22:57:11 +0000224
Tim Peterse6ddc8b2004-07-18 05:56:09 +0000225 def getlocalfiles(self):
Skip Montanaro1e8ce582007-08-06 21:07:53 +0000226 entries_keys = set(self.entries.keys())
Tim Peterse6ddc8b2004-07-18 05:56:09 +0000227 addlist = os.listdir(os.curdir)
228 for name in addlist:
Tim Peterse6ddc8b2004-07-18 05:56:09 +0000229 if not self.ignored(name):
Skip Montanaro1e8ce582007-08-06 21:07:53 +0000230 entries_keys.add(name)
231 for file in sorted(entries_keys):
Tim Peterse6ddc8b2004-07-18 05:56:09 +0000232 try:
233 e = self.entries[file]
234 except KeyError:
235 e = self.entries[file] = self.FileClass(file)
236 e.getlocal()
237
238 def getremotefiles(self, proxy = None):
239 if proxy:
240 self.proxy = proxy
241 if not self.proxy:
Collin Winter6f2df4d2007-07-17 20:59:35 +0000242 raise RuntimeError("no RCS proxy")
Tim Peterse6ddc8b2004-07-18 05:56:09 +0000243 addlist = self.proxy.listfiles()
244 for file in addlist:
245 try:
246 e = self.entries[file]
247 except KeyError:
248 e = self.entries[file] = self.FileClass(file)
249 e.getremote(self.proxy)
250
251 def report(self):
Collin Winter6f2df4d2007-07-17 20:59:35 +0000252 for e in list(self.values()):
Tim Peterse6ddc8b2004-07-18 05:56:09 +0000253 e.report()
Collin Winter6f2df4d2007-07-17 20:59:35 +0000254 print('-'*50)
Tim Peterse6ddc8b2004-07-18 05:56:09 +0000255
256 def keys(self):
Skip Montanaro1e8ce582007-08-06 21:07:53 +0000257 return sorted(self.entries.keys())
Tim Peterse6ddc8b2004-07-18 05:56:09 +0000258
259 def values(self):
260 def value(key, self=self):
261 return self.entries[key]
Skip Montanaro1e8ce582007-08-06 21:07:53 +0000262 return [value(k) for k in self.keys()]
Tim Peterse6ddc8b2004-07-18 05:56:09 +0000263
264 def items(self):
265 def item(key, self=self):
266 return (key, self.entries[key])
Skip Montanaro1e8ce582007-08-06 21:07:53 +0000267 return [item(k) for k in self.keys()]
Tim Peterse6ddc8b2004-07-18 05:56:09 +0000268
269 def cvsexists(self, file):
270 file = os.path.join("CVS", file)
271 return os.path.exists(file)
272
273 def cvsopen(self, file, mode = 'r'):
274 file = os.path.join("CVS", file)
275 if 'r' not in mode:
276 self.backup(file)
277 return open(file, mode)
278
279 def backup(self, file):
280 if os.path.isfile(file):
281 bfile = file + '~'
282 try: os.unlink(bfile)
283 except os.error: pass
284 os.rename(file, bfile)
285
286 def ignored(self, file):
287 if os.path.isdir(file): return True
288 for pat in self.IgnoreList:
289 if fnmatch.fnmatch(file, pat): return True
290 return False
Guido van Rossum00bbf091995-04-27 21:24:16 +0000291
292
293# hexify and unhexify are useful to print MD5 checksums in hex format
Guido van Rossum5f07b841995-04-26 22:57:11 +0000294
295hexify_format = '%02x' * 16
296def hexify(sum):
Tim Peterse6ddc8b2004-07-18 05:56:09 +0000297 "Return a hex representation of a 16-byte string (e.g. an MD5 digest)"
298 if sum is None:
299 return "None"
300 return hexify_format % tuple(map(ord, sum))
Guido van Rossum5f07b841995-04-26 22:57:11 +0000301
302def unhexify(hexsum):
Tim Peterse6ddc8b2004-07-18 05:56:09 +0000303 "Return the original from a hexified string"
304 if hexsum == "None":
305 return None
306 sum = ''
307 for i in range(0, len(hexsum), 2):
308 sum = sum + chr(string.atoi(hexsum[i:i+2], 16))
309 return sum
Guido van Rossum5f07b841995-04-26 22:57:11 +0000310
311
312unctime_monthmap = {}
313def unctime(date):
Tim Peterse6ddc8b2004-07-18 05:56:09 +0000314 if date == "None": return None
315 if not unctime_monthmap:
316 months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
317 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
318 i = 0
319 for m in months:
320 i = i+1
321 unctime_monthmap[m] = i
322 words = string.split(date) # Day Mon DD HH:MM:SS YEAR
323 year = string.atoi(words[4])
324 month = unctime_monthmap[words[1]]
325 day = string.atoi(words[2])
Collin Winter6f2df4d2007-07-17 20:59:35 +0000326 [hh, mm, ss] = list(map(string.atoi, string.splitfields(words[3], ':')))
Tim Peterse6ddc8b2004-07-18 05:56:09 +0000327 ss = ss - time.timezone
328 return time.mktime((year, month, day, hh, mm, ss, 0, 0, 0))
Guido van Rossum5f07b841995-04-26 22:57:11 +0000329
330def gmctime(t):
Tim Peterse6ddc8b2004-07-18 05:56:09 +0000331 if t is None: return "None"
332 return time.asctime(time.gmtime(t))
Guido van Rossum5f07b841995-04-26 22:57:11 +0000333
334def test_unctime():
Tim Peterse6ddc8b2004-07-18 05:56:09 +0000335 now = int(time.time())
336 t = time.gmtime(now)
337 at = time.asctime(t)
Collin Winter6f2df4d2007-07-17 20:59:35 +0000338 print('GMT', now, at)
339 print('timezone', time.timezone)
340 print('local', time.ctime(now))
Tim Peterse6ddc8b2004-07-18 05:56:09 +0000341 u = unctime(at)
Collin Winter6f2df4d2007-07-17 20:59:35 +0000342 print('unctime()', u)
Tim Peterse6ddc8b2004-07-18 05:56:09 +0000343 gu = time.gmtime(u)
Collin Winter6f2df4d2007-07-17 20:59:35 +0000344 print('->', gu)
345 print(time.asctime(gu))
Guido van Rossum5f07b841995-04-26 22:57:11 +0000346
347def test():
Tim Peterse6ddc8b2004-07-18 05:56:09 +0000348 x = CVS()
349 x.getentries()
350 x.getlocalfiles()
351## x.report()
352 import rcsclient
353 proxy = rcsclient.openrcsclient()
354 x.getremotefiles(proxy)
355 x.report()
Guido van Rossum5f07b841995-04-26 22:57:11 +0000356
357
358if __name__ == "__main__":
Tim Peterse6ddc8b2004-07-18 05:56:09 +0000359 test()