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 authentication 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 os
34 import re
35
36 DEFAULT_RCS = ["svn", "cvs", "darcs", "git", "bzr", "hg"]
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
48
67
68
69
70 try:
71
72 import subprocess
73
74
75
76
78 """Runs a command (array of program name and arguments) and returns the
79 exitcode, the output and the error as a tuple.
80
81 @param command: list of arguments to be joined for a program call
82 @type command: list
83 @param cwd: optional directory where the command should be executed
84 @type cwd: str
85 """
86
87 try:
88 proc = subprocess.Popen(args=command,
89 stdout=subprocess.PIPE,
90 stderr=subprocess.PIPE,
91 stdin=subprocess.PIPE,
92 cwd=cwd)
93 (output, error) = proc.communicate()
94 ret = proc.returncode
95 return ret, output, error
96 except OSError, err_msg:
97
98 return -1, "", err_msg
99
100 except ImportError:
101
102 import popen2
103
105 """Runs a command (array of program name and arguments) and returns the
106 exitcode, the output and the error as a tuple.
107
108 There is no need to check for exceptions (like for subprocess above),
109 since popen2 opens a shell that will fail with an error code in case
110 of a missing executable.
111
112 @param command: list of arguments to be joined for a program call
113 @type command: list
114 @param cwd: optional directory where the command should be executed
115 @type cwd: str
116 """
117 escaped_command = " ".join([__shellescape(arg) for arg in command])
118 if cwd:
119
120
121 escaped_command = "cd %s; %s" % (__shellescape(cwd), escaped_command)
122 proc = popen2.Popen3(escaped_command, True)
123 (c_stdin, c_stdout, c_stderr) = (proc.tochild, proc.fromchild, proc.childerr)
124 output = c_stdout.read()
125 error = c_stderr.read()
126 ret = proc.wait()
127 c_stdout.close()
128 c_stderr.close()
129 c_stdin.close()
130 return ret, output, error
131
132
134 """Shell-escape any non-alphanumeric characters."""
135 return re.sub(r'(\W)', r'\\\1', path)
136
137
139 """The super class for all version control classes.
140
141 Always inherit from this class to implement another RC interface.
142
143 At least the two attributes "RCS_METADIR" and "SCAN_PARENTS" must be
144 overriden by all implementations that derive from this class.
145
146 By default, all implementations can rely on the following attributes:
147 - root_dir: the parent of the metadata directory of the working copy
148 - location_abs: the absolute path of the RCS object
149 - location_rel: the path of the RCS object relative to 'root_dir'
150 """
151
152 RCS_METADIR = None
153 """The name of the metadata directory of the RCS
154
155 e.g.: for Subversion -> ".svn"
156 """
157
158 SCAN_PARENTS = None
159 """whether to check the parent directories for the metadata directory of
160 the RCS working copy
161
162 some revision control systems store their metadata directory only
163 in the base of the working copy (e.g. bzr, GIT and Darcs)
164 use "True" for these RCS
165
166 other RCS store a metadata directory in every single directory of
167 the working copy (e.g. Subversion and CVS)
168 use "False" for these RCS
169 """
170
172 """find the relevant information about this RCS object
173
174 The IOError exception indicates that the specified object (file or
175 directory) is not controlled by the given version control system.
176 """
177
178 self._self_check()
179
180 result = self._find_rcs_directory(location)
181 if result is None:
182 raise IOError("Could not find revision control information: %s" \
183 % location)
184 else:
185 self.root_dir, self.location_abs, self.location_rel = result
186
188 """Try to find the metadata directory of the RCS
189
190 @rtype: tuple
191 @return:
192 - the absolute path of the directory, that contains the metadata directory
193 - the absolute path of the RCS object
194 - the relative path of the RCS object based on the directory above
195 """
196 if os.path.isdir(os.path.abspath(rcs_obj)):
197 rcs_obj_dir = os.path.abspath(rcs_obj)
198 else:
199 rcs_obj_dir = os.path.dirname(os.path.abspath(rcs_obj))
200
201 if os.path.isdir(os.path.join(rcs_obj_dir, self.RCS_METADIR)):
202
203
204 location_abs = os.path.abspath(rcs_obj)
205 location_rel = os.path.basename(location_abs)
206 return (rcs_obj_dir, location_abs, location_rel)
207 elif self.SCAN_PARENTS:
208
209
210 return self._find_rcs_in_parent_directories(rcs_obj)
211 else:
212
213 return None
214
216 """Try to find the metadata directory in all parent directories"""
217
218 current_dir = os.path.dirname(os.path.realpath(rcs_obj))
219
220 max_depth = 64
221
222 while not os.path.isdir(os.path.join(current_dir, self.RCS_METADIR)):
223 if os.path.dirname(current_dir) == current_dir:
224
225 return None
226 if max_depth <= 0:
227
228 return None
229
230 current_dir = os.path.dirname(current_dir)
231
232
233 rcs_dir = current_dir
234 location_abs = os.path.realpath(rcs_obj)
235
236 basedir = rcs_dir + os.path.sep
237 if location_abs.startswith(basedir):
238
239 location_rel = location_abs.replace(basedir, "", 1)
240
241 return (rcs_dir, location_abs, location_rel)
242 else:
243
244 return None
245
247 """Check if all necessary attributes are defined
248
249 Useful to make sure, that a new implementation does not forget
250 something like "RCS_METADIR"
251 """
252 if self.RCS_METADIR is None:
253 raise IOError("Incomplete RCS interface implementation: " \
254 + "self.RCS_METADIR is None")
255 if self.SCAN_PARENTS is None:
256 raise IOError("Incomplete RCS interface implementation: " \
257 + "self.SCAN_PARENTS is None")
258
259
260 return True
261
263 """Dummy to be overridden by real implementations"""
264 raise NotImplementedError("Incomplete RCS interface implementation:" \
265 + " 'getcleanfile' is missing")
266
267 - def commit(self, revision=None, author=None):
268 """Dummy to be overridden by real implementations"""
269 raise NotImplementedError("Incomplete RCS interface implementation:" \
270 + " 'commit' is missing")
271
272 - def update(self, revision=None):
273 """Dummy to be overridden by real implementations"""
274 raise NotImplementedError("Incomplete RCS interface implementation:" \
275 + " 'update' is missing")
276
277
282 """return a list of objects, each pointing to a file below this directory
283 """
284 rcs_objs = []
285 if versioning_systems is None:
286 versioning_systems = DEFAULT_RCS
287
288 def scan_directory(arg, dirname, fnames):
289 for fname in fnames:
290 full_fname = os.path.join(dirname, fname)
291 if os.path.isfile(full_fname):
292 try:
293 rcs_objs.append(get_versioned_object(full_fname,
294 versioning_systems, follow_symlinks))
295 except IOError:
296 pass
297
298 os.path.walk(location, scan_directory, None)
299 return rcs_objs
300
301
306 """return a versioned object for the given file"""
307 if versioning_systems is None:
308 versioning_systems = DEFAULT_RCS
309
310 for vers_sys in versioning_systems:
311 try:
312 vers_sys_class = __get_rcs_class(vers_sys)
313 if not vers_sys_class is None:
314 return vers_sys_class(location)
315 except IOError:
316 continue
317
318 if follow_symlinks and os.path.islink(location):
319 return get_versioned_object(os.path.realpath(location),
320 versioning_systems=versioning_systems,
321 follow_symlinks=False)
322
323 raise IOError("Could not find version control information: %s" % location)
324
325
327 """ return the class objects of all locally available version control
328 systems
329 """
330 result = []
331 for rcs in DEFAULT_RCS:
332 rcs_class = __get_rcs_class(rcs)
333 if rcs_class:
334 result.append(rcs_class)
335 return result
336
337
338
340 return get_versioned_object(filename).update()
341
342
344 return get_versioned_object(filename).getcleanfile(revision)
345
346
347 -def commitfile(filename, message=None, author=None):
348 return get_versioned_object(filename).commit(message=message, author=author)
349
350
352 """commit all files below the given directory
353
354 files that are just symlinked into the directory are supported, too
355 """
356
357
358 for rcs_obj in get_versioned_objects_recursive(directory):
359 rcs_obj.commit(message=message, author=author)
360
361
363 """update all files below the given directory
364
365 files that are just symlinked into the directory are supported, too
366 """
367
368
369 for rcs_obj in get_versioned_objects_recursive(directory):
370 rcs_obj.update()
371
372
374 try:
375
376 get_versioned_object(item)
377 return True
378 except IOError:
379 return False
380
381
382 if __name__ == "__main__":
383 import sys
384 filenames = sys.argv[1:]
385 if filenames:
386
387 for filename in filenames:
388 contents = getcleanfile(filename)
389 sys.stdout.write("\n\n******** %s ********\n\n" % filename)
390 sys.stdout.write(contents)
391 else:
392
393
394
395 import translate.storage.versioncontrol
396
397 for rcs in get_available_version_control_systems():
398 print rcs
399