Changeset 31
- Timestamp:
- 06/30/09 13:17:30 (14 months ago)
- Location:
- postfacto/trunk/postfacto
- Files:
-
- 4 modified
-
__init__.py (modified) (11 diffs)
-
cmdline.py (modified) (8 diffs)
-
differ.py (modified) (1 diff)
-
whitelists.py (modified) (1 diff)
Legend:
- Unmodified
- Added
- Removed
-
postfacto/trunk/postfacto/__init__.py
r30 r31 200 200 201 201 def diff_plan(repo_db, relname, fromrev, torev): 202 """Yield (revision_id, diff ) tuples to convert fromrev to torev.202 """Yield (revision_id, diff, up?) tuples to convert fromrev to torev. 203 203 204 204 The 'torev' argument MUST be an exact revision_id, and MUST be on the … … 217 217 d, = repo_db.fetchone("SELECT %s FROM %s WHERE revision_id = %d;" % 218 218 (field, relname, revid)) 219 yield revid, d 219 yield revid, d, field == 'diffup' 220 220 221 221 … … 340 340 wr=wrev, wc=wcopy, s=local_postfacto) as txmgr: 341 341 # Apply the diffs to our working revision... 342 for revid, d in plan:342 for revid, d, up in plan: 343 343 if d: 344 344 try: … … 349 349 350 350 # ...and apply the same diffs to our working copy. 351 for revid, d in plan:351 for revid, d, up in plan: 352 352 if d: 353 353 try: … … 449 449 450 450 451 def update(working_copy, revision=None , force=False):451 def update(working_copy, revision=None): 452 452 """Update the working copy to the given revision. Return the revision id.""" 453 453 co = info(working_copy) … … 551 551 552 552 553 def diff(source, target ):553 def diff(source, target, down=False): 554 554 """Return a diff between the given source and target. 555 555 … … 572 572 if ':' in revision: 573 573 fromrev, torev = revision.split(':', 1) 574 if down: 575 fromrev, torev = torev, fromrev 574 576 fromrev = closest_revision(repo_db, relname, branch, fromrev) 575 577 torev = closest_revision(repo_db, relname, branch, torev) 576 578 plan = diff_plan(repo_db, relname, fromrev, torev) 577 return [('-- Postfacto revision: %s@%d --\n' % 578 (baseurl, revid)) + d 579 for revid, d in plan] 579 for revid, d, up in plan: 580 yield ('-- Postfacto revision: %s@%d (%s)--' % 581 (baseurl, revid, {True: 'up', False: 'down'}[up])) 582 for chunk in d.split('\n\n\n'): 583 yield chunk 584 return 580 585 else: 581 586 rev = closest_revision(repo_db, relname, branch, revision) 582 diffup, = repo_db.fetchone( 583 "SELECT diffup FROM %s WHERE revision_id = %d" % (relname, rev)) 584 return ['-- Postfacto revision: %s@%d --\n' % 585 (baseurl, rev)] + diffup.split('\n\n\n') 587 if down: 588 col = 'down' 589 else: 590 col = 'up' 591 d, = repo_db.fetchone( 592 "SELECT diff%s FROM %s WHERE revision_id = %d" % 593 (col, relname, rev)) 594 yield ('-- Postfacto revision: %s@%d (%s) --\n' % 595 (baseurl, rev, col)) 596 for chunk in d.split('\n\n\n'): 597 yield chunk 598 return 586 599 else: 587 600 # target must be a working_copy DB. … … 590 603 if not db1.exists(): 591 604 raise Failure("The working revision database does not exist.") 605 592 606 db2 = postgres.Database(target, local_server) 593 607 if not db2.exists(): … … 600 614 wl2 = wl1.union(Whitelist.from_str(co['add'])) 601 615 wl2 = wl2.difference(Whitelist.from_str(co['delete'])) 616 617 prologue = ('-- Postfacto working copy diff: %s (%s)--' % 618 (target, {True: 'up', False: 'down'}[down])) 602 619 else: 603 620 raise NotImplemented 604 621 605 622 d = differ.Differ(db1, db2, wl1, wl2) 606 forward = list(d.forward_diff()) 607 if forward == [d.param_block]: 608 return [] 609 return forward 610 611 612 def commit(working_copy, message, force=False): 613 """Commit changes in working copy to repo. Return new revision number.""" 623 if down: 624 diff = d.reverse_diff() 625 else: 626 diff = d.forward_diff() 627 param_block = diff.next() 628 if param_block == d.param_block: 629 # If this next line raises StopIteration, there are no changes, 630 # so we won't yield anything if we propagate it outward. 631 first_chunk = diff.next() 632 yield prologue 633 yield param_block 634 yield first_chunk 635 else: 636 yield prologue 637 yield param_block 638 639 for chunk in diff: 640 yield chunk 641 642 def commit(working_copy, message, force=False, forward=None, reverse=None): 643 """Commit changes in working copy to repo. Return new revision number. 644 645 If 'forward' is given, it will be inserted as the diffup value instead 646 of calculating the difference between the working copy and its base. 647 648 If 'reverse' is given, it will be inserted as the diffdown value instead 649 of calculating the difference between the working copy and its base. 650 651 Both of these allow a diff to include RENAMEs, INSERTs, UPDATEs, 652 etc. which are not able to be inferred from the system catalog. 653 Use 'pf diff DB > up.sql' and 'pf diff --reverse DB > down.sql' 654 to obtain files which you can then modify and commit using 655 'commit --forward=up.sql --reverse=down.sql DB'. 656 657 Note that the whitelists for the working copy will still be used even if 658 --forward and --reverse are provided; that is, you must still use the 659 'add' and 'remove' commands in sync with the provided diffs. 660 """ 614 661 co = info(working_copy) 615 662 wl = Whitelist.from_str(co['whitelist']) 663 # First, expand the whitelist to include any added objects... 664 wl2 = wl.union(Whitelist.from_str(co['add'])) 665 # ...and subtract any deleted objects. 666 wl2 = wl2.difference(Whitelist.from_str(co['delete'])) 616 667 617 668 server = server_from_netloc(co['server']) … … 620 671 raise Failure("The repository database %r does not exist." % co['repo']) 621 672 622 # Get our diffs.623 673 wrev = postgres.Database('postfacto_w_%s' % working_copy, local_server) 624 674 if not wrev.exists(): … … 627 677 if not wcopy.exists(): 628 678 raise Failure("The given working copy %r does not exist." % working_copy) 629 # We must first expand the whitelist to include any added objects. 630 wl2 = wl.union(Whitelist.from_str(co['add'])) 631 # ...and now we merge our deleted list 632 wl2 = wl2.difference(Whitelist.from_str(co['delete'])) 633 d = differ.Differ(wrev, wcopy, wl, wl2) 634 forward = list(d.forward_diff()) 635 reverse = list(d.reverse_diff()) 679 680 # Get our diffs. 681 if not (forward and reverse): 682 d = differ.Differ(wrev, wcopy, wl, wl2) 683 if forward: 684 forward = open(forward, 'rb').read() 685 else: 686 forward = list(d.forward_diff()) 687 if reverse: 688 reverse = open(reverse, 'rb').read() 689 else: 690 reverse = list(d.reverse_diff()) 636 691 637 692 if forward == [d.param_block] and reverse == [d.param_block] and not force: -
postfacto/trunk/postfacto/cmdline.py
r30 r31 17 17 'add [--no-expand] DB whitelist', 18 18 'delete [--no-expand] DB whitelist', 19 'commit --message [--force] DB',19 'commit --message [--force] [--forward] [--reverse] DB', 20 20 'copy [--message] URL branch', 21 'diff URL|DB',21 'diff [--down] URL|DB', 22 22 'info DB', 23 23 'import [--message] [--force] [--no-checkout] DB URL', … … 27 27 'revert DB', 28 28 'switch URL DB', 29 'update DB ',29 'update DB [REV]', 30 30 ] 31 31 … … 33 33 OptionParser.__init__(self) 34 34 self.usage = '%prog [options] command *args' 35 wltypes = sorted(postfacto.differ.whitelist_types) 35 36 self.epilog = ( 36 37 os.linesep + 37 38 'URLs: postfacto://[[user@]server]/repodb[/project[/branch][@revision]]' + 39 os.linesep + 40 'Whitelist example: tables=users,accounts;schemas=test' + 41 os.linesep + 42 'Whitelist types : ' + ', '.join(wltypes) + 38 43 os.linesep + 39 44 'Commands:' + … … 60 65 action='store_true', dest='force', 61 66 help="Ignore warnings when performing the operation.") 62 self.add_option('-w', '--whitelist', default=None,63 action='store', type='string', dest='whitelist',64 help="Whitelist string: cat1=name1,name2;cat2=name3")65 67 self.add_option('--no-expand', default=False, 66 68 action='store_true', dest='no_expand', … … 71 73 help="Do not register the imported DB as a checked " 72 74 "out copy.") 75 self.add_option('--forward', default=None, 76 action='store', dest='forward', 77 help="File to store instead of working copy diff up.") 78 self.add_option('--reverse', default=None, 79 action='store', dest='reverse', 80 help="File to store instead of working copy diff down.") 81 self.add_option('--down', default=False, 82 action='store_true', dest='down', 83 help="Return a reverse diff instead of a forward diff.") 73 84 74 85 def format_epilog(self, formatter): … … 113 124 raise postfacto.Failure( 114 125 'You must supply a working copy name and whitelist.') 115 wl = postfacto.add(args[0], args[1].strip(), 116 expand=(not opts.no_expand)) 126 try: 127 wl = postfacto.add(args[0], args[1].strip(), 128 expand=(not opts.no_expand)) 129 except postfacto.whitelists.WhitelistError, x: 130 print x 131 sys.exit(1) 117 132 print "Added %d objects:" % sum([len(v) for v in wl.values()], 0) 118 133 for k in sorted(wl.keys()): … … 141 156 raise postfacto.Failure( 142 157 'You must supply a working copy name and a --message.') 143 rev = postfacto.commit(args[0], opts.message, force=opts.force) 158 rev = postfacto.commit(args[0], opts.message, force=opts.force, 159 forward=opts.forward, reverse=opts.reverse) 144 160 print "Committed revision %d." % rev 145 161 elif command == 'diff': 146 162 started = False 147 163 if len(args) == 1: 148 for block in postfacto.diff(source=None, target=args[0]): 164 for block in postfacto.diff(source=None, target=args[0], 165 down=opts.down): 149 166 if not started: 150 167 started = True … … 213 230 print "Updated to revision %d." % rev 214 231 elif command == 'update': 215 rev = postfacto.update(args[0]) 232 revision = None 233 if len(args) == 2: 234 revision = args[1] 235 rev = postfacto.update(args[0], revision=revision) 216 236 print "Updated to revision %d." % rev 217 237 except postfacto.Failure, f: -
postfacto/trunk/postfacto/differ.py
r27 r31 2092 2092 raise ValueError("Circular dependencies found.", remaining) 2093 2093 2094 2095 whitelist_types = set() 2096 schema_whitelist_types = set() 2097 for v in vars().values(): 2098 if isinstance(v, type) and issubclass(v, DumpableObject): 2099 t = v.whitelist_type 2100 if t: 2101 whitelist_types.add(t) 2102 if issubclass(v, SchemaObject): 2103 schema_whitelist_types.add(t) 2104 del t 2105 del v 2106 -
postfacto/trunk/postfacto/whitelists.py
r26 r31 12 12 13 13 14 class WhitelistError(Exception): 15 pass 16 14 17 class Whitelist(dict): 15 18 """A dict of {category name: set(object names)} pairs.""" 19 20 def __setitem__(self, k, v): 21 if k in postfacto.differ.schema_whitelist_types: 22 for i in v: 23 if '.' not in i: 24 raise WhitelistError("The given value '%s=%s' is not " 25 "schema-qualified." % (k, i)) 16 26 17 27 def __str__(self):
