1   
  2   
  3   
  4   
  5   
  6   
  7   
  8   
  9   
 10   
 11   
 12   
 13   
 14   
 15   
 16   
 17  """ 
 18  Access to the subversion respository 
 19   
 20  @var version: The version of the subversion library (maj, min, pat, rev) 
 21  @type version: C{_Version} 
 22   
 23  @var _SVN_INVALID_REVNUM: the invalid revision number 
 24  @type _SVN_INVALID_REVNUM: C{int} 
 25  """ 
 26  __author__    = "André Malo" 
 27  __docformat__ = "epytext en" 
 28  __all__       = [ 
 29      'version', 'Repository', 'Error', 'RepositoryError', 'isUnicodeProperty' 
 30      'VersionedPathDescriptor', 'LockedPathDescriptor', 
 31  ] 
 32   
 33   
 34   
 35  import os 
 36  from svn import core as svn_core 
 37  from svn import repos as svn_repos 
 38  from svn import fs as svn_fs 
 39  from svn import delta as svn_delta 
 40   
 41  _SVN_INVALID_REVNUM = svn_core.SWIG_SVN_INVALID_REVNUM 
 42   
 43   
 44  Error = svn_core.SubversionException 
 45   
 46   
 48      """ SVN version container class 
 49   
 50          @ivar major: Major version 
 51          @type major: C{int} 
 52   
 53          @ivar minor: Minor version 
 54          @type minor: C{int} 
 55   
 56          @ivar patch: Patch level 
 57          @type patch: C{int} 
 58   
 59          @ivar revision: Revision number 
 60          @type revision: C{int} 
 61   
 62          @ivar tag: Additional tag 
 63          @type tag: C{str} 
 64   
 65          @ivar min_1_2: SVN >= 1.2? 
 66          @type min_1_2: C{bool} 
 67      """ 
 68   
 70          """ Initialization """ 
 71          self.major = svn_core.SVN_VER_MAJOR 
 72          self.minor = svn_core.SVN_VER_MINOR 
 73          self.revision = svn_core.SVN_VER_REVISION 
 74          self.tag = svn_core.SVN_VER_TAG 
 75          try: 
 76              self.patch = svn_core.SVN_VER_PATCH  
 77          except AttributeError: 
 78              self.patch = svn_core.SVN_VER_MICRO  
 79   
 80          self.min_1_2 = bool( 
 81              self.major > 1 or (self.major == 1 and self.minor >= 2) 
 82          ) 
 83          self.min_1_7 = bool( 
 84              self.major > 1 or (self.major == 1 and self.minor >= 7) 
 85          ) 
   86   
 87  version = _Version() 
 88   
 89   
 91      """ Returns if the supplied name represents a translated property 
 92   
 93          @param name: The property name 
 94          @type name: C{str} 
 95   
 96          @return: The decision 
 97          @rtype: C{bool} 
 98      """ 
 99      return bool(svn_core.svn_prop_needs_translation(name)) 
 100   
101   
103      """ Returns True if the supplied mime type represents a binary 
104   
105          @param mtype: The mime type 
106          @type mtype: C{str} 
107   
108          @return: The decision 
109          @rtype: C{bool} 
110      """ 
111      return bool(mtype and 
112          svn_core.svn_mime_type_is_binary(mtype) 
113      ) 
 114   
115   
117      """ A repository error occured 
118   
119          @ivar svn_err_code: The SVN error code 
120          @type svn_err_code: C{int} 
121   
122          @ivar svn_err_name: The name of the SVN error (if it could be mapped) 
123          @type svn_err_name: C{str} 
124   
125          @ivar svn_err_str: The SVN error description 
126          @type svn_err_str: C{str} 
127      """ 
128   
130          """ Initialization 
131   
132              @param sexc: C{svnmailer.subversion.Error} 
133              @type sexc: C{svnmailer.subversion.Error} 
134          """ 
135          Exception.__init__(self) 
136          self.svn_err_str, self.svn_err_code = sexc.args 
137          self.svn_err_name = dict([(getattr(svn_core, var), var) 
138              for var in vars(svn_core) if var.startswith('SVN_ERR') 
139          ]).get(self.svn_err_code, 'unknown APR code') 
 140   
141   
143          """ Human readable representation 
144   
145              @return: The string representation 
146              @rtype: C{str} 
147          """ 
148          return str((self.svn_err_code, self.svn_err_name, self.svn_err_str)) 
  149   
150   
151   
153      """ Access to the subversion repository 
154   
155          @cvar _pool: C{None} 
156          @ivar _pool: main APR pool 
157          @type _pool: swig object 
158   
159          @cvar _apr_initialized: C{False} 
160          @ivar _apr_initialized: is APR initialized? 
161          @type _apr_initialized: C{bool} 
162   
163          @ivar _repos: Reference to the open repository 
164          @type _repos: swig object 
165   
166          @ivar _fs: Reference to the repos filesystem 
167          @type _fs: swig object 
168   
169          @ivar _revRoots: Cached revision root objects 
170          @type _revRoots: C{dict} 
171   
172          @ivar _revChanges: Cached revision change lists 
173          @type _revChanges: C{dict} 
174   
175          @ivar _revProps: Cached revision properties 
176          @type _revProps: C{dict} 
177   
178          @ivar _revTimes: Cached revision times 
179          @type _revTimes: C{dict} 
180   
181          @ivar _pathProps: Cached path properties 
182          @type _pathProps: C{dict} 
183   
184          @ivar _pathPropLists: Cached path propery lists 
185          @type _pathPropLists: C{dict} 
186   
187          @ivar path: The path to the repository 
188          @type path: C{unicode} 
189      """ 
190      _pool = None 
191      _apr_initialized = False 
192   
193   
195          """ Open the repository 
196   
197              @param repos_path: The repository path as unicode 
198              @type repos_path: C{unicode} 
199          """ 
200           
201          svn_core.apr_initialize() 
202          self._apr_initialized = True 
203   
204           
205          self._pool = svn_core.svn_pool_create(None) 
206   
207           
208          repos_path = repos_path.encode("utf-8", "strict") 
209          repos_path = os.path.normpath(os.path.abspath(repos_path)) 
210   
211           
212          self._repos = svn_repos.svn_repos_open(repos_path, self._pool) 
213          self._fs = svn_repos.svn_repos_fs(self._repos) 
214   
215           
216          self._revRoots = {} 
217          self._revChanges = {} 
218          self._revProps = {} 
219          self._revTimes = {} 
220          self._pathProps = {} 
221          self._pathPropLists = {} 
222          self.path = repos_path 
 223   
224   
226          """ Destroy the pool and release the shared lock """ 
227          try: 
228              if self._pool: 
229                  pool = self._pool 
230                  self._pool = None 
231                  svn_core.svn_pool_destroy(pool) 
232          finally: 
233              if self._apr_initialized: 
234                  self._apr_initialized = False 
235                  svn_core.apr_terminate() 
 236   
237   
239          """ Return the list of changes of a revisions sorted by path 
240   
241              @param revision: The revision 
242              @type revision: C{int} 
243   
244              @return: The Changes list 
245              @rtype: C{list} 
246          """ 
247          try: 
248              changelist = self._revChanges[revision] 
249          except KeyError: 
250              editor = self._getChangeCollector(revision) 
251              e_pool = editor.getPool() 
252              e_ptr, e_baton = svn_delta.make_editor(editor, e_pool) 
253              svn_repos.svn_repos_replay( 
254                  self._getRevisionRoot(revision), e_ptr, e_baton, e_pool 
255              ) 
256   
257              e_changes = (version.min_1_2 and 
258                  [editor.get_changes()] or [editor.changes])[0] 
259              changelist = [VersionedPathDescriptor(self, path, revision, change) 
260                  for path, change in e_changes.items() 
261              ] 
262              changelist.sort() 
263   
264               
265              self._revChanges[revision] = changelist 
266   
267              del editor  
268   
269          return changelist 
 270   
271   
273          """ Get a dict of properties for a particular path/revision 
274   
275              @param path: The path 
276              @type path: C{str} 
277   
278              @param revision: The revision number 
279              @type revision: C{int} 
280   
281              @return: The dict of properties 
282              @rtype: C{dict} 
283          """ 
284          try: 
285              plist = self._pathPropLists[(path, revision)] 
286          except KeyError: 
287              rev_root = self._getRevisionRoot(revision) 
288              plist = self._pathPropLists[(path, revision)] = dict([ 
289                  (key, str(value)) for key, value in 
290                  svn_fs.node_proplist(rev_root, path, self._pool).items() 
291              ]) 
292   
293          return plist 
 294   
295   
297          """ Get the value of a particular property 
298   
299              @param name: The name of the property 
300              @type name: C{str} 
301   
302              @param path: The path the property is attached to 
303              @type path: C{str} 
304   
305              @param revision: The revision number 
306              @type revision: C{int} 
307   
308              @return: The property value or C{None} if the property 
309                  doesn't exist. 
310              @rtype: C{str} 
311          """ 
312          try: 
313              value = self._pathProps[(name, path, revision)] 
314          except KeyError: 
315              root = self._getRevisionRoot(revision) 
316              value = self._pathProps[(name, path, revision)] = svn_fs.node_prop( 
317                  root, path, name, self._pool 
318              ) 
319   
320          return value 
 321   
322   
324          """ Get the MIME type of a particular path 
325   
326              @param path: The path 
327              @type path: C{str} 
328   
329              @param revision: The revision number 
330              @type revision: C{int} 
331   
332              @return: The mime type or C{None} 
333              @rtype: C{str} 
334          """ 
335          return self.getPathProperty( 
336              svn_core.SVN_PROP_MIME_TYPE, path, revision 
337          ) 
 338   
339   
340 -    def dumpPathContent(self, fp, path, revision): 
 341          """ Dump the contents of a particular path into a file 
342   
343              @param fp: The file descriptor 
344              @type fp: file like object 
345   
346              @param path: The path to process 
347              @type path: C{str} 
348   
349              @param revision: The revision number 
350              @type revision: C{int} 
351          """ 
352          pool = svn_core.svn_pool_create(self._pool) 
353   
354          try: 
355              root = self._getRevisionRoot(revision) 
356              stream = svn_fs.file_contents(root, path, pool) 
357   
358              try: 
359                  while True: 
360                      chunk = svn_core.svn_stream_read( 
361                          stream, svn_core.SVN_STREAM_CHUNK_SIZE 
362                      ) 
363                      if not chunk: 
364                          break 
365   
366                      fp.write(chunk) 
367              finally: 
368                  svn_core.svn_stream_close(stream) 
369          finally: 
370              svn_core.svn_pool_destroy(pool) 
 371   
372   
374          """ Returns the time of a particular rev. in seconds since epoch 
375   
376              @param revision: The revision number 
377              @type revision: C{int} 
378   
379              @return: The time 
380              @rtype: C{int} 
381          """ 
382          try: 
383              rtime = self._revTimes[revision] 
384          except KeyError: 
385              svndate = self.getRevisionProperty( 
386                  revision, svn_core.SVN_PROP_REVISION_DATE 
387              ) 
388              rtime = self._revTimes[revision] = svn_core.secs_from_timestr( 
389                  svndate, self._pool 
390              ) 
391   
392          return rtime 
 393   
394   
396          """ Returns the author of a particular revision 
397   
398              @param revision: The revision number 
399              @type revision: C{int} 
400   
401              @return: The author 
402              @rtype: C{str} 
403          """ 
404          return self.getRevisionProperty( 
405              revision, svn_core.SVN_PROP_REVISION_AUTHOR 
406          ) 
 407   
408   
410          """ Returns the log entry of a particular revision 
411   
412              @param revision: The revision number 
413              @type revision: C{int} 
414   
415              @return: The log entry or C{None} 
416              @rtype: C{str} 
417          """ 
418          return self.getRevisionProperty( 
419              revision, svn_core.SVN_PROP_REVISION_LOG 
420          ) 
 421   
422   
424          """ Returns the value of a revision property 
425   
426              @param propname: The property name 
427              @type propname: C{str} 
428   
429              @param revision: The revision number 
430              @type revision: C{int} 
431   
432              @return: The property value 
433              @rtype: C{str} 
434          """ 
435          try: 
436              value = self._revProps[(revision, propname)] 
437          except KeyError: 
438              value = self._revProps[(revision, propname)] = \ 
439                  svn_fs.revision_prop(self._fs, revision, propname, self._pool) 
440   
441          return value 
 442   
443   
445          """ Return the RevisionChangeCollector instance 
446   
447              @param revision: The revision 
448              @type revision: C{int} 
449   
450              @return: The Collector instance 
451              @rtype: C{_RevisionChangeCollector} 
452          """ 
453          return _RevisionChangeCollector(self, revision) 
 454   
455   
457          """ Return the root object of a particular revision 
458   
459              @note: The root objects are cached 
460   
461              @param revision: The revision number 
462              @type revision: C{int} 
463   
464              @return: The revision root 
465              @rtype: swig object 
466          """ 
467          try: 
468              root = self._revRoots[revision] 
469          except KeyError: 
470              root = self._revRoots[revision] = svn_fs.revision_root( 
471                  self._fs, revision, self._pool 
472              ) 
473   
474          return root 
  475   
476   
478      """ Describes the basic information of a particular path 
479   
480          @ivar path: The path, we're talking about 
481          @type path: C{str} 
482   
483          @ivar repos: The repository this change belongs to 
484          @type repos: C{Repository} 
485      """ 
486   
487 -    def __init__(self, repos, path, *args, **kwargs): 
 488          """ Initialization 
489   
490              @note: Don't override this method, override L{init} 
491                  instead. 
492   
493              @param repos: The repository reference 
494              @type repos: C{Repository} 
495   
496              @param path: The path 
497              @type path: C{str} 
498          """ 
499          self.repos = repos 
500          self.path = (path[:1] == '/' and [path[1:]] or [path])[0] 
501   
502          self.init(*args, **kwargs) 
 503   
504   
505 -    def init(self, *args, **kwargs): 
 506          """ Custom initialization """ 
507          pass 
 508   
509   
511          """ Compares two change objects by path 
512   
513              @param other: The object compared to 
514              @type other: hopefully C{VersionedPathDescriptor} 
515   
516              @return: Returns -1, 0 or 1 
517              @rtype: C{int} 
518          """ 
519          return cmp(self.path, other.path) 
 520   
521   
523          """ Returns whether the path is a directory 
524   
525              @return: is a directory? 
526              @rtype: C{bool} 
527          """ 
528          raise NotImplementedError() 
  529   
530   
532      """ Describes the lock status of a particular path 
533   
534          @ivar is_locked: is locked? 
535          @type is_locked: C{bool} 
536      """ 
537 -    def init(self, *args, **kwargs): 
 538          """ Custom initialization """ 
539          self._init(*args, **kwargs) 
 540   
541   
542 -    def _init(self, is_locked): 
 543          """ Initialization """ 
544          self.is_locked = is_locked 
545          self._cache = None 
 546   
547   
549          """ Returns whether the path is a directory """ 
550           
551          return False 
 552   
553   
555          """ Returns the lock description object """ 
556          if self._cache is None: 
557              self._cache = svn_fs.get_lock( 
558                  self.repos._fs, self.path, self.repos._pool 
559              ) 
560   
561          return self._cache 
 562   
563   
 571   
572   
574      """ Describes the changes of a particular path 
575   
576          This is a wrapper around svn_repos.ChangedPath instances. 
577          outside of this module one shouldn't need to deal with these. 
578   
579          @ivar revision: The revision number 
580          @type revision: C{int} 
581   
582          @ivar _change: The change 
583          @type _change: C{svn_repos.ChangedPath} 
584      """ 
585 -    def init(self, *args, **kwargs): 
 586          """ Custom initialization """ 
587          self._init(*args, **kwargs) 
 588   
589   
590 -    def _init(self, revision, change): 
 591          """ Initialization 
592   
593              @param revision: The revision number 
594              @type revision: C{int} 
595   
596              @param change: The change specification 
597              @type change: C{svn_repos.ChangedPath} 
598          """ 
599          self.revision = revision 
600          self._change = change 
 601   
602   
604          """ Returns the revision number of the original path 
605   
606              @return: The revision number 
607              @rtype: C{int} 
608          """ 
609          return self._change.base_rev 
 610   
611   
613          """ Returns the original path 
614   
615              @return: The path 
616              @rtype: C{str} 
617          """ 
618           
619          return (self._change.base_path[:1] == '/' and 
620              [self._change.base_path[1:]] or [self._change.base_path])[0] 
 621   
622   
624          """ Returns the dict of modified properties 
625   
626              The dict contains the property names as keys and 
627              a 2-tuple as value where the first element contains the 
628              old property value and second element the new one. 
629   
630              @return: The dict of changed properties 
631              @rtype: C{dict} 
632          """ 
633          if type(self._change.prop_changes) == type({}): 
634              return self._change.prop_changes 
635   
636          if not self._change.prop_changes: 
637              return {} 
638   
639           
640          if self.wasAdded(): 
641              propdict1 = {} 
642          else: 
643              propdict1 = self.repos.getPathProperties( 
644                  self.getBasePath(), self.getBaseRevision() 
645              ) 
646   
647          if self.wasDeleted(): 
648              propdict2 = {} 
649          else: 
650              propdict2 = self.repos.getPathProperties( 
651                  self.path, self.revision 
652              ) 
653   
654           
655           
656          self._change.prop_changes = {} 
657          for name, value1, value2 in [ 
658                  (key, propdict1.get(key), propdict2.get(key)) for key in 
659                  dict.fromkeys(propdict1.keys() + propdict2.keys()).keys()]: 
660   
661              if value1 != value2: 
662                  self._change.prop_changes[name] = (value1, value2) 
663   
664          return self._change.prop_changes 
 665   
666   
668          """ Returns whether the path is a directory """ 
669          return bool(self._change.item_kind == svn_core.svn_node_dir) 
 670   
671   
690   
691   
693          """ Returns whether the path has property changes 
694   
695              @return: has property changes? 
696              @rtype: C{bool} 
697          """ 
698          return bool(self._change.prop_changes) 
 699   
700   
702          """ Returns whether the path has content changes 
703   
704              @return: has content changes? 
705              @rtype: C{bool} 
706          """ 
707          return bool(self._change.text_changed) 
 708   
709   
711          """ Returns whether the path was deleted 
712   
713              @return: was deleted? 
714              @rtype: C{bool} 
715          """ 
716          if version.min_1_7: 
717              return self._change.action == svn_repos.CHANGE_ACTION_DELETE 
718          return bool(self._change.path is None) 
 719   
720   
722          """ Returns whether the path was added 
723   
724              @return: was added? 
725              @rtype: C{bool} 
726          """ 
727          return bool(self._change.added) 
 728   
729   
731          """ Returns whether the path was just modified 
732   
733              @return: was modified? 
734              @rtype: C{bool} 
735          """ 
736          return bool(not self._change.added and not self.wasDeleted()) 
 737   
738   
740          """ Returns whether the path was copied 
741   
742              @return: was copied? 
743              @rtype: C{bool} 
744          """ 
745          return bool( 
746              self._change.added and self._change.base_path and 
747              self._change.base_rev != _SVN_INVALID_REVNUM 
748          ) 
  749   
750   
751  if version.min_1_2: 
752 -    class Collector(svn_repos.ChangeCollector, object): 
 753          """ svn 1.2 collector """ 
754          pass 
 755  else: 
756 -    class Collector(svn_repos.RevisionChangeCollector, object): 
 757          """ svn 1.[01] collector """ 
758          pass 
 759   
760   
762      """ Collect all changes between two particular revisions 
763   
764          @cvar __pool: C{None} 
765          @ivar __pool: The APR subpool 
766          @type __pool: swig object 
767      """ 
768      __pool = None 
769   
788   
789   
791          """ Destroy the subpool """ 
792          if self.__pool: 
793              pool = self.__pool 
794              self.__pool = None 
795              svn_core.svn_pool_destroy(pool) 
 796   
797   
799          """ Returns the subpool 
800   
801              @return: the pool 
802              @rtype: swig object 
803          """ 
804          return self.__pool 
 805   
806   
808          """ Return the root of a particular revision 
809   
810              @note: The root objects are cached 
811   
812              @param rev: The revision number 
813              @type rev: C{int} 
814   
815              @return: The revision root 
816              @rtype: swig object 
817          """ 
818          return self.__repos._getRevisionRoot(rev) 
  819