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