blob: 5e79247cb3670fba707b2601c61f2d62ff531b4a [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
Guido van Rossum802c4371995-06-23 21:58:18 +000011import regsub
12import 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
36 okchars = string.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?
86 continue
87 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:
93 raise IOError, status
94 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"
146 textfile = None
147 try:
148 if new:
149 textfile = tempfile.mktemp()
150 f = open(textfile, 'w')
151 f.write(message)
152 f.close()
153 cmd = 'ci %s%s -t%s %s %s' % \
154 (lockflag, rev, textfile, otherflags, name)
155 else:
156 message = regsub.gsub('\([\\"$`]\)', '\\\\\\1', message)
157 cmd = 'ci %s%s -m"%s" %s %s' % \
158 (lockflag, rev, message, otherflags, name)
159 return self._system(cmd)
160 finally:
161 if textfile: self._remove(textfile)
Guido van Rossum79ed32d1995-06-23 14:40:06 +0000162
Guido van Rossum802c4371995-06-23 21:58:18 +0000163 # --- Exported support methods ---
Guido van Rossum79ed32d1995-06-23 14:40:06 +0000164
Guido van Rossum802c4371995-06-23 21:58:18 +0000165 def listfiles(self, pat = None):
Guido van Rossum4117e541998-09-14 16:44:15 +0000166 """Return a list of all version files matching optional PATTERN."""
167 files = os.listdir(os.curdir)
168 files = filter(self._isrcs, files)
169 if os.path.isdir('RCS'):
170 files2 = os.listdir('RCS')
171 files2 = filter(self._isrcs, files2)
172 files = files + files2
173 files = map(self.realname, files)
174 return self._filter(files, pat)
Guido van Rossum802c4371995-06-23 21:58:18 +0000175
176 def isvalid(self, name):
Guido van Rossum4117e541998-09-14 16:44:15 +0000177 """Test whether NAME has a version file associated."""
178 namev = self.rcsname(name)
179 return (os.path.isfile(namev) or
180 os.path.isfile(os.path.join('RCS', namev)))
Guido van Rossum802c4371995-06-23 21:58:18 +0000181
182 def rcsname(self, name):
Guido van Rossum4117e541998-09-14 16:44:15 +0000183 """Return the pathname of the version file for NAME.
Guido van Rossum802c4371995-06-23 21:58:18 +0000184
Guido van Rossum4117e541998-09-14 16:44:15 +0000185 The argument can be a work file name or a version file name.
186 If the version file does not exist, the name of the version
187 file that would be created by "ci" is returned.
Guido van Rossum802c4371995-06-23 21:58:18 +0000188
Guido van Rossum4117e541998-09-14 16:44:15 +0000189 """
190 if self._isrcs(name): namev = name
191 else: namev = name + ',v'
192 if os.path.isfile(namev): return namev
193 namev = os.path.join('RCS', os.path.basename(namev))
194 if os.path.isfile(namev): return namev
195 if os.path.isdir('RCS'):
196 return os.path.join('RCS', namev)
197 else:
198 return namev
Guido van Rossum802c4371995-06-23 21:58:18 +0000199
200 def realname(self, namev):
Guido van Rossum4117e541998-09-14 16:44:15 +0000201 """Return the pathname of the work file for NAME.
Guido van Rossum802c4371995-06-23 21:58:18 +0000202
Guido van Rossum4117e541998-09-14 16:44:15 +0000203 The argument can be a work file name or a version file name.
204 If the work file does not exist, the name of the work file
205 that would be created by "co" is returned.
Guido van Rossum802c4371995-06-23 21:58:18 +0000206
Guido van Rossum4117e541998-09-14 16:44:15 +0000207 """
208 if self._isrcs(namev): name = namev[:-2]
209 else: name = namev
210 if os.path.isfile(name): return name
211 name = os.path.basename(name)
212 return name
Guido van Rossum802c4371995-06-23 21:58:18 +0000213
214 def islocked(self, name_rev):
Guido van Rossum4117e541998-09-14 16:44:15 +0000215 """Test whether FILE (which must have a version file) is locked.
Guido van Rossum802c4371995-06-23 21:58:18 +0000216
Guido van Rossum4117e541998-09-14 16:44:15 +0000217 XXX This does not tell you which revision number is locked and
218 ignores any revision you may pass in (by virtue of using rlog
219 -L -R).
Guido van Rossum802c4371995-06-23 21:58:18 +0000220
Guido van Rossum4117e541998-09-14 16:44:15 +0000221 """
222 f = self._open(name_rev, 'rlog -L -R')
223 line = f.readline()
224 status = self._closepipe(f)
225 if status:
226 raise IOError, status
227 if not line: return None
228 if line[-1] == '\n':
229 line = line[:-1]
230 return self.realname(name_rev) == self.realname(line)
Guido van Rossum802c4371995-06-23 21:58:18 +0000231
232 def checkfile(self, name_rev):
Guido van Rossum4117e541998-09-14 16:44:15 +0000233 """Normalize NAME_REV into a (NAME, REV) tuple.
Guido van Rossum802c4371995-06-23 21:58:18 +0000234
Guido van Rossum4117e541998-09-14 16:44:15 +0000235 Raise an exception if there is no corresponding version file.
Guido van Rossum802c4371995-06-23 21:58:18 +0000236
Guido van Rossum4117e541998-09-14 16:44:15 +0000237 """
238 name, rev = self._unmangle(name_rev)
239 if not self.isvalid(name):
240 raise os.error, 'not an rcs file %s' % `name`
241 return name, rev
Guido van Rossum802c4371995-06-23 21:58:18 +0000242
243 # --- Internal methods ---
244
245 def _open(self, name_rev, cmd = 'co -p', rflag = '-r'):
Guido van Rossum4117e541998-09-14 16:44:15 +0000246 """INTERNAL: open a read pipe to NAME_REV using optional COMMAND.
Guido van Rossum802c4371995-06-23 21:58:18 +0000247
Guido van Rossum4117e541998-09-14 16:44:15 +0000248 Optional FLAG is used to indicate the revision (default -r).
Guido van Rossum802c4371995-06-23 21:58:18 +0000249
Guido van Rossum4117e541998-09-14 16:44:15 +0000250 Default COMMAND is "co -p".
Guido van Rossum802c4371995-06-23 21:58:18 +0000251
Guido van Rossum4117e541998-09-14 16:44:15 +0000252 Return a file object connected by a pipe to the command's
253 output.
Guido van Rossum802c4371995-06-23 21:58:18 +0000254
Guido van Rossum4117e541998-09-14 16:44:15 +0000255 """
256 name, rev = self.checkfile(name_rev)
257 namev = self.rcsname(name)
258 if rev:
259 cmd = cmd + ' ' + rflag + rev
260 return os.popen("%s %s" % (cmd, `namev`))
Guido van Rossum802c4371995-06-23 21:58:18 +0000261
262 def _unmangle(self, name_rev):
Guido van Rossum4117e541998-09-14 16:44:15 +0000263 """INTERNAL: Normalize NAME_REV argument to (NAME, REV) tuple.
Guido van Rossum802c4371995-06-23 21:58:18 +0000264
Guido van Rossum4117e541998-09-14 16:44:15 +0000265 Raise an exception if NAME contains invalid characters.
Guido van Rossum802c4371995-06-23 21:58:18 +0000266
Guido van Rossum4117e541998-09-14 16:44:15 +0000267 A NAME_REV argument is either NAME string (implying REV='') or
268 a tuple of the form (NAME, REV).
Guido van Rossum802c4371995-06-23 21:58:18 +0000269
Guido van Rossum4117e541998-09-14 16:44:15 +0000270 """
271 if type(name_rev) == type(''):
272 name_rev = name, rev = name_rev, ''
273 else:
274 name, rev = name_rev
275 for c in rev:
276 if c not in self.okchars:
277 raise ValueError, "bad char in rev"
278 return name_rev
Guido van Rossum802c4371995-06-23 21:58:18 +0000279
280 def _closepipe(self, f):
Guido van Rossum4117e541998-09-14 16:44:15 +0000281 """INTERNAL: Close PIPE and print its exit status if nonzero."""
282 sts = f.close()
283 if not sts: return None
284 detail, reason = divmod(sts, 256)
285 if reason == 0: return 'exit', detail # Exit status
286 signal = reason&0x7F
287 if signal == 0x7F:
288 code = 'stopped'
289 signal = detail
290 else:
291 code = 'killed'
292 if reason&0x80:
293 code = code + '(coredump)'
294 return code, signal
Guido van Rossum802c4371995-06-23 21:58:18 +0000295
296 def _system(self, cmd):
Guido van Rossum4117e541998-09-14 16:44:15 +0000297 """INTERNAL: run COMMAND in a subshell.
Guido van Rossum802c4371995-06-23 21:58:18 +0000298
Jeremy Hyltona05e2932000-06-28 14:48:01 +0000299 Standard input for the command is taken from /dev/null.
Guido van Rossum802c4371995-06-23 21:58:18 +0000300
Guido van Rossum4117e541998-09-14 16:44:15 +0000301 Raise IOError when the exit status is not zero.
Guido van Rossum802c4371995-06-23 21:58:18 +0000302
Guido van Rossum4117e541998-09-14 16:44:15 +0000303 Return whatever the calling method should return; normally
304 None.
Guido van Rossum802c4371995-06-23 21:58:18 +0000305
Guido van Rossum4117e541998-09-14 16:44:15 +0000306 A derived class may override this method and redefine it to
307 capture stdout/stderr of the command and return it.
Guido van Rossum802c4371995-06-23 21:58:18 +0000308
Guido van Rossum4117e541998-09-14 16:44:15 +0000309 """
310 cmd = cmd + " </dev/null"
311 sts = os.system(cmd)
312 if sts: raise IOError, "command exit status %d" % sts
Guido van Rossum802c4371995-06-23 21:58:18 +0000313
314 def _filter(self, files, pat = None):
Guido van Rossum4117e541998-09-14 16:44:15 +0000315 """INTERNAL: Return a sorted copy of the given list of FILES.
Guido van Rossum802c4371995-06-23 21:58:18 +0000316
Guido van Rossum4117e541998-09-14 16:44:15 +0000317 If a second PATTERN argument is given, only files matching it
318 are kept. No check for valid filenames is made.
319
320 """
321 if pat:
322 def keep(name, pat = pat):
323 return fnmatch.fnmatch(name, pat)
324 files = filter(keep, files)
325 else:
326 files = files[:]
327 files.sort()
328 return files
Guido van Rossum802c4371995-06-23 21:58:18 +0000329
330 def _remove(self, fn):
Guido van Rossum4117e541998-09-14 16:44:15 +0000331 """INTERNAL: remove FILE without complaints."""
332 try:
333 os.unlink(fn)
334 except os.error:
335 pass
Guido van Rossum802c4371995-06-23 21:58:18 +0000336
337 def _isrcs(self, name):
Guido van Rossum4117e541998-09-14 16:44:15 +0000338 """INTERNAL: Test whether NAME ends in ',v'."""
339 return name[-2:] == ',v'