1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 """This module manages interaction with version control systems.
23
24 To implement support for a new version control system, inherit the class
25 GenericRevisionControlSystem.
26
27 TODO:
28 * add authenticatin handling
29 * 'commitdirectory' should do a single commit instead of one for each file
30 * maybe implement some caching for 'get_versioned_object' - check profiler
31 """
32
33 import re
34 import os
35
36 DEFAULT_RCS = ["svn", "cvs", "darcs", "git", "bzr"]
37 """the names of all supported revision control systems
38
39 modules of the same name containing a class with the same name are expected
40 to be defined below 'translate.storage.versioncontrol'
41 """
42
43 __CACHED_RCS_CLASSES = {}
44 """The dynamically loaded revision control system implementations (python
45 modules) are cached here for faster access.
46 """
47
58
59
60
61 try:
62
63 import subprocess
64
65
66
67
69 """Runs a command (array of program name and arguments) and returns the
70 exitcode, the output and the error as a tuple.
71 """
72
73 proc = subprocess.Popen(args = command,
74 stdout = subprocess.PIPE,
75 stderr = subprocess.PIPE,
76 stdin = subprocess.PIPE)
77 (output, error) = proc.communicate()
78 ret = proc.returncode
79 return ret, output, error
80
81 except ImportError:
82
83 import popen2
84
86 """Runs a command (array of program name and arguments) and returns the
87 exitcode, the output and the error as a tuple.
88 """
89 escaped_command = " ".join([__shellescape(arg) for arg in command])
90 proc = popen2.Popen3(escaped_command, True)
91 (c_stdin, c_stdout, c_stderr) = (proc.tochild, proc.fromchild, proc.childerr)
92 output = c_stdout.read()
93 error = c_stderr.read()
94 ret = proc.wait()
95 c_stdout.close()
96 c_stderr.close()
97 c_stdin.close()
98 return ret, output, error
99
101 """Shell-escape any non-alphanumeric characters."""
102 return re.sub(r'(\W)', r'\\\1', path)
103
104
106 """The super class for all version control classes.
107
108 Always inherit from this class to implement another RC interface.
109
110 At least the two attributes "RCS_METADIR" and "SCAN_PARENTS" must be
111 overriden by all implementations that derive from this class.
112
113 By default, all implementations can rely on the following attributes:
114 root_dir: the parent of the metadata directory of the working copy
115 location_abs: the absolute path of the RCS object
116 location_rel: the path of the RCS object relative to 'root_dir'
117 """
118
119 RCS_METADIR = None
120 """The name of the metadata directory of the RCS
121
122 e.g.: for Subversion -> ".svn"
123 """
124
125 SCAN_PARENTS = None
126 """whether to check the parent directories for the metadata directory of
127 the RCS working copy
128
129 some revision control systems store their metadata directory only
130 in the base of the working copy (e.g. bzr, GIT and Darcs)
131 use "True" for these RCS
132
133 other RCS store a metadata directory in every single directory of
134 the working copy (e.g. Subversion and CVS)
135 use "False" for these RCS
136 """
137
139 """find the relevant information about this RCS object
140
141 The IOError exception indicates that the specified object (file or
142 directory) is not controlled by the given version control system.
143 """
144
145 self._self_check()
146
147 result = self._find_rcs_directory(location)
148 if result is None:
149 raise IOError("Could not find revision control information: %s" \
150 % location)
151 else:
152 self.root_dir, self.location_abs, self.location_rel = result
153
155 """Try to find the metadata directory of the RCS
156
157 returns a tuple:
158 the absolute path of the directory, that contains the metadata directory
159 the absolute path of the RCS object
160 the relative path of the RCS object based on the directory above
161 """
162 rcs_obj_dir = os.path.dirname(os.path.abspath(rcs_obj))
163 if os.path.isdir(os.path.join(rcs_obj_dir, self.RCS_METADIR)):
164
165
166 location_abs = os.path.abspath(rcs_obj)
167 location_rel = os.path.basename(location_abs)
168 return (rcs_obj_dir, location_abs, location_rel)
169 elif self.SCAN_PARENTS:
170
171
172 return self._find_rcs_in_parent_directories(rcs_obj)
173 else:
174
175 return None
176
178 """Try to find the metadata directory in all parent directories"""
179
180 current_dir = os.path.dirname(os.path.realpath(rcs_obj))
181
182 max_depth = 64
183
184 while not os.path.isdir(os.path.join(current_dir, self.RCS_METADIR)):
185 if os.path.dirname(current_dir) == current_dir:
186
187 return None
188 if max_depth <= 0:
189
190 return None
191
192 current_dir = os.path.dirname(current_dir)
193
194
195 rcs_dir = current_dir
196 location_abs = os.path.realpath(rcs_obj)
197
198 basedir = rcs_dir + os.path.sep
199 if location_abs.startswith(basedir):
200
201 location_rel = location_abs.replace(basedir, "", 1)
202
203 return (rcs_dir, location_abs, location_rel)
204 else:
205
206 return None
207
209 """Check if all necessary attributes are defined
210
211 Useful to make sure, that a new implementation does not forget
212 something like "RCS_METADIR"
213 """
214 if self.RCS_METADIR is None:
215 raise IOError("Incomplete RCS interface implementation: " \
216 + "self.RCS_METADIR is None")
217 if self.SCAN_PARENTS is None:
218 raise IOError("Incomplete RCS interface implementation: " \
219 + "self.SCAN_PARENTS is None")
220
221
222 return True
223
225 """Dummy to be overridden by real implementations"""
226 raise NotImplementedError("Incomplete RCS interface implementation:" \
227 + " 'getcleanfile' is missing")
228
229
230 - def commit(self, revision=None):
231 """Dummy to be overridden by real implementations"""
232 raise NotImplementedError("Incomplete RCS interface implementation:" \
233 + " 'commit' is missing")
234
235
236 - def update(self, revision=None):
237 """Dummy to be overridden by real implementations"""
238 raise NotImplementedError("Incomplete RCS interface implementation:" \
239 + " 'update' is missing")
240
241
246 """return a list of objcts, each pointing to a file below this directory
247 """
248 rcs_objs = []
249
250 def scan_directory(arg, dirname, fnames):
251 for fname in fnames:
252 full_fname = os.path.join(dirname, fname)
253 if os.path.isfile(full_fname):
254 try:
255 rcs_objs.append(get_versioned_object(full_fname,
256 versioning_systems, follow_symlinks))
257 except IOError:
258 pass
259
260 os.path.walk(location, scan_directory, None)
261 return rcs_objs
262
267 """return a versioned object for the given file"""
268
269 for vers_sys in versioning_systems:
270 try:
271 vers_sys_class = __get_rcs_class(vers_sys)
272 if not vers_sys_class is None:
273 return vers_sys_class(location)
274 except IOError:
275 continue
276
277 if follow_symlinks and os.path.islink(location):
278 return get_versioned_object(os.path.realpath(location),
279 versioning_systems = versioning_systems,
280 follow_symlinks = False)
281
282 raise IOError("Could not find version control information: %s" % location)
283
284
287
290
293
295 """commit all files below the given directory
296
297 files that are just symlinked into the directory are supported, too
298 """
299
300
301 for rcs_obj in get_versioned_objects_recursive(directory):
302 rcs_obj.commit(message)
303
313
321
322
323
324 if __name__ == "__main__":
325 import sys
326 filenames = sys.argv[1:]
327 for filename in filenames:
328 contents = getcleanfile(filename)
329 sys.stdout.write("\n\n******** %s ********\n\n" % filename)
330 sys.stdout.write(contents)
331