Changeset 31

Show
Ignore:
Timestamp:
06/30/09 13:17:30 (14 months ago)
Author:
fumanchu
Message:

Lots of changes, not all of them tested:

  1. New --forward/--reverse options to commit command.
  2. New --down option to diff command.
  3. Previously broke update-to-a-revision by removing --revision option. Replaced it with a positional arg instead: "update DB [@REV]".
  4. Added more whitelist info, including available type names, to pf usage output.
  5. Removed now-unused --whitelist option.
  6. New test and exception for whitelist values which are missing the required schema qualification.
  7. Improved SQL comments in diff output declaring the url@rev and direction.
  8. Improved the diff command to yield in order to have a chance at streaming its output.
Location:
postfacto/trunk/postfacto
Files:
4 modified

Legend:

Unmodified
Added
Removed
  • postfacto/trunk/postfacto/__init__.py

    r30 r31  
    200200 
    201201def 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. 
    203203     
    204204    The 'torev' argument MUST be an exact revision_id, and MUST be on the 
     
    217217        d, = repo_db.fetchone("SELECT %s FROM %s WHERE revision_id = %d;" % 
    218218                              (field, relname, revid)) 
    219         yield revid, d 
     219        yield revid, d, field == 'diffup' 
    220220 
    221221 
     
    340340            wr=wrev, wc=wcopy, s=local_postfacto) as txmgr: 
    341341        # Apply the diffs to our working revision... 
    342         for revid, d in plan: 
     342        for revid, d, up in plan: 
    343343            if d: 
    344344                try: 
     
    349349         
    350350        # ...and apply the same diffs to our working copy. 
    351         for revid, d in plan: 
     351        for revid, d, up in plan: 
    352352            if d: 
    353353                try: 
     
    449449 
    450450 
    451 def update(working_copy, revision=None, force=False): 
     451def update(working_copy, revision=None): 
    452452    """Update the working copy to the given revision. Return the revision id.""" 
    453453    co = info(working_copy) 
     
    551551 
    552552 
    553 def diff(source, target): 
     553def diff(source, target, down=False): 
    554554    """Return a diff between the given source and target. 
    555555     
     
    572572            if ':' in revision: 
    573573                fromrev, torev = revision.split(':', 1) 
     574                if down: 
     575                    fromrev, torev = torev, fromrev 
    574576                fromrev = closest_revision(repo_db, relname, branch, fromrev) 
    575577                torev = closest_revision(repo_db, relname, branch, torev) 
    576578                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 
    580585            else: 
    581586                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 
    586599        else: 
    587600            # target must be a working_copy DB. 
     
    590603            if not db1.exists(): 
    591604                raise Failure("The working revision database does not exist.") 
     605             
    592606            db2 = postgres.Database(target, local_server) 
    593607            if not db2.exists(): 
     
    600614            wl2 = wl1.union(Whitelist.from_str(co['add'])) 
    601615            wl2 = wl2.difference(Whitelist.from_str(co['delete'])) 
     616             
     617            prologue = ('-- Postfacto working copy diff: %s (%s)--' % 
     618                        (target, {True: 'up', False: 'down'}[down])) 
    602619    else: 
    603620        raise NotImplemented 
    604621     
    605622    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 
     642def 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    """ 
    614661    co = info(working_copy) 
    615662    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'])) 
    616667     
    617668    server = server_from_netloc(co['server']) 
     
    620671        raise Failure("The repository database %r does not exist." % co['repo']) 
    621672     
    622     # Get our diffs. 
    623673    wrev = postgres.Database('postfacto_w_%s' % working_copy, local_server) 
    624674    if not wrev.exists(): 
     
    627677    if not wcopy.exists(): 
    628678        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()) 
    636691     
    637692    if forward == [d.param_block] and reverse == [d.param_block] and not force: 
  • postfacto/trunk/postfacto/cmdline.py

    r30 r31  
    1717                'add [--no-expand] DB whitelist', 
    1818                'delete [--no-expand] DB whitelist', 
    19                 'commit --message [--force] DB', 
     19                'commit --message [--force] [--forward] [--reverse] DB', 
    2020                'copy [--message] URL branch', 
    21                 'diff URL|DB', 
     21                'diff [--down] URL|DB', 
    2222                'info DB', 
    2323                'import [--message] [--force] [--no-checkout] DB URL', 
     
    2727                'revert DB', 
    2828                'switch URL DB', 
    29                 'update DB', 
     29                'update DB [REV]', 
    3030                ] 
    3131     
     
    3333        OptionParser.__init__(self) 
    3434        self.usage = '%prog [options] command *args' 
     35        wltypes = sorted(postfacto.differ.whitelist_types) 
    3536        self.epilog = ( 
    3637            os.linesep + 
    3738            '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) + 
    3843            os.linesep + 
    3944            'Commands:' + 
     
    6065                        action='store_true', dest='force', 
    6166                        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") 
    6567        self.add_option('--no-expand', default=False, 
    6668                        action='store_true', dest='no_expand', 
     
    7173                        help="Do not register the imported DB as a checked " 
    7274                             "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.") 
    7384     
    7485    def format_epilog(self, formatter): 
     
    113124                raise postfacto.Failure( 
    114125                    '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) 
    117132            print "Added %d objects:" % sum([len(v) for v in wl.values()], 0) 
    118133            for k in sorted(wl.keys()): 
     
    141156                raise postfacto.Failure( 
    142157                    '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) 
    144160            print "Committed revision %d." % rev 
    145161        elif command == 'diff': 
    146162            started = False 
    147163            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): 
    149166                    if not started: 
    150167                        started = True 
     
    213230            print "Updated to revision %d." % rev 
    214231        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) 
    216236            print "Updated to revision %d." % rev 
    217237    except postfacto.Failure, f: 
  • postfacto/trunk/postfacto/differ.py

    r27 r31  
    20922092            raise ValueError("Circular dependencies found.", remaining) 
    20932093 
     2094 
     2095whitelist_types = set() 
     2096schema_whitelist_types = set() 
     2097for 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 
     2105del v 
     2106 
  • postfacto/trunk/postfacto/whitelists.py

    r26 r31  
    1212 
    1313 
     14class WhitelistError(Exception): 
     15    pass 
     16 
    1417class Whitelist(dict): 
    1518    """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)) 
    1626     
    1727    def __str__(self):