Package translate :: Package storage :: Package versioncontrol
[hide private]
[frames] | no frames]

Source Code for Package translate.storage.versioncontrol

  1  #!/usr/bin/env python 
  2  # -*- coding: utf-8 -*- 
  3  #  
  4  # Copyright 2004-2007 Zuza Software Foundation 
  5  #  
  6  # This file is part of translate. 
  7  # 
  8  # translate is free software; you can redistribute it and/or modify 
  9  # it under the terms of the GNU General Public License as published by 
 10  # the Free Software Foundation; either version 2 of the License, or 
 11  # (at your option) any later version. 
 12  #  
 13  # translate is distributed in the hope that it will be useful, 
 14  # but WITHOUT ANY WARRANTY; without even the implied warranty of 
 15  # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the 
 16  # GNU General Public License for more details. 
 17  # 
 18  # You should have received a copy of the GNU General Public License 
 19  # along with translate; if not, write to the Free Software 
 20  # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA 
 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   
48 -def __get_rcs_class(name):
49 if not name in __CACHED_RCS_CLASSES: 50 try: 51 module = __import__("translate.storage.versioncontrol.%s" % name, 52 globals(), {}, name) 53 rcs_class = getattr(module, name) 54 except [ImportError, AttributeError]: 55 return None 56 __CACHED_RCS_CLASSES[name] = rcs_class 57 return __CACHED_RCS_CLASSES[name]
58 59 60 # use either 'popen2' or 'subprocess' for command execution 61 try: 62 # available for python >= 2.4 63 import subprocess 64 65 # The subprocess module allows to use cross-platform command execution 66 # without using the shell (increases security). 67
68 - def run_command(command):
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 # ok - we use "subprocess" 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 # fallback for python < 2.4 83 import popen2 84
85 - def run_command(command):
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
100 -def __shellescape(path):
101 """Shell-escape any non-alphanumeric characters.""" 102 return re.sub(r'(\W)', r'\\\1', path)
103 104
105 -class GenericRevisionControlSystem:
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
138 - def __init__(self, location):
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 # check if the implementation looks ok - otherwise raise IOError 145 self._self_check() 146 # search for the repository information 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
154 - def _find_rcs_directory(self, rcs_obj):
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 # is there a metadir next to the rcs_obj? 165 # (for Subversion, CVS, ...) 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 # scan for the metadir in parent directories 171 # (for bzr, GIT, Darcs, ...) 172 return self._find_rcs_in_parent_directories(rcs_obj) 173 else: 174 # no RCS metadata found 175 return None
176
177 - def _find_rcs_in_parent_directories(self, rcs_obj):
178 """Try to find the metadata directory in all parent directories""" 179 # first: resolve possible symlinks 180 current_dir = os.path.dirname(os.path.realpath(rcs_obj)) 181 # prevent infite loops 182 max_depth = 64 183 # stop as soon as we find the metadata directory 184 while not os.path.isdir(os.path.join(current_dir, self.RCS_METADIR)): 185 if os.path.dirname(current_dir) == current_dir: 186 # we reached the root directory - stop 187 return None 188 if max_depth <= 0: 189 # some kind of dead loop or a _very_ deep directory structure 190 return None 191 # go to the next higher level 192 current_dir = os.path.dirname(current_dir) 193 # the loop was finished successfully 194 # i.e.: we found the metadata directory 195 rcs_dir = current_dir 196 location_abs = os.path.realpath(rcs_obj) 197 # strip the base directory from the path of the rcs_obj 198 basedir = rcs_dir + os.path.sep 199 if location_abs.startswith(basedir): 200 # remove the base directory (including the trailing slash) 201 location_rel = location_abs.replace(basedir, "", 1) 202 # successfully finished 203 return (rcs_dir, location_abs, location_rel) 204 else: 205 # this should never happen 206 return None
207
208 - def _self_check(self):
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 # we do not check for implemented functions - they raise 221 # NotImplementedError exceptions anyway 222 return True
223
224 - def getcleanfile(self, revision=None):
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
242 -def get_versioned_objects_recursive( 243 location, 244 versioning_systems=DEFAULT_RCS, 245 follow_symlinks=True):
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
263 -def get_versioned_object( 264 location, 265 versioning_systems=DEFAULT_RCS, 266 follow_symlinks=True):
267 """return a versioned object for the given file""" 268 # go through all RCS and return a versioned object if possible 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 # if 'location' is a symlink, then we should try the original file 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 # if everything fails: 282 raise IOError("Could not find version control information: %s" % location)
283 284 # stay compatible to the previous version
285 -def updatefile(filename):
286 return get_versioned_object(filename).update()
287
288 -def getcleanfile(filename, revision=None):
289 return get_versioned_object(filename).getcleanfile(revision)
290
291 -def commitfile(filename, message=None):
292 return get_versioned_object(filename).commit(message)
293
294 -def commitdirectory(directory, message=None):
295 """commit all files below the given directory 296 297 files that are just symlinked into the directory are supported, too 298 """ 299 # for now all files are committed separately 300 # should we combine them into one commit? 301 for rcs_obj in get_versioned_objects_recursive(directory): 302 rcs_obj.commit(message)
303
304 -def updatedirectory(directory):
305 """update all files below the given directory 306 307 files that are just symlinked into the directory are supported, too 308 """ 309 # for now all files are updated separately 310 # should we combine them into one update? 311 for rcs_obj in get_versioned_objects_recursive(directory): 312 rcs_obj.update()
313
314 -def hasversioning(item):
315 try: 316 # try all available version control systems 317 get_versioned_object(item) 318 return True 319 except IOError: 320 return False
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