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