Package CedarBackup2 :: Package extend :: Module subversion
[hide private]
[frames] | no frames]

Source Code for Module CedarBackup2.extend.subversion

   1  # -*- coding: iso-8859-1 -*- 
   2  # vim: set ft=python ts=3 sw=3 expandtab: 
   3  # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # 
   4  # 
   5  #              C E D A R 
   6  #          S O L U T I O N S       "Software done right." 
   7  #           S O F T W A R E 
   8  # 
   9  # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # 
  10  # 
  11  # Copyright (c) 2005,2007 Kenneth J. Pronovici. 
  12  # All rights reserved. 
  13  # 
  14  # This program is free software; you can redistribute it and/or 
  15  # modify it under the terms of the GNU General Public License, 
  16  # Version 2, as published by the Free Software Foundation. 
  17  # 
  18  # This program is distributed in the hope that it will be useful, 
  19  # but WITHOUT ANY WARRANTY; without even the implied warranty of 
  20  # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 
  21  # 
  22  # Copies of the GNU General Public License are available from 
  23  # the Free Software Foundation website, http://www.gnu.org/. 
  24  # 
  25  # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # 
  26  # 
  27  # Author   : Kenneth J. Pronovici <pronovic@ieee.org> 
  28  # Language : Python (>= 2.3) 
  29  # Project  : Official Cedar Backup Extensions 
  30  # Revision : $Id: subversion.py 761 2007-07-05 03:04:54Z pronovic $ 
  31  # Purpose  : Provides an extension to back up Subversion repositories. 
  32  # 
  33  # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # 
  34   
  35  ######################################################################## 
  36  # Module documentation 
  37  ######################################################################## 
  38   
  39  """ 
  40  Provides an extension to back up Subversion databases. 
  41   
  42  This is a Cedar Backup extension used to back up Subversion repositories via 
  43  the Cedar Backup command line.  Each Subversion repository can be backed using 
  44  the same collect modes allowed for filesystems in the standard Cedar Backup 
  45  collect action: weekly, daily, incremental.   
  46   
  47  This extension requires a new configuration section <subversion> and is 
  48  intended to be run either immediately before or immediately after the standard 
  49  collect action.  Aside from its own configuration, it requires the options and 
  50  collect configuration sections in the standard Cedar Backup configuration file. 
  51   
  52  There are two different kinds of Subversion repositories at this writing: BDB 
  53  (Berkeley Database) and FSFS (a "filesystem within a filesystem").  Although 
  54  the repository type can be specified in configuration, that information is just 
  55  kept around for reference.  It doesn't affect the backup.  Both kinds of 
  56  repositories are backed up in the same way, using C{svnadmin dump} in an 
  57  incremental mode. 
  58   
  59  It turns out that FSFS repositories can also be backed up just like any 
  60  other filesystem directory.  If you would rather do that, then use the normal 
  61  collect action.  This is probably simpler, although it carries its own  
  62  advantages and disadvantages (plus you will have to be careful to exclude 
  63  the working directories Subversion uses when building an update to commit). 
  64  Check the Subversion documentation for more information. 
  65    
  66  @author: Kenneth J. Pronovici <pronovic@ieee.org> 
  67  """ 
  68   
  69  ######################################################################## 
  70  # Imported modules 
  71  ######################################################################## 
  72   
  73  # System modules 
  74  import os 
  75  import logging 
  76  import pickle 
  77  from bz2 import BZ2File 
  78  from gzip import GzipFile 
  79   
  80  # Cedar Backup modules 
  81  from CedarBackup2.xmlutil import createInputDom, addContainerNode, addStringNode 
  82  from CedarBackup2.xmlutil import isElement, readChildren, readFirstChild, readString, readStringList 
  83  from CedarBackup2.config import VALID_COLLECT_MODES, VALID_COMPRESS_MODES 
  84  from CedarBackup2.filesystem import FilesystemList 
  85  from CedarBackup2.util import UnorderedList, RegexList 
  86  from CedarBackup2.util import isStartOfWeek, buildNormalizedPath 
  87  from CedarBackup2.util import resolveCommand, executeCommand 
  88  from CedarBackup2.util import ObjectTypeList, encodePath, changeOwnership 
  89   
  90   
  91  ######################################################################## 
  92  # Module-wide constants and variables 
  93  ######################################################################## 
  94   
  95  logger = logging.getLogger("CedarBackup2.log.extend.subversion") 
  96   
  97  SVNLOOK_COMMAND      = [ "svnlook", ] 
  98  SVNADMIN_COMMAND     = [ "svnadmin", ] 
  99   
 100  REVISION_PATH_EXTENSION = "svnlast" 
 101   
 102   
 103  ######################################################################## 
 104  # RepositoryDir class definition 
 105  ######################################################################## 
 106   
107 -class RepositoryDir(object):
108 109 """ 110 Class representing Subversion repository directory. 111 112 A repository directory is a directory that contains one or more Subversion 113 repositories. 114 115 The following restrictions exist on data in this class: 116 117 - The directory path must be absolute. 118 - The collect mode must be one of the values in L{VALID_COLLECT_MODES}. 119 - The compress mode must be one of the values in L{VALID_COMPRESS_MODES}. 120 121 The repository type value is kept around just for reference. It doesn't 122 affect the behavior of the backup. 123 124 Relative exclusions are allowed here. However, there is no configured 125 ignore file, because repository dir backups are not recursive. 126 127 @sort: __init__, __repr__, __str__, __cmp__, directoryPat, collectMode, compressMode 128 """ 129
130 - def __init__(self, repositoryType=None, directoryPath=None, collectMode=None, compressMode=None, 131 relativeExcludePaths=None, excludePatterns=None):
132 """ 133 Constructor for the C{RepositoryDir} class. 134 135 @param repositoryType: Type of repository, for reference 136 @param directoryPath: Absolute path of the Subversion parent directory 137 @param collectMode: Overridden collect mode for this directory. 138 @param compressMode: Overridden compression mode for this directory. 139 @param relativeExcludePaths: List of relative paths to exclude. 140 @param excludePatterns: List of regular expression patterns to exclude 141 """ 142 self._repositoryType = None 143 self._directoryPath = None 144 self._collectMode = None 145 self._compressMode = None 146 self._relativeExcludePaths = None 147 self._excludePatterns = None 148 self.repositoryType = repositoryType 149 self.directoryPath = directoryPath 150 self.collectMode = collectMode 151 self.compressMode = compressMode 152 self.relativeExcludePaths = relativeExcludePaths 153 self.excludePatterns = excludePatterns
154
155 - def __repr__(self):
156 """ 157 Official string representation for class instance. 158 """ 159 return "RepositoryDir(%s, %s, %s, %s, %s, %s)" % (self.repositoryType, self.directoryPath, self.collectMode, 160 self.compressMode, self.relativeExcludePaths, self.excludePatterns)
161
162 - def __str__(self):
163 """ 164 Informal string representation for class instance. 165 """ 166 return self.__repr__()
167
168 - def __cmp__(self, other):
169 """ 170 Definition of equals operator for this class. 171 @param other: Other object to compare to. 172 @return: -1/0/1 depending on whether self is C{<}, C{=} or C{>} other. 173 """ 174 if other is None: 175 return 1 176 if self._repositoryType != other._repositoryType: 177 if self._repositoryType < other._repositoryType: 178 return -1 179 else: 180 return 1 181 if self._directoryPath != other._directoryPath: 182 if self._directoryPath < other._directoryPath: 183 return -1 184 else: 185 return 1 186 if self._collectMode != other._collectMode: 187 if self._collectMode < other._collectMode: 188 return -1 189 else: 190 return 1 191 if self._compressMode != other._compressMode: 192 if self._compressMode < other._compressMode: 193 return -1 194 else: 195 return 1 196 if self._relativeExcludePaths != other._relativeExcludePaths: 197 if self._relativeExcludePaths < other._relativeExcludePaths: 198 return -1 199 else: 200 return 1 201 if self._excludePatterns != other._excludePatterns: 202 if self._excludePatterns < other._excludePatterns: 203 return -1 204 else: 205 return 1 206 return 0
207
208 - def _setRepositoryType(self, value):
209 """ 210 Property target used to set the repository type. 211 There is no validation; this value is kept around just for reference. 212 """ 213 self._repositoryType = value
214
215 - def _getRepositoryType(self):
216 """ 217 Property target used to get the repository type. 218 """ 219 return self._repositoryType
220
221 - def _setDirectoryPath(self, value):
222 """ 223 Property target used to set the directory path. 224 The value must be an absolute path if it is not C{None}. 225 It does not have to exist on disk at the time of assignment. 226 @raise ValueError: If the value is not an absolute path. 227 @raise ValueError: If the value cannot be encoded properly. 228 """ 229 if value is not None: 230 if not os.path.isabs(value): 231 raise ValueError("Repository path must be an absolute path.") 232 self._directoryPath = encodePath(value)
233
234 - def _getDirectoryPath(self):
235 """ 236 Property target used to get the repository path. 237 """ 238 return self._directoryPath
239
240 - def _setCollectMode(self, value):
241 """ 242 Property target used to set the collect mode. 243 If not C{None}, the mode must be one of the values in L{VALID_COLLECT_MODES}. 244 @raise ValueError: If the value is not valid. 245 """ 246 if value is not None: 247 if value not in VALID_COLLECT_MODES: 248 raise ValueError("Collect mode must be one of %s." % VALID_COLLECT_MODES) 249 self._collectMode = value
250
251 - def _getCollectMode(self):
252 """ 253 Property target used to get the collect mode. 254 """ 255 return self._collectMode
256
257 - def _setCompressMode(self, value):
258 """ 259 Property target used to set the compress mode. 260 If not C{None}, the mode must be one of the values in L{VALID_COMPRESS_MODES}. 261 @raise ValueError: If the value is not valid. 262 """ 263 if value is not None: 264 if value not in VALID_COMPRESS_MODES: 265 raise ValueError("Compress mode must be one of %s." % VALID_COMPRESS_MODES) 266 self._compressMode = value
267
268 - def _getCompressMode(self):
269 """ 270 Property target used to get the compress mode. 271 """ 272 return self._compressMode
273
274 - def _setRelativeExcludePaths(self, value):
275 """ 276 Property target used to set the relative exclude paths list. 277 Elements do not have to exist on disk at the time of assignment. 278 """ 279 if value is None: 280 self._relativeExcludePaths = None 281 else: 282 try: 283 saved = self._relativeExcludePaths 284 self._relativeExcludePaths = UnorderedList() 285 self._relativeExcludePaths.extend(value) 286 except Exception, e: 287 self._relativeExcludePaths = saved 288 raise e
289
290 - def _getRelativeExcludePaths(self):
291 """ 292 Property target used to get the relative exclude paths list. 293 """ 294 return self._relativeExcludePaths
295
296 - def _setExcludePatterns(self, value):
297 """ 298 Property target used to set the exclude patterns list. 299 """ 300 if value is None: 301 self._excludePatterns = None 302 else: 303 try: 304 saved = self._excludePatterns 305 self._excludePatterns = RegexList() 306 self._excludePatterns.extend(value) 307 except Exception, e: 308 self._excludePatterns = saved 309 raise e
310
311 - def _getExcludePatterns(self):
312 """ 313 Property target used to get the exclude patterns list. 314 """ 315 return self._excludePatterns
316 317 repositoryType = property(_getRepositoryType, _setRepositoryType, None, doc="Type of this repository, for reference.") 318 directoryPath = property(_getDirectoryPath, _setDirectoryPath, None, doc="Absolute path of the Subversion parent directory.") 319 collectMode = property(_getCollectMode, _setCollectMode, None, doc="Overridden collect mode for this repository.") 320 compressMode = property(_getCompressMode, _setCompressMode, None, doc="Overridden compress mode for this repository.") 321 relativeExcludePaths = property(_getRelativeExcludePaths, _setRelativeExcludePaths, None, "List of relative paths to exclude.") 322 excludePatterns = property(_getExcludePatterns, _setExcludePatterns, None, "List of regular expression patterns to exclude.")
323 324 325 ######################################################################## 326 # Repository class definition 327 ######################################################################## 328
329 -class Repository(object):
330 331 """ 332 Class representing generic Subversion repository configuration.. 333 334 The following restrictions exist on data in this class: 335 336 - The respository path must be absolute. 337 - The collect mode must be one of the values in L{VALID_COLLECT_MODES}. 338 - The compress mode must be one of the values in L{VALID_COMPRESS_MODES}. 339 340 The repository type value is kept around just for reference. It doesn't 341 affect the behavior of the backup. 342 343 @sort: __init__, __repr__, __str__, __cmp__, repositoryPath, collectMode, compressMode 344 """ 345
346 - def __init__(self, repositoryType=None, repositoryPath=None, collectMode=None, compressMode=None):
347 """ 348 Constructor for the C{Repository} class. 349 350 @param repositoryType: Type of repository, for reference 351 @param repositoryPath: Absolute path to a Subversion repository on disk. 352 @param collectMode: Overridden collect mode for this directory. 353 @param compressMode: Overridden compression mode for this directory. 354 """ 355 self._repositoryType = None 356 self._repositoryPath = None 357 self._collectMode = None 358 self._compressMode = None 359 self.repositoryType = repositoryType 360 self.repositoryPath = repositoryPath 361 self.collectMode = collectMode 362 self.compressMode = compressMode
363
364 - def __repr__(self):
365 """ 366 Official string representation for class instance. 367 """ 368 return "Repository(%s, %s, %s, %s)" % (self.repositoryType, self.repositoryPath, self.collectMode, self.compressMode)
369
370 - def __str__(self):
371 """ 372 Informal string representation for class instance. 373 """ 374 return self.__repr__()
375
376 - def __cmp__(self, other):
377 """ 378 Definition of equals operator for this class. 379 @param other: Other object to compare to. 380 @return: -1/0/1 depending on whether self is C{<}, C{=} or C{>} other. 381 """ 382 if other is None: 383 return 1 384 if self._repositoryType != other._repositoryType: 385 if self._repositoryType < other._repositoryType: 386 return -1 387 else: 388 return 1 389 if self._repositoryPath != other._repositoryPath: 390 if self._repositoryPath < other._repositoryPath: 391 return -1 392 else: 393 return 1 394 if self._collectMode != other._collectMode: 395 if self._collectMode < other._collectMode: 396 return -1 397 else: 398 return 1 399 if self._compressMode != other._compressMode: 400 if self._compressMode < other._compressMode: 401 return -1 402 else: 403 return 1 404 return 0
405
406 - def _setRepositoryType(self, value):
407 """ 408 Property target used to set the repository type. 409 There is no validation; this value is kept around just for reference. 410 """ 411 self._repositoryType = value
412
413 - def _getRepositoryType(self):
414 """ 415 Property target used to get the repository type. 416 """ 417 return self._repositoryType
418
419 - def _setRepositoryPath(self, value):
420 """ 421 Property target used to set the repository path. 422 The value must be an absolute path if it is not C{None}. 423 It does not have to exist on disk at the time of assignment. 424 @raise ValueError: If the value is not an absolute path. 425 @raise ValueError: If the value cannot be encoded properly. 426 """ 427 if value is not None: 428 if not os.path.isabs(value): 429 raise ValueError("Repository path must be an absolute path.") 430 self._repositoryPath = encodePath(value)
431
432 - def _getRepositoryPath(self):
433 """ 434 Property target used to get the repository path. 435 """ 436 return self._repositoryPath
437
438 - def _setCollectMode(self, value):
439 """ 440 Property target used to set the collect mode. 441 If not C{None}, the mode must be one of the values in L{VALID_COLLECT_MODES}. 442 @raise ValueError: If the value is not valid. 443 """ 444 if value is not None: 445 if value not in VALID_COLLECT_MODES: 446 raise ValueError("Collect mode must be one of %s." % VALID_COLLECT_MODES) 447 self._collectMode = value
448
449 - def _getCollectMode(self):
450 """ 451 Property target used to get the collect mode. 452 """ 453 return self._collectMode
454
455 - def _setCompressMode(self, value):
456 """ 457 Property target used to set the compress mode. 458 If not C{None}, the mode must be one of the values in L{VALID_COMPRESS_MODES}. 459 @raise ValueError: If the value is not valid. 460 """ 461 if value is not None: 462 if value not in VALID_COMPRESS_MODES: 463 raise ValueError("Compress mode must be one of %s." % VALID_COMPRESS_MODES) 464 self._compressMode = value
465
466 - def _getCompressMode(self):
467 """ 468 Property target used to get the compress mode. 469 """ 470 return self._compressMode
471 472 repositoryType = property(_getRepositoryType, _setRepositoryType, None, doc="Type of this repository, for reference.") 473 repositoryPath = property(_getRepositoryPath, _setRepositoryPath, None, doc="Path to the repository to collect.") 474 collectMode = property(_getCollectMode, _setCollectMode, None, doc="Overridden collect mode for this repository.") 475 compressMode = property(_getCompressMode, _setCompressMode, None, doc="Overridden compress mode for this repository.")
476 477 478 ######################################################################## 479 # SubversionConfig class definition 480 ######################################################################## 481
482 -class SubversionConfig(object):
483 484 """ 485 Class representing Subversion configuration. 486 487 Subversion configuration is used for backing up Subversion repositories. 488 489 The following restrictions exist on data in this class: 490 491 - The collect mode must be one of the values in L{VALID_COLLECT_MODES}. 492 - The compress mode must be one of the values in L{VALID_COMPRESS_MODES}. 493 - The repositories list must be a list of C{Repository} objects. 494 - The repositoryDirs list must be a list of C{RepositoryDir} objects. 495 496 For the two lists, validation is accomplished through the 497 L{util.ObjectTypeList} list implementation that overrides common list 498 methods and transparently ensures that each element has the correct type. 499 500 @note: Lists within this class are "unordered" for equality comparisons. 501 502 @sort: __init__, __repr__, __str__, __cmp__, collectMode, compressMode, repositories 503 """ 504
505 - def __init__(self, collectMode=None, compressMode=None, repositories=None, repositoryDirs=None):
506 """ 507 Constructor for the C{SubversionConfig} class. 508 509 @param collectMode: Default collect mode. 510 @param compressMode: Default compress mode. 511 @param repositories: List of Subversion repositories to back up. 512 @param repositoryDirs: List of Subversion parent directories to back up. 513 514 @raise ValueError: If one of the values is invalid. 515 """ 516 self._collectMode = None 517 self._compressMode = None 518 self._repositories = None 519 self._repositoryDirs = None 520 self.collectMode = collectMode 521 self.compressMode = compressMode 522 self.repositories = repositories 523 self.repositoryDirs = repositoryDirs
524
525 - def __repr__(self):
526 """ 527 Official string representation for class instance. 528 """ 529 return "SubversionConfig(%s, %s, %s, %s)" % (self.collectMode, self.compressMode, self.repositories, self.repositoryDirs)
530
531 - def __str__(self):
532 """ 533 Informal string representation for class instance. 534 """ 535 return self.__repr__()
536
537 - def __cmp__(self, other):
538 """ 539 Definition of equals operator for this class. 540 Lists within this class are "unordered" for equality comparisons. 541 @param other: Other object to compare to. 542 @return: -1/0/1 depending on whether self is C{<}, C{=} or C{>} other. 543 """ 544 if other is None: 545 return 1 546 if self._collectMode != other._collectMode: 547 if self._collectMode < other._collectMode: 548 return -1 549 else: 550 return 1 551 if self._compressMode != other._compressMode: 552 if self._compressMode < other._compressMode: 553 return -1 554 else: 555 return 1 556 if self._repositories != other._repositories: 557 if self._repositories < other._repositories: 558 return -1 559 else: 560 return 1 561 if self._repositoryDirs != other._repositoryDirs: 562 if self._repositoryDirs < other._repositoryDirs: 563 return -1 564 else: 565 return 1 566 return 0
567
568 - def _setCollectMode(self, value):
569 """ 570 Property target used to set the collect mode. 571 If not C{None}, the mode must be one of the values in L{VALID_COLLECT_MODES}. 572 @raise ValueError: If the value is not valid. 573 """ 574 if value is not None: 575 if value not in VALID_COLLECT_MODES: 576 raise ValueError("Collect mode must be one of %s." % VALID_COLLECT_MODES) 577 self._collectMode = value
578
579 - def _getCollectMode(self):
580 """ 581 Property target used to get the collect mode. 582 """ 583 return self._collectMode
584
585 - def _setCompressMode(self, value):
586 """ 587 Property target used to set the compress mode. 588 If not C{None}, the mode must be one of the values in L{VALID_COMPRESS_MODES}. 589 @raise ValueError: If the value is not valid. 590 """ 591 if value is not None: 592 if value not in VALID_COMPRESS_MODES: 593 raise ValueError("Compress mode must be one of %s." % VALID_COMPRESS_MODES) 594 self._compressMode = value
595
596 - def _getCompressMode(self):
597 """ 598 Property target used to get the compress mode. 599 """ 600 return self._compressMode
601
602 - def _setRepositories(self, value):
603 """ 604 Property target used to set the repositories list. 605 Either the value must be C{None} or each element must be a C{Repository}. 606 @raise ValueError: If the value is not a C{Repository} 607 """ 608 if value is None: 609 self._repositories = None 610 else: 611 try: 612 saved = self._repositories 613 self._repositories = ObjectTypeList(Repository, "Repository") 614 self._repositories.extend(value) 615 except Exception, e: 616 self._repositories = saved 617 raise e
618
619 - def _getRepositories(self):
620 """ 621 Property target used to get the repositories list. 622 """ 623 return self._repositories
624
625 - def _setRepositoryDirs(self, value):
626 """ 627 Property target used to set the repositoryDirs list. 628 Either the value must be C{None} or each element must be a C{Repository}. 629 @raise ValueError: If the value is not a C{Repository} 630 """ 631 if value is None: 632 self._repositoryDirs = None 633 else: 634 try: 635 saved = self._repositoryDirs 636 self._repositoryDirs = ObjectTypeList(RepositoryDir, "RepositoryDir") 637 self._repositoryDirs.extend(value) 638 except Exception, e: 639 self._repositoryDirs = saved 640 raise e
641
642 - def _getRepositoryDirs(self):
643 """ 644 Property target used to get the repositoryDirs list. 645 """ 646 return self._repositoryDirs
647 648 collectMode = property(_getCollectMode, _setCollectMode, None, doc="Default collect mode.") 649 compressMode = property(_getCompressMode, _setCompressMode, None, doc="Default compress mode.") 650 repositories = property(_getRepositories, _setRepositories, None, doc="List of Subversion repositories to back up.") 651 repositoryDirs = property(_getRepositoryDirs, _setRepositoryDirs, None, doc="List of Subversion parent directories to back up.")
652 653 654 ######################################################################## 655 # LocalConfig class definition 656 ######################################################################## 657
658 -class LocalConfig(object):
659 660 """ 661 Class representing this extension's configuration document. 662 663 This is not a general-purpose configuration object like the main Cedar 664 Backup configuration object. Instead, it just knows how to parse and emit 665 Subversion-specific configuration values. Third parties who need to read 666 and write configuration related to this extension should access it through 667 the constructor, C{validate} and C{addConfig} methods. 668 669 @note: Lists within this class are "unordered" for equality comparisons. 670 671 @sort: __init__, __repr__, __str__, __cmp__, subversion, validate, addConfig 672 """ 673
674 - def __init__(self, xmlData=None, xmlPath=None, validate=True):
675 """ 676 Initializes a configuration object. 677 678 If you initialize the object without passing either C{xmlData} or 679 C{xmlPath} then configuration will be empty and will be invalid until it 680 is filled in properly. 681 682 No reference to the original XML data or original path is saved off by 683 this class. Once the data has been parsed (successfully or not) this 684 original information is discarded. 685 686 Unless the C{validate} argument is C{False}, the L{LocalConfig.validate} 687 method will be called (with its default arguments) against configuration 688 after successfully parsing any passed-in XML. Keep in mind that even if 689 C{validate} is C{False}, it might not be possible to parse the passed-in 690 XML document if lower-level validations fail. 691 692 @note: It is strongly suggested that the C{validate} option always be set 693 to C{True} (the default) unless there is a specific need to read in 694 invalid configuration from disk. 695 696 @param xmlData: XML data representing configuration. 697 @type xmlData: String data. 698 699 @param xmlPath: Path to an XML file on disk. 700 @type xmlPath: Absolute path to a file on disk. 701 702 @param validate: Validate the document after parsing it. 703 @type validate: Boolean true/false. 704 705 @raise ValueError: If both C{xmlData} and C{xmlPath} are passed-in. 706 @raise ValueError: If the XML data in C{xmlData} or C{xmlPath} cannot be parsed. 707 @raise ValueError: If the parsed configuration document is not valid. 708 """ 709 self._subversion = None 710 self.subversion = None 711 if xmlData is not None and xmlPath is not None: 712 raise ValueError("Use either xmlData or xmlPath, but not both.") 713 if xmlData is not None: 714 self._parseXmlData(xmlData) 715 if validate: 716 self.validate() 717 elif xmlPath is not None: 718 xmlData = open(xmlPath).read() 719 self._parseXmlData(xmlData) 720 if validate: 721 self.validate()
722
723 - def __repr__(self):
724 """ 725 Official string representation for class instance. 726 """ 727 return "LocalConfig(%s)" % (self.subversion)
728
729 - def __str__(self):
730 """ 731 Informal string representation for class instance. 732 """ 733 return self.__repr__()
734
735 - def __cmp__(self, other):
736 """ 737 Definition of equals operator for this class. 738 Lists within this class are "unordered" for equality comparisons. 739 @param other: Other object to compare to. 740 @return: -1/0/1 depending on whether self is C{<}, C{=} or C{>} other. 741 """ 742 if other is None: 743 return 1 744 if self._subversion != other._subversion: 745 if self._subversion < other._subversion: 746 return -1 747 else: 748 return 1 749 return 0
750
751 - def _setSubversion(self, value):
752 """ 753 Property target used to set the subversion configuration value. 754 If not C{None}, the value must be a C{SubversionConfig} object. 755 @raise ValueError: If the value is not a C{SubversionConfig} 756 """ 757 if value is None: 758 self._subversion = None 759 else: 760 if not isinstance(value, SubversionConfig): 761 raise ValueError("Value must be a C{SubversionConfig} object.") 762 self._subversion = value
763
764 - def _getSubversion(self):
765 """ 766 Property target used to get the subversion configuration value. 767 """ 768 return self._subversion
769 770 subversion = property(_getSubversion, _setSubversion, None, "Subversion configuration in terms of a C{SubversionConfig} object.") 771
772 - def validate(self):
773 """ 774 Validates configuration represented by the object. 775 776 Subversion configuration must be filled in. Within that, the collect 777 mode and compress mode are both optional, but the list of repositories 778 must contain at least one entry. 779 780 Each repository must contain a repository path, and then must be either 781 able to take collect mode and compress mode configuration from the parent 782 C{SubversionConfig} object, or must set each value on its own. 783 784 @raise ValueError: If one of the validations fails. 785 """ 786 if self.subversion is None: 787 raise ValueError("Subversion section is required.") 788 if ((self.subversion.repositories is None or len(self.subversion.repositories) < 1) and 789 (self.subversion.repositoryDirs is None or len(self.subversion.repositoryDirs) <1)): 790 raise ValueError("At least one Subversion repository must be configured.") 791 if self.subversion.repositories is not None: 792 for repository in self.subversion.repositories: 793 if repository.repositoryPath is None: 794 raise ValueError("Each repository must set a repository path.") 795 if self.subversion.collectMode is None and repository.collectMode is None: 796 raise ValueError("Collect mode must either be set in parent section or individual repository.") 797 if self.subversion.compressMode is None and repository.compressMode is None: 798 raise ValueError("Compress mode must either be set in parent section or individual repository.") 799 if self.subversion.repositoryDirs is not None: 800 for repositoryDir in self.subversion.repositoryDirs: 801 if repositoryDir.directoryPath is None: 802 raise ValueError("Each repository directory must set a directory path.") 803 if self.subversion.collectMode is None and repositoryDir.collectMode is None: 804 raise ValueError("Collect mode must either be set in parent section or repository directory.") 805 if self.subversion.compressMode is None and repositoryDir.compressMode is None: 806 raise ValueError("Compress mode must either be set in parent section or repository directory.")
807
808 - def addConfig(self, xmlDom, parentNode):
809 """ 810 Adds a <subversion> configuration section as the next child of a parent. 811 812 Third parties should use this function to write configuration related to 813 this extension. 814 815 We add the following fields to the document:: 816 817 collectMode //cb_config/subversion/collectMode 818 compressMode //cb_config/subversion/compressMode 819 820 We also add groups of the following items, one list element per 821 item:: 822 823 repository //cb_config/subversion/repository 824 repository_dir //cb_config/subversion/repository_dir 825 826 @param xmlDom: DOM tree as from C{impl.createDocument()}. 827 @param parentNode: Parent that the section should be appended to. 828 """ 829 if self.subversion is not None: 830 sectionNode = addContainerNode(xmlDom, parentNode, "subversion") 831 addStringNode(xmlDom, sectionNode, "collect_mode", self.subversion.collectMode) 832 addStringNode(xmlDom, sectionNode, "compress_mode", self.subversion.compressMode) 833 if self.subversion.repositories is not None: 834 for repository in self.subversion.repositories: 835 LocalConfig._addRepository(xmlDom, sectionNode, repository) 836 if self.subversion.repositoryDirs is not None: 837 for repositoryDir in self.subversion.repositoryDirs: 838 LocalConfig._addRepositoryDir(xmlDom, sectionNode, repositoryDir)
839
840 - def _parseXmlData(self, xmlData):
841 """ 842 Internal method to parse an XML string into the object. 843 844 This method parses the XML document into a DOM tree (C{xmlDom}) and then 845 calls a static method to parse the subversion configuration section. 846 847 @param xmlData: XML data to be parsed 848 @type xmlData: String data 849 850 @raise ValueError: If the XML cannot be successfully parsed. 851 """ 852 (xmlDom, parentNode) = createInputDom(xmlData) 853 self._subversion = LocalConfig._parseSubversion(parentNode)
854
855 - def _parseSubversion(parent):
856 """ 857 Parses a subversion configuration section. 858 859 We read the following individual fields:: 860 861 collectMode //cb_config/subversion/collect_mode 862 compressMode //cb_config/subversion/compress_mode 863 864 We also read groups of the following item, one list element per 865 item:: 866 867 repositories //cb_config/subversion/repository 868 repository_dirs //cb_config/subversion/repository_dir 869 870 The repositories are parsed by L{_parseRepositories}, and the repository 871 dirs are parsed by L{_parseRepositoryDirs}. 872 873 @param parent: Parent node to search beneath. 874 875 @return: C{SubversionConfig} object or C{None} if the section does not exist. 876 @raise ValueError: If some filled-in value is invalid. 877 """ 878 subversion = None 879 section = readFirstChild(parent, "subversion") 880 if section is not None: 881 subversion = SubversionConfig() 882 subversion.collectMode = readString(section, "collect_mode") 883 subversion.compressMode = readString(section, "compress_mode") 884 subversion.repositories = LocalConfig._parseRepositories(section) 885 subversion.repositoryDirs = LocalConfig._parseRepositoryDirs(section) 886 return subversion
887 _parseSubversion = staticmethod(_parseSubversion) 888
889 - def _parseRepositories(parent):
890 """ 891 Reads a list of C{Repository} objects from immediately beneath the parent. 892 893 We read the following individual fields:: 894 895 repositoryType type 896 repositoryPath abs_path 897 collectMode collect_mode 898 compressMode compess_mode 899 900 The type field is optional, and its value is kept around only for 901 reference. 902 903 @param parent: Parent node to search beneath. 904 905 @return: List of C{Repository} objects or C{None} if none are found. 906 @raise ValueError: If some filled-in value is invalid. 907 """ 908 lst = [] 909 for entry in readChildren(parent, "repository"): 910 if isElement(entry): 911 repository = Repository() 912 repository.repositoryType = readString(entry, "type") 913 repository.repositoryPath = readString(entry, "abs_path") 914 repository.collectMode = readString(entry, "collect_mode") 915 repository.compressMode = readString(entry, "compress_mode") 916 lst.append(repository) 917 if lst == []: 918 lst = None 919 return lst
920 _parseRepositories = staticmethod(_parseRepositories) 921
922 - def _addRepository(xmlDom, parentNode, repository):
923 """ 924 Adds a repository container as the next child of a parent. 925 926 We add the following fields to the document:: 927 928 repositoryType repository/type 929 repositoryPath repository/abs_path 930 collectMode repository/collect_mode 931 compressMode repository/compress_mode 932 933 The <repository> node itself is created as the next child of the parent 934 node. This method only adds one repository node. The parent must loop 935 for each repository in the C{SubversionConfig} object. 936 937 If C{repository} is C{None}, this method call will be a no-op. 938 939 @param xmlDom: DOM tree as from C{impl.createDocument()}. 940 @param parentNode: Parent that the section should be appended to. 941 @param repository: Repository to be added to the document. 942 """ 943 if repository is not None: 944 sectionNode = addContainerNode(xmlDom, parentNode, "repository") 945 addStringNode(xmlDom, sectionNode, "type", repository.repositoryType) 946 addStringNode(xmlDom, sectionNode, "abs_path", repository.repositoryPath) 947 addStringNode(xmlDom, sectionNode, "collect_mode", repository.collectMode) 948 addStringNode(xmlDom, sectionNode, "compress_mode", repository.compressMode)
949 _addRepository = staticmethod(_addRepository) 950
951 - def _parseRepositoryDirs(parent):
952 """ 953 Reads a list of C{RepositoryDir} objects from immediately beneath the parent. 954 955 We read the following individual fields:: 956 957 repositoryType type 958 directoryPath abs_path 959 collectMode collect_mode 960 compressMode compess_mode 961 962 We also read groups of the following items, one list element per 963 item:: 964 965 relativeExcludePaths exclude/rel_path 966 excludePatterns exclude/pattern 967 968 The exclusions are parsed by L{_parseExclusions}. 969 970 The type field is optional, and its value is kept around only for 971 reference. 972 973 @param parent: Parent node to search beneath. 974 975 @return: List of C{RepositoryDir} objects or C{None} if none are found. 976 @raise ValueError: If some filled-in value is invalid. 977 """ 978 lst = [] 979 for entry in readChildren(parent, "repository_dir"): 980 if isElement(entry): 981 repositoryDir = RepositoryDir() 982 repositoryDir.repositoryType = readString(entry, "type") 983 repositoryDir.directoryPath = readString(entry, "abs_path") 984 repositoryDir.collectMode = readString(entry, "collect_mode") 985 repositoryDir.compressMode = readString(entry, "compress_mode") 986 (repositoryDir.relativeExcludePaths, repositoryDir.excludePatterns) = LocalConfig._parseExclusions(entry) 987 lst.append(repositoryDir) 988 if lst == []: 989 lst = None 990 return lst
991 _parseRepositoryDirs = staticmethod(_parseRepositoryDirs) 992
993 - def _parseExclusions(parentNode):
994 """ 995 Reads exclusions data from immediately beneath the parent. 996 997 We read groups of the following items, one list element per item:: 998 999 relative exclude/rel_path 1000 patterns exclude/pattern 1001 1002 If there are none of some pattern (i.e. no relative path items) then 1003 C{None} will be returned for that item in the tuple. 1004 1005 @param parentNode: Parent node to search beneath. 1006 1007 @return: Tuple of (relative, patterns) exclusions. 1008 """ 1009 section = readFirstChild(parentNode, "exclude") 1010 if section is None: 1011 return (None, None) 1012 else: 1013 relative = readStringList(section, "rel_path") 1014 patterns = readStringList(section, "pattern") 1015 return (relative, patterns)
1016 _parseExclusions = staticmethod(_parseExclusions) 1017
1018 - def _addRepositoryDir(xmlDom, parentNode, repositoryDir):
1019 """ 1020 Adds a repository dir container as the next child of a parent. 1021 1022 We add the following fields to the document:: 1023 1024 repositoryType repository_dir/type 1025 directoryPath repository_dir/abs_path 1026 collectMode repository_dir/collect_mode 1027 compressMode repository_dir/compress_mode 1028 1029 We also add groups of the following items, one list element per item:: 1030 1031 relativeExcludePaths dir/exclude/rel_path 1032 excludePatterns dir/exclude/pattern 1033 1034 The <repository_dir> node itself is created as the next child of the 1035 parent node. This method only adds one repository node. The parent must 1036 loop for each repository dir in the C{SubversionConfig} object. 1037 1038 If C{repositoryDir} is C{None}, this method call will be a no-op. 1039 1040 @param xmlDom: DOM tree as from C{impl.createDocument()}. 1041 @param parentNode: Parent that the section should be appended to. 1042 @param repositoryDir: Repository dir to be added to the document. 1043 """ 1044 if repositoryDir is not None: 1045 sectionNode = addContainerNode(xmlDom, parentNode, "repository_dir") 1046 addStringNode(xmlDom, sectionNode, "type", repositoryDir.repositoryType) 1047 addStringNode(xmlDom, sectionNode, "abs_path", repositoryDir.directoryPath) 1048 addStringNode(xmlDom, sectionNode, "collect_mode", repositoryDir.collectMode) 1049 addStringNode(xmlDom, sectionNode, "compress_mode", repositoryDir.compressMode) 1050 if ((repositoryDir.relativeExcludePaths is not None and repositoryDir.relativeExcludePaths != []) or 1051 (repositoryDir.excludePatterns is not None and repositoryDir.excludePatterns != [])): 1052 excludeNode = addContainerNode(xmlDom, sectionNode, "exclude") 1053 if repositoryDir.relativeExcludePaths is not None: 1054 for relativePath in repositoryDir.relativeExcludePaths: 1055 addStringNode(xmlDom, excludeNode, "rel_path", relativePath) 1056 if repositoryDir.excludePatterns is not None: 1057 for pattern in repositoryDir.excludePatterns: 1058 addStringNode(xmlDom, excludeNode, "pattern", pattern)
1059 _addRepositoryDir = staticmethod(_addRepositoryDir)
1060 1061 1062 ######################################################################## 1063 # Public functions 1064 ######################################################################## 1065 1066 ########################### 1067 # executeAction() function 1068 ########################### 1069
1070 -def executeAction(configPath, options, config):
1071 """ 1072 Executes the Subversion backup action. 1073 1074 @param configPath: Path to configuration file on disk. 1075 @type configPath: String representing a path on disk. 1076 1077 @param options: Program command-line options. 1078 @type options: Options object. 1079 1080 @param config: Program configuration. 1081 @type config: Config object. 1082 1083 @raise ValueError: Under many generic error conditions 1084 @raise IOError: If a backup could not be written for some reason. 1085 """ 1086 logger.debug("Executing Subversion extended action.") 1087 if config.options is None or config.collect is None: 1088 raise ValueError("Cedar Backup configuration is not properly filled in.") 1089 local = LocalConfig(xmlPath=configPath) 1090 todayIsStart = isStartOfWeek(config.options.startingDay) 1091 fullBackup = options.full or todayIsStart 1092 logger.debug("Full backup flag is [%s]" % fullBackup) 1093 if local.subversion.repositories is not None: 1094 for repository in local.subversion.repositories: 1095 _backupRepository(config, local, todayIsStart, fullBackup, repository) 1096 if local.subversion.repositoryDirs is not None: 1097 for repositoryDir in local.subversion.repositoryDirs: 1098 logger.debug("Working with repository directory [%s]." % repositoryDir.directoryPath) 1099 for repositoryPath in _getRepositoryPaths(repositoryDir): 1100 repository = Repository(repositoryDir.repositoryType, repositoryPath, 1101 repositoryDir.collectMode, repositoryDir.compressMode) 1102 _backupRepository(config, local, todayIsStart, fullBackup, repository) 1103 logger.info("Completed backing up Subversion repository directory [%s]." % repositoryDir.directoryPath) 1104 logger.info("Executed the Subversion extended action successfully.")
1105
1106 -def _getCollectMode(local, repository):
1107 """ 1108 Gets the collect mode that should be used for a repository. 1109 Use repository's if possible, otherwise take from subversion section. 1110 @param repository: Repository object. 1111 @return: Collect mode to use. 1112 """ 1113 if repository.collectMode is None: 1114 collectMode = local.subversion.collectMode 1115 else: 1116 collectMode = repository.collectMode 1117 logger.debug("Collect mode is [%s]" % collectMode) 1118 return collectMode
1119
1120 -def _getCompressMode(local, repository):
1121 """ 1122 Gets the compress mode that should be used for a repository. 1123 Use repository's if possible, otherwise take from subversion section. 1124 @param local: LocalConfig object. 1125 @param repository: Repository object. 1126 @return: Compress mode to use. 1127 """ 1128 if repository.compressMode is None: 1129 compressMode = local.subversion.compressMode 1130 else: 1131 compressMode = repository.compressMode 1132 logger.debug("Compress mode is [%s]" % compressMode) 1133 return compressMode
1134
1135 -def _getRevisionPath(config, repository):
1136 """ 1137 Gets the path to the revision file associated with a repository. 1138 @param config: Config object. 1139 @param repository: Repository object. 1140 @return: Absolute path to the revision file associated with the repository. 1141 """ 1142 normalized = buildNormalizedPath(repository.repositoryPath) 1143 filename = "%s.%s" % (normalized, REVISION_PATH_EXTENSION) 1144 revisionPath = os.path.join(config.options.workingDir, filename) 1145 logger.debug("Revision file path is [%s]" % revisionPath) 1146 return revisionPath
1147
1148 -def _getBackupPath(config, repositoryPath, compressMode, startRevision, endRevision):
1149 """ 1150 Gets the backup file path (including correct extension) associated with a repository. 1151 @param config: Config object. 1152 @param repositoryPath: Path to the indicated repository 1153 @param compressMode: Compress mode to use for this repository. 1154 @param startRevision: Starting repository revision. 1155 @param endRevision: Ending repository revision. 1156 @return: Absolute path to the backup file associated with the repository. 1157 """ 1158 normalizedPath = buildNormalizedPath(repositoryPath) 1159 filename = "svndump-%d:%d-%s.txt" % (startRevision, endRevision, normalizedPath) 1160 if compressMode == 'gzip': 1161 filename = "%s.gz" % filename 1162 elif compressMode == 'bzip2': 1163 filename = "%s.bz2" % filename 1164 backupPath = os.path.join(config.collect.targetDir, filename) 1165 logger.debug("Backup file path is [%s]" % backupPath) 1166 return backupPath
1167
1168 -def _getRepositoryPaths(repositoryDir):
1169 """ 1170 Gets a list of child repository paths within a repository directory. 1171 @param repositoryDir: RepositoryDirectory 1172 """ 1173 (excludePaths, excludePatterns) = _getExclusions(repositoryDir) 1174 fsList = FilesystemList() 1175 fsList.excludeFiles = True 1176 fsList.excludeLinks = True 1177 fsList.excludePaths = excludePaths 1178 fsList.excludePatterns = excludePatterns 1179 fsList.addDirContents(path=repositoryDir.directoryPath, recursive=False, addSelf=False) 1180 return fsList
1181
1182 -def _getExclusions(repositoryDir):
1183 """ 1184 Gets exclusions (file and patterns) associated with an repository directory. 1185 1186 The returned files value is a list of absolute paths to be excluded from the 1187 backup for a given directory. It is derived from the repository directory's 1188 relative exclude paths. 1189 1190 The returned patterns value is a list of patterns to be excluded from the 1191 backup for a given directory. It is derived from the repository directory's 1192 list of patterns. 1193 1194 @param repositoryDir: Repository directory object. 1195 1196 @return: Tuple (files, patterns) indicating what to exclude. 1197 """ 1198 paths = [] 1199 if repositoryDir.relativeExcludePaths is not None: 1200 for relativePath in repositoryDir.relativeExcludePaths: 1201 paths.append(os.path.join(repositoryDir.directoryPath, relativePath)) 1202 patterns = [] 1203 if repositoryDir.excludePatterns is not None: 1204 patterns.extend(repositoryDir.excludePatterns) 1205 logger.debug("Exclude paths: %s" % paths) 1206 logger.debug("Exclude patterns: %s" % patterns) 1207 return(paths, patterns)
1208
1209 -def _backupRepository(config, local, todayIsStart, fullBackup, repository):
1210 """ 1211 Backs up an individual Subversion repository. 1212 1213 This internal method wraps the public methods and adds some functionality 1214 to work better with the extended action itself. 1215 1216 @param config: Cedar Backup configuration. 1217 @param local: Local configuration 1218 @param todayIsStart: Indicates whether today is start of week 1219 @param fullBackup: Full backup flag 1220 @param repository: Repository to operate on 1221 1222 @raise ValueError: If some value is missing or invalid. 1223 @raise IOError: If there is a problem executing the Subversion dump. 1224 """ 1225 logger.debug("Working with repository [%s]" % repository.repositoryPath) 1226 logger.debug("Repository type is [%s]" % repository.repositoryType) 1227 collectMode = _getCollectMode(local, repository) 1228 compressMode = _getCompressMode(local, repository) 1229 revisionPath = _getRevisionPath(config, repository) 1230 if not (fullBackup or (collectMode in ['daily', 'incr', ]) or (collectMode == 'weekly' and todayIsStart)): 1231 logger.debug("Repository will not be backed up, per collect mode.") 1232 return 1233 logger.debug("Repository meets criteria to be backed up today.") 1234 if collectMode != "incr" or fullBackup: 1235 startRevision = 0 1236 endRevision = getYoungestRevision(repository.repositoryPath) 1237 logger.debug("Using full backup, revision: (%d, %d)." % (startRevision, endRevision)) 1238 else: 1239 if fullBackup: 1240 startRevision = 0 1241 endRevision = getYoungestRevision(repository.repositoryPath) 1242 else: 1243 startRevision = _loadLastRevision(revisionPath) + 1 1244 endRevision = getYoungestRevision(repository.repositoryPath) 1245 if startRevision > endRevision: 1246 logger.info("No need to back up repository [%s]; no new revisions." % repository.repositoryPath) 1247 return 1248 logger.debug("Using incremental backup, revision: (%d, %d)." % (startRevision, endRevision)) 1249 backupPath = _getBackupPath(config, repository.repositoryPath, compressMode, startRevision, endRevision) 1250 outputFile = _getOutputFile(backupPath, compressMode) 1251 try: 1252 backupRepository(repository.repositoryPath, outputFile, startRevision, endRevision) 1253 finally: 1254 outputFile.close() 1255 if not os.path.exists(backupPath): 1256 raise IOError("Dump file [%s] does not seem to exist after backup completed." % backupPath) 1257 changeOwnership(backupPath, config.options.backupUser, config.options.backupGroup) 1258 if collectMode == "incr": 1259 _writeLastRevision(config, revisionPath, endRevision) 1260 logger.info("Completed backing up Subversion repository [%s]." % repository.repositoryPath)
1261
1262 -def _getOutputFile(backupPath, compressMode):
1263 """ 1264 Opens the output file used for saving the Subversion dump. 1265 1266 If the compress mode is "gzip", we'll open a C{GzipFile}, and if the 1267 compress mode is "bzip2", we'll open a C{BZ2File}. Otherwise, we'll just 1268 return an object from the normal C{open()} method. 1269 1270 @param backupPath: Path to file to open. 1271 @param compressMode: Compress mode of file ("none", "gzip", "bzip"). 1272 1273 @return: Output file object. 1274 """ 1275 if compressMode == "gzip": 1276 return GzipFile(backupPath, "w") 1277 elif compressMode == "bzip2": 1278 return BZ2File(backupPath, "w") 1279 else: 1280 return open(backupPath, "w")
1281
1282 -def _loadLastRevision(revisionPath):
1283 """ 1284 Loads the indicated revision file from disk into an integer. 1285 1286 If we can't load the revision file successfully (either because it doesn't 1287 exist or for some other reason), then a revision of -1 will be returned - 1288 but the condition will be logged. This way, we err on the side of backing 1289 up too much, because anyone using this will presumably be adding 1 to the 1290 revision, so they don't duplicate any backups. 1291 1292 @param revisionPath: Path to the revision file on disk. 1293 1294 @return: Integer representing last backed-up revision, -1 on error or if none can be read. 1295 """ 1296 if not os.path.isfile(revisionPath): 1297 startRevision = -1 1298 logger.debug("Revision file [%s] does not exist on disk." % revisionPath) 1299 else: 1300 try: 1301 startRevision = pickle.load(open(revisionPath, "r")) 1302 logger.debug("Loaded revision file [%s] from disk: %d." % (revisionPath, startRevision)) 1303 except: 1304 startRevision = -1 1305 logger.error("Failed loading revision file [%s] from disk." % revisionPath) 1306 return startRevision
1307
1308 -def _writeLastRevision(config, revisionPath, endRevision):
1309 """ 1310 Writes the end revision to the indicated revision file on disk. 1311 1312 If we can't write the revision file successfully for any reason, we'll log 1313 the condition but won't throw an exception. 1314 1315 @param config: Config object. 1316 @param revisionPath: Path to the revision file on disk. 1317 @param endRevision: Last revision backed up on this run. 1318 """ 1319 try: 1320 pickle.dump(endRevision, open(revisionPath, "w")) 1321 changeOwnership(revisionPath, config.options.backupUser, config.options.backupGroup) 1322 logger.debug("Wrote new revision file [%s] to disk: %d." % (revisionPath, endRevision)) 1323 except: 1324 logger.error("Failed to write revision file [%s] to disk." % revisionPath)
1325 1326 1327 ############################## 1328 # backupRepository() function 1329 ############################## 1330
1331 -def backupRepository(repositoryPath, backupFile, startRevision=None, endRevision=None):
1332 """ 1333 Backs up an individual Subversion repository. 1334 1335 The starting and ending revision values control an incremental backup. If 1336 the starting revision is not passed in, then revision zero (the start of the 1337 repository) is assumed. If the ending revision is not passed in, then the 1338 youngest revision in the database will be used as the endpoint. 1339 1340 The backup data will be written into the passed-in back file. Normally, 1341 this would be an object as returned from C{open}, but it is possible to use 1342 something like a C{GzipFile} to write compressed output. The caller is 1343 responsible for closing the passed-in backup file. 1344 1345 @note: This function should either be run as root or as the owner of the 1346 Subversion repository. 1347 1348 @note: It is apparently I{not} a good idea to interrupt this function. 1349 Sometimes, this leaves the repository in a "wedged" state, which requires 1350 recovery using C{svnadmin recover}. 1351 1352 @param repositoryPath: Path to Subversion repository to back up 1353 @type repositoryPath: String path representing Subversion repository on disk. 1354 1355 @param backupFile: Python file object to use for writing backup. 1356 @type backupFile: Python file object as from C{open()} or C{file()}. 1357 1358 @param startRevision: Starting repository revision to back up (for incremental backups) 1359 @type startRevision: Integer value >= 0. 1360 1361 @param endRevision: Ending repository revision to back up (for incremental backups) 1362 @type endRevision: Integer value >= 0. 1363 1364 @raise ValueError: If some value is missing or invalid. 1365 @raise IOError: If there is a problem executing the Subversion dump. 1366 """ 1367 if startRevision is None: 1368 startRevision = 0 1369 if endRevision is None: 1370 endRevision = getYoungestRevision(repositoryPath) 1371 if int(startRevision) < 0: 1372 raise ValueError("Start revision must be >= 0.") 1373 if int(endRevision) < 0: 1374 raise ValueError("End revision must be >= 0.") 1375 if startRevision > endRevision: 1376 raise ValueError("Start revision must be <= end revision.") 1377 args = [ "dump", "--quiet", "-r%s:%s" % (startRevision, endRevision), "--incremental", repositoryPath, ] 1378 command = resolveCommand(SVNADMIN_COMMAND) 1379 result = executeCommand(command, args, returnOutput=False, ignoreStderr=True, doNotLog=True, outputFile=backupFile)[0] 1380 if result != 0: 1381 raise IOError("Error [%d] executing Subversion dump for repository [%s]." % (result, repositoryPath)) 1382 logger.debug("Completed dumping subversion repository [%s]." % repositoryPath)
1383 1384 1385 ################################# 1386 # getYoungestRevision() function 1387 ################################# 1388
1389 -def getYoungestRevision(repositoryPath):
1390 """ 1391 Gets the youngest (newest) revision in a Subversion repository using C{svnlook}. 1392 1393 @note: This function should either be run as root or as the owner of the 1394 Subversion repository. 1395 1396 @param repositoryPath: Path to Subversion repository to look in. 1397 @type repositoryPath: String path representing Subversion repository on disk. 1398 1399 @return: Youngest revision as an integer. 1400 1401 @raise ValueError: If there is a problem parsing the C{svnlook} output. 1402 @raise IOError: If there is a problem executing the C{svnlook} command. 1403 """ 1404 args = [ 'youngest', repositoryPath, ] 1405 command = resolveCommand(SVNLOOK_COMMAND) 1406 (result, output) = executeCommand(command, args, returnOutput=True, ignoreStderr=True) 1407 if result != 0: 1408 raise IOError("Error [%d] executing 'svnlook youngest' for repository [%s]." % (result, repositoryPath)) 1409 if len(output) != 1: 1410 raise ValueError("Unable to parse 'svnlook youngest' output.") 1411 return int(output[0])
1412 1413 1414 ######################################################################## 1415 # Deprecated functionality 1416 ######################################################################## 1417
1418 -class BDBRepository(Repository):
1419 1420 """ 1421 Class representing Subversion BDB (Berkeley Database) repository configuration. 1422 This object is deprecated. Use a simple L{Repository} instead. 1423 """ 1424
1425 - def __init__(self, repositoryPath=None, collectMode=None, compressMode=None):
1426 """ 1427 Constructor for the C{BDBRepository} class. 1428 """ 1429 super(BDBRepository, self).__init__("BDB", repositoryPath, collectMode, compressMode)
1430
1431 - def __repr__(self):
1432 """ 1433 Official string representation for class instance. 1434 """ 1435 return "BDBRepository(%s, %s, %s)" % (self.repositoryPath, self.collectMode, self.compressMode)
1436 1437
1438 -class FSFSRepository(Repository):
1439 1440 """ 1441 Class representing Subversion FSFS repository configuration. 1442 This object is deprecated. Use a simple L{Repository} instead. 1443 """ 1444
1445 - def __init__(self, repositoryPath=None, collectMode=None, compressMode=None):
1446 """ 1447 Constructor for the C{FSFSRepository} class. 1448 """ 1449 super(FSFSRepository, self).__init__("FSFS", repositoryPath, collectMode, compressMode)
1450
1451 - def __repr__(self):
1452 """ 1453 Official string representation for class instance. 1454 """ 1455 return "FSFSRepository(%s, %s, %s)" % (self.repositoryPath, self.collectMode, self.compressMode)
1456 1457
1458 -def backupBDBRepository(repositoryPath, backupFile, startRevision=None, endRevision=None):
1459 """ 1460 Backs up an individual Subversion BDB repository. 1461 This function is deprecated. Use L{backupRepository} instead. 1462 """ 1463 return backupRepository(repositoryPath, backupFile, startRevision, endRevision);
1464 1465
1466 -def backupFSFSRepository(repositoryPath, backupFile, startRevision=None, endRevision=None):
1467 """ 1468 Backs up an individual Subversion FSFS repository. 1469 This function is deprecated. Use L{backupRepository} instead. 1470 """ 1471 return backupRepository(repositoryPath, backupFile, startRevision, endRevision);
1472