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