blob: 5e4ecc01b3f086433a9a52c4cd9b2efdf9982682 [file] [log] [blame]
Guido van Rossum79ed32d1995-06-23 14:40:06 +00001"""Base local RCS interface module.
2
3Provides simplified interface to RCS controlled files. Unabashedly
4ripped most of this from RCSProxy.py which had in some respects, more
5than I needed, and in others, not enough. I think RCSProxy.py could
6be rewritten using this module as a base.
7"""
8
9__version__ = "guido's, based on: rcs.py,v 1.2 1995/06/22 21:11:37 bwarsaw Exp"
10
11
12import string
13import os
14import tempfile
15
16okchars = string.letters + string.digits + '-_=+.'
17
18
19class RCSDirectory:
20
21 def __init__(self, directory=None):
22 self._dirstack = []
23 if directory:
24 self.cd(directory)
25
26 def _close(self):
27 while self._dirstack:
28 self.back()
29
30 def pwd(self):
31 return os.getcwd()
32
33 def cd(self, name):
34 save = os.getcwd()
35 os.chdir(name)
36 self._dirstack.append(save)
37
38 def back(self):
39 if not self._dirstack:
40 raise os.error, "empty directory stack"
41 dir = self._dirstack[-1]
42 os.chdir(dir)
43 del self._dirstack[-1]
44
45 def _filter(self, files, pat = None):
46 if pat:
47 def keep(name, pat = pat):
48 return fnmatch.fnmatch(name, pat)
49 files = filter(keep, files)
50 files.sort()
51 return files
52
53 def isfile(self, name):
54 namev = self.rcsname(name)
55 return namev and (os.path.isfile(namev) or \
56 os.path.isfile(os.path.join('RCS', namev)))
57
58 def isrcs(self, name):
59 return name[-2:] == ',v'
60
61 def rcsname(self, name):
62 if self.isrcs(name): namev = name
63 else: namev = name + ',v'
64 if os.path.isfile(namev): return namev
65 namev = os.path.join('RCS', namev)
66 if os.path.isfile(namev): return namev
67 return None
68
69 def realname(self, namev):
70 if self.isrcs(namev): name = namev[:-2]
71 else: name = namev
72 if os.path.isfile(name): return name
73 name = os.path.basename(name)
74 if os.path.isfile(name): return name
75 return None
76
77 def islocked(self, name):
78 f = self._open(name, 'rlog -L -R')
79 line = f.readline()
80 self._closepipe(f)
81 if not line: return None
82 return not self.realname(name) == self.realname(line)
83
84 def _unmangle(self, name):
85 if type(name) == type(''):
86 rev = ''
87 else:
88 name, rev = name
89 return name, rev
90
91 def checkfile(self, name):
92 name, rev = self._unmangle(name)
93 if not self.isfile(name):
94 raise os.error, 'not an rcs file %s' % `name`
95 for c in rev:
96 if c not in okchars:
97 raise ValueError, "bad char in rev"
98 return name, rev
99
100 def listfiles(self, pat = None):
101 files = os.listdir(os.curdir)
102 files = filter(self.isrcs, files)
103 if os.path.isdir('RCS'):
104 files2 = os.listdir('RCS')
105 files2 = filter(self.isrcs, files2)
106 files = files + files2
107 files = map(self.realname, files)
108 return self._filter(files, pat)
109
110 def listsubdirs(self, pat = None):
111 files = os.listdir(os.curdir)
112 files = filter(os.path.isdir, files)
113 return self._filter(files, pat)
114
115 def isdir(self, name):
116 return os.path.isdir(name)
117
118 def _open(self, name, cmd = 'co -p'):
119 name, rev = self.checkfile(name)
120 namev = self.rcsname(name)
121 if rev:
122 cmd = cmd + ' -r' + rev
123 return os.popen('%s %s' % (cmd, `namev`))
124
125 def _closepipe(self, f):
126 sts = f.close()
127 if sts:
128 raise IOError, "Exit status %d" % sts
129
130 def _remove(self, fn):
131 try:
132 os.unlink(fn)
133 except os.error:
134 pass
135
136 def _list(self, function, list):
137 if list is None:
138 list = self.listfiles()
139 res = []
140 for name in list:
141 try:
142 res.append((name, function(name)))
143 except (os.error, IOError):
144 res.append((name, None))
145 return res
146
147 def _dict(self, function, list):
148 if list is None:
149 list = self.listfiles()
150 dict = {}
151 for name in list:
152 try:
153 dict[name] = function(name)
154 except (os.error, IOError):
155 pass
156 return dict
157
158 def info(self, name):
159 f = self._open(name, 'rlog -h')
160 dict = {}
161 while 1:
162 line = f.readline()
163 if not line: break
164 if line[0] == '\t':
165 # TBD: could be a lock or symbolic name
166 # Anything else?
167 continue
168 i = string.find(line, ':')
169 if i > 0:
170 key, value = line[:i], string.strip(line[i+1:])
171 dict[key] = value
172 self._closepipe(f)
173 return dict
174
175 def head(self, name):
176 dict = self.info(name)
177 return dict['head']
178
179 def log(self, name, flags = ''):
180 f = self._open(name, 'rlog %s 2>&1' % flags)
181 log = f.read()
182 self._closepipe(f)
183 return log
184
185 def get(self, name, withlock=None, otherflags=""):
186 """a.k.a. co"""
187 name, rev = self._unmangle(name)
188 if not self.isfile(name):
189 raise os.error, 'not an rcs file %s' % `name`
190 if withlock: lockflag = "-l"
191 else: lockflag = "-u"
192 cmd = 'co %s%s %s %s 2>&1' % (lockflag, rev, otherflags, name)
193 sts = os.system(cmd)
194 if sts: raise IOError, "bad co exit status %d" % sts
195
196 def put(self, name, message=None, otherflags=""):
197 """a.k.a. ci"""
198 if not message: message = "<none>"
199 if message and message[-1] != '\n':
200 message = message + '\n'
201 name, rev = self._unmangle(name)
202 new = not self.isfile(name)
203 # by default, check it in -u so we get an unlocked copy back
204 # in the current directory
205 textfile = None
206 try:
207 if new:
208 for c in name:
209 if c not in okchars:
210 raise ValueError, "bad char in name"
211 textfile = tempfile.mktemp()
212 f = open(textfile, 'w')
213 f.write(message)
214 f.close()
215 cmd = 'ci -u%s -t%s %s %s 2>&1' % \
216 (rev, textfile, otherflags, name)
217 else:
218 cmd = 'ci -u%s -m"%s" %s %s 2>&1' % \
219 (rev, message, otherflags, name)
220
221 sts = os.system(cmd)
222 if sts: raise IOError, "bad ci exit status %d" % sts
223 finally:
224 if textfile: self._remove(textfile)
225
226 def mkdir(self, name):
227 os.mkdir(name, 0777)
228
229 def rmdir(self, name):
230 os.rmdir(name)