blob: 9690f3bb7c4f05895290900cb0bef92a184b52da [file] [log] [blame]
Guido van Rossum802c4371995-06-23 21:58:18 +00001"""RCS interface module.
Guido van Rossum79ed32d1995-06-23 14:40:06 +00002
Guido van Rossum802c4371995-06-23 21:58:18 +00003Defines the class RCS, which represents a directory with rcs version
4files and (possibly) corresponding work files.
5
Guido van Rossum79ed32d1995-06-23 14:40:06 +00006"""
7
Guido van Rossum79ed32d1995-06-23 14:40:06 +00008
Guido van Rossum802c4371995-06-23 21:58:18 +00009import fnmatch
Guido van Rossum79ed32d1995-06-23 14:40:06 +000010import os
Thomas Wouters49fd7fa2006-04-21 10:40:58 +000011import re
Guido van Rossum802c4371995-06-23 21:58:18 +000012import string
Guido van Rossum79ed32d1995-06-23 14:40:06 +000013import tempfile
14
Guido van Rossum79ed32d1995-06-23 14:40:06 +000015
Guido van Rossum802c4371995-06-23 21:58:18 +000016class RCS:
Guido van Rossum79ed32d1995-06-23 14:40:06 +000017
Guido van Rossum802c4371995-06-23 21:58:18 +000018 """RCS interface class (local filesystem version).
Guido van Rossum79ed32d1995-06-23 14:40:06 +000019
Guido van Rossum802c4371995-06-23 21:58:18 +000020 An instance of this class represents a directory with rcs version
21 files and (possible) corresponding work files.
Guido van Rossum79ed32d1995-06-23 14:40:06 +000022
Guido van Rossum802c4371995-06-23 21:58:18 +000023 Methods provide access to most rcs operations such as
24 checkin/checkout, access to the rcs metadata (revisions, logs,
25 branches etc.) as well as some filesystem operations such as
26 listing all rcs version files.
Guido van Rossum79ed32d1995-06-23 14:40:06 +000027
Guido van Rossum802c4371995-06-23 21:58:18 +000028 XXX BUGS / PROBLEMS
Guido van Rossum79ed32d1995-06-23 14:40:06 +000029
Guido van Rossum802c4371995-06-23 21:58:18 +000030 - The instance always represents the current directory so it's not
31 very useful to have more than one instance around simultaneously
Guido van Rossum79ed32d1995-06-23 14:40:06 +000032
Guido van Rossum802c4371995-06-23 21:58:18 +000033 """
Guido van Rossum79ed32d1995-06-23 14:40:06 +000034
Guido van Rossum802c4371995-06-23 21:58:18 +000035 # Characters allowed in work file names
Fred Drake79e75e12001-07-20 19:05:50 +000036 okchars = string.ascii_letters + string.digits + '-_=+'
Guido van Rossum79ed32d1995-06-23 14:40:06 +000037
Guido van Rossum802c4371995-06-23 21:58:18 +000038 def __init__(self):
Guido van Rossum4117e541998-09-14 16:44:15 +000039 """Constructor."""
40 pass
Guido van Rossum79ed32d1995-06-23 14:40:06 +000041
Guido van Rossum802c4371995-06-23 21:58:18 +000042 def __del__(self):
Guido van Rossum4117e541998-09-14 16:44:15 +000043 """Destructor."""
44 pass
Guido van Rossum79ed32d1995-06-23 14:40:06 +000045
Guido van Rossum802c4371995-06-23 21:58:18 +000046 # --- Informational methods about a single file/revision ---
Guido van Rossum79ed32d1995-06-23 14:40:06 +000047
Guido van Rossum802c4371995-06-23 21:58:18 +000048 def log(self, name_rev, otherflags = ''):
Guido van Rossum4117e541998-09-14 16:44:15 +000049 """Return the full log text for NAME_REV as a string.
Guido van Rossum79ed32d1995-06-23 14:40:06 +000050
Guido van Rossum4117e541998-09-14 16:44:15 +000051 Optional OTHERFLAGS are passed to rlog.
Guido van Rossum79ed32d1995-06-23 14:40:06 +000052
Guido van Rossum4117e541998-09-14 16:44:15 +000053 """
54 f = self._open(name_rev, 'rlog ' + otherflags)
55 data = f.read()
56 status = self._closepipe(f)
57 if status:
58 data = data + "%s: %s" % status
59 elif data[-1] == '\n':
60 data = data[:-1]
61 return data
Guido van Rossum79ed32d1995-06-23 14:40:06 +000062
Guido van Rossum802c4371995-06-23 21:58:18 +000063 def head(self, name_rev):
Guido van Rossum4117e541998-09-14 16:44:15 +000064 """Return the head revision for NAME_REV"""
65 dict = self.info(name_rev)
66 return dict['head']
Guido van Rossum79ed32d1995-06-23 14:40:06 +000067
Guido van Rossum802c4371995-06-23 21:58:18 +000068 def info(self, name_rev):
Guido van Rossum4117e541998-09-14 16:44:15 +000069 """Return a dictionary of info (from rlog -h) for NAME_REV
Guido van Rossum79ed32d1995-06-23 14:40:06 +000070
Guido van Rossum4117e541998-09-14 16:44:15 +000071 The dictionary's keys are the keywords that rlog prints
72 (e.g. 'head' and its values are the corresponding data
73 (e.g. '1.3').
Guido van Rossum79ed32d1995-06-23 14:40:06 +000074
Guido van Rossum4117e541998-09-14 16:44:15 +000075 XXX symbolic names and locks are not returned
Guido van Rossum79ed32d1995-06-23 14:40:06 +000076
Guido van Rossum4117e541998-09-14 16:44:15 +000077 """
78 f = self._open(name_rev, 'rlog -h')
79 dict = {}
80 while 1:
81 line = f.readline()
82 if not line: break
83 if line[0] == '\t':
84 # XXX could be a lock or symbolic name
85 # Anything else?
Tim Peterse6ddc8b2004-07-18 05:56:09 +000086 continue
Guido van Rossum4117e541998-09-14 16:44:15 +000087 i = string.find(line, ':')
88 if i > 0:
89 key, value = line[:i], string.strip(line[i+1:])
90 dict[key] = value
91 status = self._closepipe(f)
92 if status:
Collin Winter6f2df4d2007-07-17 20:59:35 +000093 raise IOError(status)
Guido van Rossum4117e541998-09-14 16:44:15 +000094 return dict
Guido van Rossum79ed32d1995-06-23 14:40:06 +000095
Guido van Rossum802c4371995-06-23 21:58:18 +000096 # --- Methods that change files ---
Guido van Rossum79ed32d1995-06-23 14:40:06 +000097
Guido van Rossum802c4371995-06-23 21:58:18 +000098 def lock(self, name_rev):
Guido van Rossum4117e541998-09-14 16:44:15 +000099 """Set an rcs lock on NAME_REV."""
100 name, rev = self.checkfile(name_rev)
101 cmd = "rcs -l%s %s" % (rev, name)
102 return self._system(cmd)
Guido van Rossum79ed32d1995-06-23 14:40:06 +0000103
Guido van Rossum802c4371995-06-23 21:58:18 +0000104 def unlock(self, name_rev):
Guido van Rossum4117e541998-09-14 16:44:15 +0000105 """Clear an rcs lock on NAME_REV."""
106 name, rev = self.checkfile(name_rev)
107 cmd = "rcs -u%s %s" % (rev, name)
108 return self._system(cmd)
Guido van Rossum802c4371995-06-23 21:58:18 +0000109
110 def checkout(self, name_rev, withlock=0, otherflags=""):
Guido van Rossum4117e541998-09-14 16:44:15 +0000111 """Check out NAME_REV to its work file.
Guido van Rossum802c4371995-06-23 21:58:18 +0000112
Guido van Rossum4117e541998-09-14 16:44:15 +0000113 If optional WITHLOCK is set, check out locked, else unlocked.
Guido van Rossum802c4371995-06-23 21:58:18 +0000114
Guido van Rossum4117e541998-09-14 16:44:15 +0000115 The optional OTHERFLAGS is passed to co without
116 interpretation.
Guido van Rossum802c4371995-06-23 21:58:18 +0000117
Guido van Rossum4117e541998-09-14 16:44:15 +0000118 Any output from co goes to directly to stdout.
Guido van Rossum802c4371995-06-23 21:58:18 +0000119
Guido van Rossum4117e541998-09-14 16:44:15 +0000120 """
121 name, rev = self.checkfile(name_rev)
122 if withlock: lockflag = "-l"
123 else: lockflag = "-u"
124 cmd = 'co %s%s %s %s' % (lockflag, rev, otherflags, name)
125 return self._system(cmd)
Guido van Rossum79ed32d1995-06-23 14:40:06 +0000126
Guido van Rossum802c4371995-06-23 21:58:18 +0000127 def checkin(self, name_rev, message=None, otherflags=""):
Guido van Rossum4117e541998-09-14 16:44:15 +0000128 """Check in NAME_REV from its work file.
Guido van Rossum802c4371995-06-23 21:58:18 +0000129
Guido van Rossum4117e541998-09-14 16:44:15 +0000130 The optional MESSAGE argument becomes the checkin message
131 (default "<none>" if None); or the file description if this is
132 a new file.
Guido van Rossum802c4371995-06-23 21:58:18 +0000133
Guido van Rossum4117e541998-09-14 16:44:15 +0000134 The optional OTHERFLAGS argument is passed to ci without
135 interpretation.
Guido van Rossum802c4371995-06-23 21:58:18 +0000136
Guido van Rossum4117e541998-09-14 16:44:15 +0000137 Any output from ci goes to directly to stdout.
Guido van Rossum802c4371995-06-23 21:58:18 +0000138
Guido van Rossum4117e541998-09-14 16:44:15 +0000139 """
140 name, rev = self._unmangle(name_rev)
141 new = not self.isvalid(name)
142 if not message: message = "<none>"
143 if message and message[-1] != '\n':
144 message = message + '\n'
145 lockflag = "-u"
Guido van Rossum3b0a3292002-08-09 16:38:32 +0000146 if new:
147 f = tempfile.NamedTemporaryFile()
148 f.write(message)
149 f.flush()
150 cmd = 'ci %s%s -t%s %s %s' % \
151 (lockflag, rev, f.name, otherflags, name)
152 else:
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000153 message = re.sub(r'([\"$`])', r'\\\1', message)
Guido van Rossum3b0a3292002-08-09 16:38:32 +0000154 cmd = 'ci %s%s -m"%s" %s %s' % \
155 (lockflag, rev, message, otherflags, name)
156 return self._system(cmd)
Guido van Rossum79ed32d1995-06-23 14:40:06 +0000157
Guido van Rossum802c4371995-06-23 21:58:18 +0000158 # --- Exported support methods ---
Guido van Rossum79ed32d1995-06-23 14:40:06 +0000159
Guido van Rossum802c4371995-06-23 21:58:18 +0000160 def listfiles(self, pat = None):
Guido van Rossum4117e541998-09-14 16:44:15 +0000161 """Return a list of all version files matching optional PATTERN."""
162 files = os.listdir(os.curdir)
Collin Winter6f2df4d2007-07-17 20:59:35 +0000163 files = list(filter(self._isrcs, files))
Guido van Rossum4117e541998-09-14 16:44:15 +0000164 if os.path.isdir('RCS'):
165 files2 = os.listdir('RCS')
Collin Winter6f2df4d2007-07-17 20:59:35 +0000166 files2 = list(filter(self._isrcs, files2))
Guido van Rossum4117e541998-09-14 16:44:15 +0000167 files = files + files2
Collin Winter6f2df4d2007-07-17 20:59:35 +0000168 files = list(map(self.realname, files))
Guido van Rossum4117e541998-09-14 16:44:15 +0000169 return self._filter(files, pat)
Guido van Rossum802c4371995-06-23 21:58:18 +0000170
171 def isvalid(self, name):
Guido van Rossum4117e541998-09-14 16:44:15 +0000172 """Test whether NAME has a version file associated."""
173 namev = self.rcsname(name)
174 return (os.path.isfile(namev) or
175 os.path.isfile(os.path.join('RCS', namev)))
Guido van Rossum802c4371995-06-23 21:58:18 +0000176
177 def rcsname(self, name):
Guido van Rossum4117e541998-09-14 16:44:15 +0000178 """Return the pathname of the version file for NAME.
Guido van Rossum802c4371995-06-23 21:58:18 +0000179
Guido van Rossum4117e541998-09-14 16:44:15 +0000180 The argument can be a work file name or a version file name.
181 If the version file does not exist, the name of the version
182 file that would be created by "ci" is returned.
Guido van Rossum802c4371995-06-23 21:58:18 +0000183
Guido van Rossum4117e541998-09-14 16:44:15 +0000184 """
185 if self._isrcs(name): namev = name
186 else: namev = name + ',v'
187 if os.path.isfile(namev): return namev
188 namev = os.path.join('RCS', os.path.basename(namev))
189 if os.path.isfile(namev): return namev
190 if os.path.isdir('RCS'):
191 return os.path.join('RCS', namev)
192 else:
193 return namev
Guido van Rossum802c4371995-06-23 21:58:18 +0000194
195 def realname(self, namev):
Guido van Rossum4117e541998-09-14 16:44:15 +0000196 """Return the pathname of the work file for NAME.
Guido van Rossum802c4371995-06-23 21:58:18 +0000197
Guido van Rossum4117e541998-09-14 16:44:15 +0000198 The argument can be a work file name or a version file name.
199 If the work file does not exist, the name of the work file
200 that would be created by "co" is returned.
Guido van Rossum802c4371995-06-23 21:58:18 +0000201
Guido van Rossum4117e541998-09-14 16:44:15 +0000202 """
203 if self._isrcs(namev): name = namev[:-2]
204 else: name = namev
205 if os.path.isfile(name): return name
206 name = os.path.basename(name)
207 return name
Guido van Rossum802c4371995-06-23 21:58:18 +0000208
209 def islocked(self, name_rev):
Guido van Rossum4117e541998-09-14 16:44:15 +0000210 """Test whether FILE (which must have a version file) is locked.
Guido van Rossum802c4371995-06-23 21:58:18 +0000211
Guido van Rossum4117e541998-09-14 16:44:15 +0000212 XXX This does not tell you which revision number is locked and
213 ignores any revision you may pass in (by virtue of using rlog
214 -L -R).
Guido van Rossum802c4371995-06-23 21:58:18 +0000215
Guido van Rossum4117e541998-09-14 16:44:15 +0000216 """
217 f = self._open(name_rev, 'rlog -L -R')
218 line = f.readline()
219 status = self._closepipe(f)
220 if status:
Collin Winter6f2df4d2007-07-17 20:59:35 +0000221 raise IOError(status)
Guido van Rossum4117e541998-09-14 16:44:15 +0000222 if not line: return None
223 if line[-1] == '\n':
224 line = line[:-1]
225 return self.realname(name_rev) == self.realname(line)
Guido van Rossum802c4371995-06-23 21:58:18 +0000226
227 def checkfile(self, name_rev):
Guido van Rossum4117e541998-09-14 16:44:15 +0000228 """Normalize NAME_REV into a (NAME, REV) tuple.
Guido van Rossum802c4371995-06-23 21:58:18 +0000229
Guido van Rossum4117e541998-09-14 16:44:15 +0000230 Raise an exception if there is no corresponding version file.
Guido van Rossum802c4371995-06-23 21:58:18 +0000231
Guido van Rossum4117e541998-09-14 16:44:15 +0000232 """
233 name, rev = self._unmangle(name_rev)
234 if not self.isvalid(name):
Collin Winter6f2df4d2007-07-17 20:59:35 +0000235 raise os.error('not an rcs file %r' % (name,))
Guido van Rossum4117e541998-09-14 16:44:15 +0000236 return name, rev
Guido van Rossum802c4371995-06-23 21:58:18 +0000237
238 # --- Internal methods ---
239
240 def _open(self, name_rev, cmd = 'co -p', rflag = '-r'):
Guido van Rossum4117e541998-09-14 16:44:15 +0000241 """INTERNAL: open a read pipe to NAME_REV using optional COMMAND.
Guido van Rossum802c4371995-06-23 21:58:18 +0000242
Guido van Rossum4117e541998-09-14 16:44:15 +0000243 Optional FLAG is used to indicate the revision (default -r).
Guido van Rossum802c4371995-06-23 21:58:18 +0000244
Guido van Rossum4117e541998-09-14 16:44:15 +0000245 Default COMMAND is "co -p".
Guido van Rossum802c4371995-06-23 21:58:18 +0000246
Guido van Rossum4117e541998-09-14 16:44:15 +0000247 Return a file object connected by a pipe to the command's
248 output.
Guido van Rossum802c4371995-06-23 21:58:18 +0000249
Guido van Rossum4117e541998-09-14 16:44:15 +0000250 """
251 name, rev = self.checkfile(name_rev)
252 namev = self.rcsname(name)
253 if rev:
254 cmd = cmd + ' ' + rflag + rev
Walter Dörwald70a6b492004-02-12 17:35:32 +0000255 return os.popen("%s %r" % (cmd, namev))
Guido van Rossum802c4371995-06-23 21:58:18 +0000256
257 def _unmangle(self, name_rev):
Guido van Rossum4117e541998-09-14 16:44:15 +0000258 """INTERNAL: Normalize NAME_REV argument to (NAME, REV) tuple.
Guido van Rossum802c4371995-06-23 21:58:18 +0000259
Guido van Rossum4117e541998-09-14 16:44:15 +0000260 Raise an exception if NAME contains invalid characters.
Guido van Rossum802c4371995-06-23 21:58:18 +0000261
Guido van Rossum4117e541998-09-14 16:44:15 +0000262 A NAME_REV argument is either NAME string (implying REV='') or
263 a tuple of the form (NAME, REV).
Guido van Rossum802c4371995-06-23 21:58:18 +0000264
Guido van Rossum4117e541998-09-14 16:44:15 +0000265 """
266 if type(name_rev) == type(''):
267 name_rev = name, rev = name_rev, ''
268 else:
269 name, rev = name_rev
270 for c in rev:
271 if c not in self.okchars:
Collin Winter6f2df4d2007-07-17 20:59:35 +0000272 raise ValueError("bad char in rev")
Guido van Rossum4117e541998-09-14 16:44:15 +0000273 return name_rev
Guido van Rossum802c4371995-06-23 21:58:18 +0000274
275 def _closepipe(self, f):
Guido van Rossum4117e541998-09-14 16:44:15 +0000276 """INTERNAL: Close PIPE and print its exit status if nonzero."""
277 sts = f.close()
278 if not sts: return None
279 detail, reason = divmod(sts, 256)
280 if reason == 0: return 'exit', detail # Exit status
281 signal = reason&0x7F
282 if signal == 0x7F:
283 code = 'stopped'
284 signal = detail
285 else:
286 code = 'killed'
287 if reason&0x80:
288 code = code + '(coredump)'
289 return code, signal
Tim Peterse6ddc8b2004-07-18 05:56:09 +0000290
Guido van Rossum802c4371995-06-23 21:58:18 +0000291 def _system(self, cmd):
Guido van Rossum4117e541998-09-14 16:44:15 +0000292 """INTERNAL: run COMMAND in a subshell.
Guido van Rossum802c4371995-06-23 21:58:18 +0000293
Jeremy Hyltona05e2932000-06-28 14:48:01 +0000294 Standard input for the command is taken from /dev/null.
Guido van Rossum802c4371995-06-23 21:58:18 +0000295
Guido van Rossum4117e541998-09-14 16:44:15 +0000296 Raise IOError when the exit status is not zero.
Guido van Rossum802c4371995-06-23 21:58:18 +0000297
Guido van Rossum4117e541998-09-14 16:44:15 +0000298 Return whatever the calling method should return; normally
299 None.
Guido van Rossum802c4371995-06-23 21:58:18 +0000300
Guido van Rossum4117e541998-09-14 16:44:15 +0000301 A derived class may override this method and redefine it to
302 capture stdout/stderr of the command and return it.
Guido van Rossum802c4371995-06-23 21:58:18 +0000303
Guido van Rossum4117e541998-09-14 16:44:15 +0000304 """
305 cmd = cmd + " </dev/null"
306 sts = os.system(cmd)
Collin Winter6f2df4d2007-07-17 20:59:35 +0000307 if sts: raise IOError("command exit status %d" % sts)
Guido van Rossum802c4371995-06-23 21:58:18 +0000308
309 def _filter(self, files, pat = None):
Guido van Rossum4117e541998-09-14 16:44:15 +0000310 """INTERNAL: Return a sorted copy of the given list of FILES.
Guido van Rossum802c4371995-06-23 21:58:18 +0000311
Guido van Rossum4117e541998-09-14 16:44:15 +0000312 If a second PATTERN argument is given, only files matching it
313 are kept. No check for valid filenames is made.
Tim Peterse6ddc8b2004-07-18 05:56:09 +0000314
Guido van Rossum4117e541998-09-14 16:44:15 +0000315 """
316 if pat:
317 def keep(name, pat = pat):
318 return fnmatch.fnmatch(name, pat)
Collin Winter6f2df4d2007-07-17 20:59:35 +0000319 files = list(filter(keep, files))
Guido van Rossum4117e541998-09-14 16:44:15 +0000320 else:
321 files = files[:]
322 files.sort()
323 return files
Guido van Rossum802c4371995-06-23 21:58:18 +0000324
325 def _remove(self, fn):
Guido van Rossum4117e541998-09-14 16:44:15 +0000326 """INTERNAL: remove FILE without complaints."""
327 try:
328 os.unlink(fn)
329 except os.error:
330 pass
Guido van Rossum802c4371995-06-23 21:58:18 +0000331
332 def _isrcs(self, name):
Guido van Rossum4117e541998-09-14 16:44:15 +0000333 """INTERNAL: Test whether NAME ends in ',v'."""
334 return name[-2:] == ',v'