-1
What the hell? The XML feature is used by the ASF.
Why do you feel you can unilaterally remove features like this? Or create
such incompatible changes in a system that is/has already been deployed?
Ridiculous.
Unhappy,
-g
On Jan 13, 2013 7:17 PM, <breser_at_apache.org> wrote:
> Author: breser
> Date: Mon Jan 14 01:16:57 2013
> New Revision: 1432778
>
> URL: http://svn.apache.org/viewvc?rev=1432778&view=rev
> Log:
> Rework svnpubsub to reduce a lot of complexity and generalize.
>
> In particular the following significant changes have been made:
> - XML support has been removed.
> - svnpubsub.client now takes URLs instead of host/port tuples.
> - svnpubsub.client has been updated to use JSON.
> - The JSON stream when connecting to the server no longer starts an object
> that never gets finished.
> - The server provides a JSON object with the key svnpubsub that
> contains a version key. This is intended to identify the format
> the server will be sending, it can be used to make clients backwards
> compatable to protocol changes.
> - Each JSON record is separated with a null character.
> - The input JSON is now identical to the object in the commit field
> of the output object.
> - A type and format field have been added to the JSON object. type
> can be used to distinguish say a git commit from a svn commit.
> format is a integer format similar to what SVN uses all over the place.
> - The following fields have been renamed to allow the same fields to be
> used across version control systems:
> revision -> id, author -> committer (author could be used for what
> other systems use author for), repos -> repository.
> - The following fields are required by the server on the input:
> repository, id, type and format, everything else is optional.
> - The dirs_changed field has been removed, it can be calculated from
> changed.
> - The Revision classes have been renamed Commit and in general no longer
> hard code certain fields.
> - There is only one type of output stream now, the dirs-changed output
> type has been removed.
> - The URLs used by the server have changed. All PUTs go to /commits.
> Clients no longer pass a format since the only format is JSON.
> Clients can still filter by repository, but can also filter by type now.
>
> [in tools/server-side/svnpubsub]
>
> * svnpubsub/server.py
> (*): Update documentation to reflect changes.
> (twisted.web.static, xml.etree): No longer used don't import.
> (Commit): Remove and replace with...
> (Revision): which has very little in the way of requirements on the
> incoming JSON.
> (Client.init): Remove format and add type as a filter.
> (Client.interested_in): Handle both repository and type. Takes
> the commit object rather than the uuid as input now.
> (Client.write_data): Data is just a string rather than a dictionary
> since there is only one format JSON and records are separated by a
> null character.
> (JSONClient): Merged into Client since only JSON is supported.
> (Client.write_start): Start with a svnpubsub object that has a version
> field rather than starting a commits object we never close.
> (Client.write_heartbeat): Separate records with null character.
> (XMLClient): Removed.
> (SvnPubSub.clients): Is just a list not a dictionary of clients by
> type.
> (SvnPubSub.cc, SvnPubSub.remove): Adjust for clients being just a list.
> (SvnPubSub.render_GET): Adjust for new URI format and single format.
> (SvnPubSub.notifyAll): Adjust for single format, move log formatting to
> new Commit class and adjust for changes in Client.interested_in().
> (SvnPubSub.render_PUT): Handle format errors and object validation. Also
> use the new Commit class.
> (svnpubsub_server): Don't reference /dev/null as empty resource just
> create an empty Resource. Remove dirs-changed and commit URI paths.
>
> * svnpubsub/client.py
> (urlparse): Adjust import to deal with Python 3 rename.
> (xml.sax): No longer import, since we no longer support XML.
> (json): New import.
> (Client.__init__): Accept a URL instead of host and port. Use JSON
> instead of XML. Deal with bytes vs strings for Python 3.
> (Client.handle_connect, Client.handle_close, Client.handle_error):
> Pass None for the new event_arg parameter on the event_callback.
> (Client.found_terminator): bytes vs strings, Use \0 as the record
> separator and let async_chat split the records for us.
> (Client.collect_incoming_data): Just buffer incoming data, when the
> separator is found it'll call the parser from found_terminator().
> (XMLStreamHandler, Revision): Remove
> (JSONRecordHandler, Commit): Add
> (MultiClient.__init__): Take a list of urls instead of hostports.
> (MultiClient._reconnect, MultiClient._reconnect_later,
> MultiClient._add_channel, MultiClient._check_stale):
> Use a URL instead of host and port as incoming arguments and on
> callbacks. Add event_arg to the event_callbacks.
>
> * README.txt: Remove svnwcsub todos (moved to svnwcsub.py) and update
> example.
>
> * commit-hook.py
> (svncmd_dirs): Remove function
> (do_put): PUT to /commits instead of /dirs-changed
> (main): Change format of posted JSON to match changes to the server,
> remove debug print.
>
> * daemonize.py
> (daemonize_exit, daemonize): Change to the newer/clearer except Foo as e
> syntax. Makes this Python 3 compatible.
>
> * example.conf,
> * svnwcsub.conf.example:
> Rename example.conf to svnwcsub.conf.example. Fix the example stream and
> fix the interpolation syntax.
>
> * irkerbridge.py
> (*): Update documentation to reflect changes.
> (Daemon.run, BigDoEverythingClass.__init__): Give MultiClient a list of
> urls
> instead of list of host port tuples.
> (BigDoEverythingClass.locate_matching_configs,
> BigDoEverythingClass.fill_in_extra_args, BigDoEverythingClass.commit):
> Commit callback gets a url and commit object instead of host, port and
> Revision object.
> (BigDoEverythingClass._generate_dirs_changed): New function to calculate
> the
> dirs_changed field from the new changed field.
> (BigDoEverythingClass.fill_in_extra_args): Use _generate_dirs_changed()
> and
> fix a bug where dirs_root was an empty string.
> (BigDoEverythignClass.event): Event receives a url, event_name and
> event_arg
> now.
>
> * svnwcsub.py
> (*): Moved TODOs from README.txt here.
> (ConfigParser, Queue, urlparse): Adjust import for Python 3 rename.
> (posixpath): New import.
> (BigDoEverythingClasss.__init__): svnpubsub.client uses urls directly
> now.
> (BigDoEverythingClasss._normalize_path, BigDoEverythingClasss.commit):
> Use
> posixpath instead of os.path when working on fspaths.
> (BigDoEverythingClasss.commit, Daemon._event): Adjust for changed
> callback params.
> (Daemon.run): Use a list of urls instead of a list of hostport tuples.
>
> * test.conf: Delete
>
> * testserver.py
> (TEST_BODY): Change XML to JSON.
>
> * watcher.py
> (urlparse): Adjust import for Python 3 rename.
> (_commit, _event): Adjust for callback parameter changes.
> (main): svnpubsub.client uses URLs now instead of hostports.
> (*): Take a list of URLs on the command line instead of pulling in
> svnwcsub
> and using its config class.
>
> Added:
> subversion/trunk/tools/server-side/svnpubsub/notes/
> subversion/trunk/tools/server-side/svnpubsub/svnwcsub.conf.example
> - copied, changed from r1428993,
> subversion/trunk/tools/server-side/svnpubsub/example.conf
> Removed:
> subversion/trunk/tools/server-side/svnpubsub/example.conf
> subversion/trunk/tools/server-side/svnpubsub/test.conf
> Modified:
> subversion/trunk/tools/server-side/svnpubsub/README.txt
> subversion/trunk/tools/server-side/svnpubsub/commit-hook.py
> subversion/trunk/tools/server-side/svnpubsub/daemonize.py
> subversion/trunk/tools/server-side/svnpubsub/irkerbridge.py
> subversion/trunk/tools/server-side/svnpubsub/svnpubsub/client.py
> subversion/trunk/tools/server-side/svnpubsub/svnpubsub/server.py
> subversion/trunk/tools/server-side/svnpubsub/svnwcsub.py
> subversion/trunk/tools/server-side/svnpubsub/testserver.py
> subversion/trunk/tools/server-side/svnpubsub/watcher.py
>
> Modified: subversion/trunk/tools/server-side/svnpubsub/README.txt
> URL:
> http://svn.apache.org/viewvc/subversion/trunk/tools/server-side/svnpubsub/README.txt?rev=1432778&r1=1432777&r2=1432778&view=diff
>
> ==============================================================================
> --- subversion/trunk/tools/server-side/svnpubsub/README.txt (original)
> +++ subversion/trunk/tools/server-side/svnpubsub/README.txt Mon Jan 14
> 01:16:57 2013
> @@ -1,21 +1,3 @@
> -### write a README
> -
> -
> -TODO:
> -- bulk update at startup time to avoid backlog warnings
> -- switch to host:port format in config file
> -- fold BDEC into Daemon
> -- fold WorkingCopy._get_match() into __init__
> -- remove wc_ready(). assume all WorkingCopy instances are usable.
> - place the instances into .watch at creation. the .update_applies()
> - just returns if the wc is disabled (eg. could not find wc dir)
> -- figure out way to avoid the ASF-specific PRODUCTION_RE_FILTER
> - (a base path exclusion list should work for the ASF)
> -- add support for SIGHUP to reread the config and reinitialize working
> copies
> -- joes will write documentation for svnpubsub as these items become
> fulfilled
> -- make LOGLEVEL configurable
> -
> -
> Installation instructions:
>
> 1. Set up an svnpubsub service.
> @@ -39,4 +21,4 @@ Installation instructions:
> 3. Set up svnpubsub clients.
>
> (eg svnwcsub.py, svnpubsub/client.py,
> - 'curl -i http://${hostname}:2069/commits/json')
> + 'curl -sN http://${hostname}:2069/commits')
>
> Modified: subversion/trunk/tools/server-side/svnpubsub/commit-hook.py
> URL:
> http://svn.apache.org/viewvc/subversion/trunk/tools/server-side/svnpubsub/commit-hook.py?rev=1432778&r1=1432777&r2=1432778&view=diff
>
> ==============================================================================
> --- subversion/trunk/tools/server-side/svnpubsub/commit-hook.py (original)
> +++ subversion/trunk/tools/server-side/svnpubsub/commit-hook.py Mon Jan 14
> 01:16:57 2013
> @@ -48,17 +48,6 @@ def svncmd_info(repo, revision):
> 'date': data[1].strip(),
> 'log': "\n".join(data[3:]).strip()}
>
> -def svncmd_dirs(repo, revision):
> - cmd = "%s dirs-changed -r %s %s" % (SVNLOOK, revision, repo)
> - p = svncmd(cmd)
> - dirs = []
> - while True:
> - line = p.stdout.readline()
> - if not line:
> - break
> - dirs.append(line.strip())
> - return dirs
> -
> def svncmd_changed(repo, revision):
> cmd = "%s changed -r %s %s" % (SVNLOOK, revision, repo)
> p = svncmd(cmd)
> @@ -74,7 +63,7 @@ def svncmd_changed(repo, revision):
>
> def do_put(body):
> opener = urllib2.build_opener(urllib2.HTTPHandler)
> - request = urllib2.Request("http://%s:%d/dirs-changed" %(HOST, PORT),
> data=body)
> + request = urllib2.Request("http://%s:%d/commits" %(HOST, PORT),
> data=body)
> request.add_header('Content-Type', 'application/json')
> request.get_method = lambda: 'PUT'
> url = opener.open(request)
> @@ -83,18 +72,17 @@ def do_put(body):
> def main(repo, revision):
> revision = revision.lstrip('r')
> i = svncmd_info(repo, revision)
> - data = {'revision': int(revision),
> - 'dirs_changed': [],
> + data = {'type': 'svn',
> + 'format': 1,
> + 'id': int(revision),
> 'changed': {},
> - 'repos': svncmd_uuid(repo),
> - 'author': i['author'],
> + 'repository': svncmd_uuid(repo),
> + 'committer': i['author'],
> 'log': i['log'],
> 'date': i['date'],
> }
> - data['dirs_changed'].extend(svncmd_dirs(repo, revision))
> data['changed'].update(svncmd_changed(repo, revision))
> body = json.dumps(data)
> - #print body
> do_put(body)
>
> if __name__ == "__main__":
>
> Modified: subversion/trunk/tools/server-side/svnpubsub/daemonize.py
> URL:
> http://svn.apache.org/viewvc/subversion/trunk/tools/server-side/svnpubsub/daemonize.py?rev=1432778&r1=1432777&r2=1432778&view=diff
>
> ==============================================================================
> --- subversion/trunk/tools/server-side/svnpubsub/daemonize.py (original)
> +++ subversion/trunk/tools/server-side/svnpubsub/daemonize.py Mon Jan 14
> 01:16:57 2013
> @@ -50,11 +50,11 @@ class Daemon(object):
> def daemonize_exit(self):
> try:
> result = self.daemonize()
> - except (ChildFailed, DaemonFailed), e:
> + except (ChildFailed, DaemonFailed) as e:
> # duplicate the exit code
> sys.exit(e.code)
> except (ChildTerminatedAbnormally, ChildForkFailed,
> - DaemonTerminatedAbnormally, DaemonForkFailed), e:
> + DaemonTerminatedAbnormally, DaemonForkFailed) as e:
> sys.stderr.write('ERROR: %s\n' % e)
> sys.exit(1)
> except ChildResumedIncorrectly:
> @@ -74,7 +74,7 @@ class Daemon(object):
> # fork off a child that can detach itself from this process.
> try:
> pid = os.fork()
> - except OSError, e:
> + except OSError as e:
> raise ChildForkFailed(e.errno, e.strerror)
>
> if pid > 0:
> @@ -113,7 +113,7 @@ class Daemon(object):
> # perform the second fork
> try:
> pid = os.fork()
> - except OSError, e:
> + except OSError as e:
> raise DaemonForkFailed(e.errno, e.strerror)
>
> if pid > 0:
>
> Modified: subversion/trunk/tools/server-side/svnpubsub/irkerbridge.py
> URL:
> http://svn.apache.org/viewvc/subversion/trunk/tools/server-side/svnpubsub/irkerbridge.py?rev=1432778&r1=1432777&r2=1432778&view=diff
>
> ==============================================================================
> --- subversion/trunk/tools/server-side/svnpubsub/irkerbridge.py (original)
> +++ subversion/trunk/tools/server-side/svnpubsub/irkerbridge.py Mon Jan 14
> 01:16:57 2013
> @@ -30,8 +30,6 @@
> # Space separated list of URLs to streams.
> # This option should only be in the DEFAULT section, is ignored in
> # all other sections.
> -# NOTE: At current svnpubsub.client only accepts hostname and port
> -# combos so the path is ignored and /commits/xml is used.
> # irker=hostname:port
> # The hostname/port combination of the irker daemon. If port is
> # omitted it defaults to 6659. Irker is connected to over UDP.
> @@ -46,7 +44,7 @@
> # template=string
> # A string to use to format the output. The string is a Python
> # string Template. The following variables are available:
> -# $author, $rev, $date, $uuid, $log, $log, $log_firstline,
> +# $committer, $id, $date, $repository, $log, $log_firstline,
> # $log_firstparagraph, $dirs_changed, $dirs_count, $dirs_count_s,
> # $subdirs_count, $subdirs_count_s, $dirs_root
> # Most of them should be self explanatory. $dirs_count is the number of
> @@ -114,9 +112,9 @@ class Daemon(daemonize.Daemon):
> def run(self):
> print 'irkerbridge started, pid=%d' % (os.getpid())
>
> - mc = svnpubsub.client.MultiClient(self.bdec.hostports,
> - self.bdec.commit,
> - self.bdec.event)
> + mc = svnpubsub.client.MultiClient(self.bdec.urls,
> + self.bdec.commit,
> + self.bdec.event)
> mc.run_forever()
>
>
> @@ -124,12 +122,9 @@ class BigDoEverythingClass(object):
> def __init__(self, config, options):
> self.config = config
> self.options = options
> - self.hostports = []
> - for url in config.get_value('streams').split():
> - parsed = urlparse.urlparse(url.strip())
> - self.hostports.append((parsed.hostname, parsed.port or 80))
> + self.urls = config.get_value('streams').split()
>
> - def locate_matching_configs(self, rev):
> + def locate_matching_configs(self, commit):
> result = [ ]
> for section in self.config.sections():
> match = self.config.get(section, "match").split('/', 1)
> @@ -137,40 +132,62 @@ class BigDoEverythingClass(object):
> # No slash so assume all paths
> match.append('*')
> match_uuid, match_path = match
> - if rev.uuid == match_uuid or match_uuid == "*":
> - for path in rev.dirs_changed:
> + if commit.repository == match_uuid or match_uuid == "*":
> + for path in commit.changed:
> if fnmatch.fnmatch(path, match_path):
> result.append(section)
> break
> return result
>
> - def fill_in_extra_args(self, rev):
> + def _generate_dirs_changed(self, commit):
> + if hasattr(commit, 'dirs_changed') or not hasattr(commit, 'changed'):
> + return
> +
> + dirs_changed = set()
> + for p in commit.changed:
> + if p[-1] == '/' and commit.changed[p]['flags'][1] == 'U':
> + # directory with property changes add the directory itself.
> + dirs_changed.add(p)
> + else:
> + # everything else add the parent of the path
> + # directories have a trailing slash so if it's present remove
> + # it before finding the parent. The result will be a directory
> + # so it needs a trailing slash
> + dirs_changed.add(posixpath.dirname(p.rstrip('/')) + '/')
> +
> + commit.dirs_changed = dirs_changed
> + return
> +
> + def fill_in_extra_args(self, commit):
> # Set any empty members to the string "<null>"
> - v = vars(rev)
> + v = vars(commit)
> for k in v.keys():
> if not v[k]:
> v[k] = '<null>'
> -
> - # Add entries to the rev object that are useful for
> +
> + self._generate_dirs_changed(commit)
> + # Add entries to the commit object that are useful for
> # formatting.
> - rev.log_firstline = rev.log.split("\n",1)[0]
> - rev.log_firstparagraph = re.split("\r?\n\r?\n",rev.log,1)[0]
> - rev.log_firstparagraph = re.sub("\r?\n"," ",rev.log_firstparagraph)
> - if rev.dirs_changed:
> - rev.dirs_root = posixpath.commonprefix(rev.dirs_changed)
> - rev.dirs_count = len(rev.dirs_changed)
> - if rev.dirs_count > 1:
> - rev.dirs_count_s = " (%d dirs)" %(rev.dirs_count)
> + commit.log_firstline = commit.log.split("\n",1)[0]
> + commit.log_firstparagraph = re.split("\r?\n\r?\n",commit.log,1)[0]
> + commit.log_firstparagraph = re.sub("\r?\n","
> ",commit.log_firstparagraph)
> + if commit.dirs_changed:
> + commit.dirs_root = posixpath.commonprefix(commit.dirs_changed)
> + if commit.dirs_root == '':
> + commit.dirs_root = '/'
> + commit.dirs_count = len(commit.dirs_changed)
> + if commit.dirs_count > 1:
> + commit.dirs_count_s = " (%d dirs)" %(commit.dirs_count)
> else:
> - rev.dirs_count_s = ""
> + commit.dirs_count_s = ""
>
> - rev.subdirs_count = rev.dirs_count
> - if rev.dirs_root in rev.dirs_changed:
> - rev.subdirs_count -= 1
> - if rev.subdirs_count > 1:
> - rev.subdirs_count_s = " + %d subdirs" % (rev.subdirs_count)
> + commit.subdirs_count = commit.dirs_count
> + if commit.dirs_root in commit.dirs_changed:
> + commit.subdirs_count -= 1
> + if commit.subdirs_count >= 1:
> + commit.subdirs_count_s = " + %d subdirs" % (commit.subdirs_count)
> else:
> - rev.subdirs_count_s = ""
> + commit.subdirs_count_s = ""
>
> def _send(self, irker, msg):
> sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
> @@ -193,22 +210,22 @@ class BigDoEverythingClass(object):
> msg = {'to': to, 'privmsg': ''}
> self._send(irker, msg)
>
> - def commit(self, host, port, rev):
> + def commit(self, url, commit):
> if self.options.verbose:
> - print "RECV: from %s:%s" % (host, port)
> - print json.dumps(vars(rev), indent=2)
> + print "RECV: from %s" % url
> + print json.dumps(vars(commit), indent=2)
>
> try:
> - config_sections = self.locate_matching_configs(rev)
> + config_sections = self.locate_matching_configs(commit)
> if len(config_sections) > 0:
> - self.fill_in_extra_args(rev)
> + self.fill_in_extra_args(commit)
> for section in config_sections:
> irker = self.config.get(section, "irker")
> to_list = self.config.get(section, "to").split()
> template = self.config.get(section, "template")
> if not irker or not to_list or not template:
> continue
> - privmsg = Template(template).safe_substitute(vars(rev))
> + privmsg = Template(template).safe_substitute(vars(commit))
> if len(privmsg) > MAX_PRIVMSG:
> privmsg = privmsg[:MAX_PRIVMSG-3] + '...'
> for to in to_list:
> @@ -221,9 +238,9 @@ class BigDoEverythingClass(object):
> sys.stdout.flush()
> raise
>
> - def event(self, host, port, event_name):
> + def event(self, url, event_name, event_arg):
> if self.options.verbose or event_name != "ping":
> - print 'EVENT: %s from %s:%s' % (event_name, host, port)
> + print 'EVENT: %s from %s' % (event_name, url)
> sys.stdout.flush()
>
>
>
> Modified: subversion/trunk/tools/server-side/svnpubsub/svnpubsub/client.py
> URL:
> http://svn.apache.org/viewvc/subversion/trunk/tools/server-side/svnpubsub/svnpubsub/client.py?rev=1432778&r1=1432777&r2=1432778&view=diff
>
> ==============================================================================
> --- subversion/trunk/tools/server-side/svnpubsub/svnpubsub/client.py
> (original)
> +++ subversion/trunk/tools/server-side/svnpubsub/svnpubsub/client.py Mon
> Jan 14 01:16:57 2013
> @@ -38,7 +38,11 @@ import asynchat
> import socket
> import functools
> import time
> -import xml.sax
> +import json
> +try:
> + import urlparse
> +except ImportError:
> + import urllib.parse as urlparse
>
> # How long the polling loop should wait for activity before returning.
> TIMEOUT = 30.0
> @@ -55,22 +59,30 @@ STALE_DELAY = 60.0
>
> class Client(asynchat.async_chat):
>
> - def __init__(self, host, port, commit_callback, event_callback):
> + def __init__(self, url, commit_callback, event_callback):
> asynchat.async_chat.__init__(self)
>
> self.last_activity = time.time()
> + self.ibuffer = []
>
> - self.host = host
> - self.port = port
> - self.event_callback = event_callback
> + self.url = url
> + parsed_url = urlparse.urlsplit(url)
> + if parsed_url.scheme != 'http':
> + raise ValueError("URL scheme must be http: '%s'" % url)
> + host = parsed_url.hostname
> + port = parsed_url.port
> + resource = parsed_url.path
> + if parsed_url.query:
> + resource += "?%s" % parsed_url.query
> + if parsed_url.fragment:
> + resource += "#%s" % parsed_url.fragment
>
> - handler = XMLStreamHandler(commit_callback, event_callback)
> + self.event_callback = event_callback
>
> - self.parser = xml.sax.make_parser(['xml.sax.expatreader'])
> - self.parser.setContentHandler(handler)
> + self.parser = JSONRecordHandler(commit_callback, event_callback)
>
> - # Wait for the end of headers. Then we start parsing XML.
> - self.set_terminator('\r\n\r\n')
> + # Wait for the end of headers. Then we start parsing JSON.
> + self.set_terminator(b'\r\n\r\n')
> self.skipping_headers = True
>
> self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
> @@ -79,101 +91,61 @@ class Client(asynchat.async_chat):
> except:
> self.handle_error()
> return
> -
> - ### should we allow for repository restrictions?
> - self.push('GET /commits/xml HTTP/1.0\r\n\r\n')
> +
> + self.push(('GET %s HTTP/1.0\r\n\r\n' % resource).encode('ascii'))
>
> def handle_connect(self):
> - self.event_callback('connected')
> + self.event_callback('connected', None)
>
> def handle_close(self):
> - self.event_callback('closed')
> + self.event_callback('closed', None)
> self.close()
>
> def handle_error(self):
> - self.event_callback('error')
> + self.event_callback('error', None)
> self.close()
>
> def found_terminator(self):
> - self.skipping_headers = False
> -
> - # From here on, collect everything. Never look for a terminator.
> - self.set_terminator(None)
> + if self.skipping_headers:
> + self.skipping_headers = False
> + # Each JSON record is terminated by a null character
> + self.set_terminator(b'\0')
> + else:
> + record = b"".join(self.ibuffer)
> + self.ibuffer = []
> + self.parser.feed(record.decode())
>
> def collect_incoming_data(self, data):
> # Remember the last time we saw activity
> self.last_activity = time.time()
>
> if not self.skipping_headers:
> - # Just shove this into the XML parser. As the elements are
> processed,
> - # we'll collect them into an appropriate structure, and then invoke
> - # the callback when we have fully received a commit.
> - self.parser.feed(data)
> + self.ibuffer.append(data)
>
>
> -class XMLStreamHandler(xml.sax.handler.ContentHandler):
> -
> +class JSONRecordHandler:
> def __init__(self, commit_callback, event_callback):
> self.commit_callback = commit_callback
> self.event_callback = event_callback
>
> - self.rev = None
> - self.chars = ''
> - self.parent = None
> - self.attrs = [ ]
> -
> - def startElement(self, name, attrs):
> - self.attrs = attrs
> - if name == 'commit':
> - self.rev = Revision(attrs['repository'], int(attrs['revision']))
> - elif name == "dirs_changed" or name == "changed":
> - self.parent = name
> - # No other elements to worry about.
> -
> - def characters(self, data):
> - self.chars += data
> -
> - def endElement(self, name):
> - if name == 'commit':
> - self.commit_callback(self.rev)
> - self.rev = None
> - elif name == 'stillalive':
> - self.event_callback('ping')
> - elif name == self.parent:
> - self.parent = None
> - elif self.chars and self.rev:
> - value = self.chars.strip()
> - if self.parent == 'dirs_changed' and name == 'path':
> - self.rev.dirs_changed.append(value.decode('unicode_escape'))
> - elif self.parent == 'changed' and name == 'path':
> - path = value.decode('unicode_escape')
> - self.rev.changed[path] = dict(p for p in self.attrs.items())
> - elif name == 'author':
> - self.rev.author = value.decode('unicode_escape')
> - elif name == 'date':
> - self.rev.date = value.decode('unicode_escape')
> - elif name == 'log':
> - self.rev.log = value.decode('unicode_escape')
> -
> - # Toss out any accumulated characters for this element.
> - self.chars = ''
> - # Toss out the saved attributes for this element.
> - self.attrs = [ ]
> -
> -
> -class Revision(object):
> - def __init__(self, uuid, rev):
> - self.uuid = uuid
> - self.rev = rev
> - self.dirs_changed = [ ]
> - self.changed = { }
> - self.author = None
> - self.date = None
> - self.log = None
> + def feed(self, record):
> + obj = json.loads(record)
> + if 'svnpubsub' in obj:
> + self.event_callback('version', obj['svnpubsub']['version'])
> + elif 'commit' in obj:
> + commit = Commit(obj['commit'])
> + self.commit_callback(commit)
> + elif 'stillalive' in obj:
> + self.event_callback('ping', obj['stillalive'])
> +
> +
> +class Commit(object):
> + def __init__(self, commit):
> + self.__dict__.update(commit)
>
>
> class MultiClient(object):
> - def __init__(self, hostports, commit_callback, event_callback):
> + def __init__(self, urls, commit_callback, event_callback):
> self.commit_callback = commit_callback
> self.event_callback = event_callback
>
> @@ -181,33 +153,33 @@ class MultiClient(object):
> self.target_time = 0
> self.work_items = [ ]
>
> - for host, port in hostports:
> - self._add_channel(host, port)
> + for url in urls:
> + self._add_channel(url)
>
> - def _reconnect(self, host, port, event_name):
> + def _reconnect(self, url, event_name, event_arg):
> if event_name == 'closed' or event_name == 'error':
> # Stupid connection closed for some reason. Set up a reconnect. Note
> # that it should have been removed from asyncore.socket_map already.
> - self._reconnect_later(host, port)
> + self._reconnect_later(url)
>
> # Call the user's callback now.
> - self.event_callback(host, port, event_name)
> + self.event_callback(url, event_name, event_arg)
>
> - def _reconnect_later(self, host, port):
> + def _reconnect_later(self, url):
> # Set up a work item to reconnect in a little while.
> - self.work_items.append((host, port))
> + self.work_items.append(url)
>
> # Only set a target if one has not been set yet. Otherwise, we could
> # create a race condition of continually moving out towards the future
> if not self.target_time:
> self.target_time = time.time() + RECONNECT_DELAY
>
> - def _add_channel(self, host, port):
> + def _add_channel(self, url):
> # Simply instantiating the client will install it into the global map
> # for processing in the main event loop.
> - Client(host, port,
> - functools.partial(self.commit_callback, host, port),
> - functools.partial(self._reconnect, host, port))
> + Client(url,
> + functools.partial(self.commit_callback, url),
> + functools.partial(self._reconnect, url))
>
> def _check_stale(self):
> now = time.time()
> @@ -215,12 +187,12 @@ class MultiClient(object):
> if client.last_activity + STALE_DELAY < now:
> # Whoops. No activity in a while. Signal this fact, Close the
> # Client, then have it reconnected later on.
> - self.event_callback(client.host, client.port, 'stale')
> + self.event_callback(client.url, 'stale', client.last_activity)
>
> # This should remove it from .socket_map.
> client.close()
>
> - self._reconnect_later(client.host, client.port)
> + self._reconnect_later(client.url)
>
> def _maybe_work(self):
> # If we haven't reach the targetted time, or have no work to do,
> @@ -236,8 +208,8 @@ class MultiClient(object):
> work = self.work_items
> self.work_items = [ ]
>
> - for host, port in work:
> - self._add_channel(host, port)
> + for url in work:
> + self._add_channel(url)
>
> def run_forever(self):
> while True:
>
> Modified: subversion/trunk/tools/server-side/svnpubsub/svnpubsub/server.py
> URL:
> http://svn.apache.org/viewvc/subversion/trunk/tools/server-side/svnpubsub/svnpubsub/server.py?rev=1432778&r1=1432777&r2=1432778&view=diff
>
> ==============================================================================
> --- subversion/trunk/tools/server-side/svnpubsub/svnpubsub/server.py
> (original)
> +++ subversion/trunk/tools/server-side/svnpubsub/svnpubsub/server.py Mon
> Jan 14 01:16:57 2013
> @@ -28,20 +28,24 @@
> # Currently supports both XML and JSON serialization.
> #
> # Example Sub clients:
> -# curl -i http://127.0.0.1:2069/dirs-changed/xml
> -# curl -i http://127.0.0.1:2069/dirs-changed/json
> -# curl -i http://127.0.0.1:2069/commits/json
> -# curl -i
> http://127.0.0.1:2069/commits/13f79535-47bb-0310-9956-ffa450edef68/json
> -# curl -i
> http://127.0.0.1:2069/dirs-changed/13f79535-47bb-0310-9956-ffa450edef68/json
> +# curl -sN http://127.0.0.1:2069/commits
> +# curl -sN http://127.0.0.1:2069/commits/svn/*
> +# curl -sN http://127.0.0.1:2069/commits/svn
> +# curl -sN
> http://127.0.0.1:2069/commits/*/13f79535-47bb-0310-9956-ffa450edef68
> +# curl -sN
> http://127.0.0.1:2069/commits/svn/13f79535-47bb-0310-9956-ffa450edef68
> #
> -# URL is built into 3 parts:
> -# /${type}/${optional_repo_uuid}/${format}
> +# URL is built into 2 parts:
> +# /commits/${optional_type}/${optional_repository}
> +#
> +# If the type is included in the URL, you will only get commits of that
> type.
> +# The type can be * and then you will receive commits of any type.
> #
> -# If the repository UUID is included in the URl, you will only receive
> -# messages about that repository.
> +# If the repository is included in the URL, you will only receive
> +# messages about that repository. The repository can be * and then you
> +# will receive messages about all repositories.
> #
> # Example Pub clients:
> -# curl -T revinfo.json -i http://127.0.0.1:2069/commit
> +# curl -T revinfo.json -i http://127.0.0.1:2069/commits
> #
> # TODO:
> # - Add Real access controls (not just 127.0.0.1)
> @@ -61,82 +65,53 @@ import sys
> import twisted
> from twisted.internet import reactor
> from twisted.internet import defer
> -from twisted.web import server, static
> +from twisted.web import server
> from twisted.web import resource
> from twisted.python import log
>
> -try:
> - from xml.etree import cElementTree as ET
> -except:
> - from xml.etree import ElementTree as ET
> import time
>
> -class Revision:
> +class Commit:
> def __init__(self, r):
> - # Don't escape the values; json handles binary values fine.
> - # ET will happily emit literal control characters (eg, NUL),
> - # thus creating invalid XML, so the XML code paths do escaping.
> - self.rev = r.get('revision')
> - self.repos = r.get('repos')
> - self.dirs_changed = r.get('dirs_changed')
> - self.changed = r.get('changed')
> - self.author = r.get('author')
> - self.log = r.get('log')
> - self.date = r.get('date')
> -
> - def render_commit(self, format):
> - if format == "json":
> - return json.dumps({'commit': {'repository': self.repos,
> - 'revision': self.rev,
> - 'dirs_changed':
> self.dirs_changed,
> - 'changed': self.changed,
> - 'author': self.author,
> - 'log': self.log,
> - 'date': self.date}}) +","
> - elif format == "xml":
> - c = ET.Element('commit', {'repository': self.repos,
> 'revision': "%d" % (self.rev)})
> - ET.SubElement(c, 'author').text =
> self.author.encode('unicode_escape')
> - ET.SubElement(c, 'date').text =
> self.date.encode('unicode_escape')
> - ET.SubElement(c, 'log').text =
> self.log.encode('unicode_escape')
> - d = ET.SubElement(c, 'dirs_changed')
> - for p in self.dirs_changed:
> - x = ET.SubElement(d, 'path')
> - x.text = p.encode('unicode_escape')
> - ch = ET.SubElement(c, 'changed')
> - for chp in self.changed.keys():
> - x = ET.SubElement(ch, 'path', self.changed[chp])
> - x.text = chp.encode('unicode_escape')
> -
> - str = ET.tostring(c, 'UTF-8') + "\n"
> - return str[39:]
> - else:
> - raise Exception("Ooops, invalid format")
> -
> - def render_dirs_changed(self, format):
> - if format == "json":
> - return json.dumps({'commit': {'repository': self.repos,
> - 'revision': self.rev,
> - 'dirs_changed':
> self.dirs_changed}}) +","
> - elif format == "xml":
> - c = ET.Element('commit', {'repository': self.repos,
> 'revision': "%d" % (self.rev)})
> - d = ET.SubElement(c, 'dirs_changed')
> - for p in self.dirs_changed:
> - x = ET.SubElement(d, 'path')
> - x.text = p.encode('unicode_escape')
> - str = ET.tostring(c, 'UTF-8') + "\n"
> - return str[39:]
> - else:
> - raise Exception("Ooops, invalid format")
> + self.__dict__.update(r)
> + if not self.check_value('repository'):
> + raise ValueError('Invalid Repository Value')
> + if not self.check_value('type'):
> + raise ValueError('Invalid Type Value')
> + if not self.check_value('format'):
> + raise ValueError('Invalid Format Value')
> + if not self.check_value('id'):
> + raise ValueError('Invalid ID Value')
> +
> + def check_value(self, k):
> + return hasattr(self, k) and self.__dict__[k]
> +
> + def render_commit(self):
> + obj = {'commit': {}}
> + obj['commit'].update(self.__dict__)
> + return json.dumps(obj)
> +
> + def render_log(self):
> + try:
> + paths_changed = " %d paths changed" % len(self.changed)
> + except:
> + paths_changed = ""
> + return "%s:%s repo '%s' id '%s'%s" % (self.type,
> + self.format,
> + self.repository,
> + self.id,
> + paths_changed)
> +
>
> HEARTBEAT_TIME = 15
>
> class Client(object):
> - def __init__(self, pubsub, r, repos, fmt):
> + def __init__(self, pubsub, r, type, repository):
> self.pubsub = pubsub
> r.notifyFinish().addErrback(self.finished)
> self.r = r
> - self.format = fmt
> - self.repos = repos
> + self.type = type
> + self.repository = repository
> self.alive = True
> log.msg("OPEN: %s:%d (%d clients online)"% (r.getClientIP(),
> r.client.port, pubsub.cc()+1))
>
> @@ -148,12 +123,14 @@ class Client(object):
> except ValueError:
> pass
>
> - def interested_in(self, uuid):
> - if self.repos is None:
> - return True
> - if uuid == self.repos:
> - return True
> - return False
> + def interested_in(self, commit):
> + if self.type and self.type != commit.type:
> + return False
> +
> + if self.repository and self.repository != commit.repository:
> + return False
> +
> + return True
>
> def notify(self, data):
> self.write(data)
> @@ -168,88 +145,67 @@ class Client(object):
> reactor.callLater(HEARTBEAT_TIME, self.heartbeat, None)
>
> def write_data(self, data):
> - self.write(data[self.format] + "\n")
> + self.write(data + "\0")
>
> """ "Data must not be unicode" is what the interfaces.ITransport
> says... grr. """
> def write(self, input):
> self.r.write(str(input))
>
> -class JSONClient(Client):
> def write_start(self):
> self.r.setHeader('content-type', 'application/json')
> - self.write('{"commits": [\n')
> + self.write('{"svnpubsub": {"version": 1}}\0')
>
> def write_heartbeat(self):
> - self.write(json.dumps({"stillalive": time.time()}) + ",\n")
> + self.write(json.dumps({"stillalive": time.time()}) + "\0")
>
> -class XMLClient(Client):
> - def write_start(self):
> - self.r.setHeader('content-type', 'application/xml')
> - self.write("<?xml version='1.0' encoding='UTF-8'?>\n<commits>")
> -
> - def write_heartbeat(self):
> - self.write("<stillalive>%f</stillalive>\n" % (time.time()))
>
> class SvnPubSub(resource.Resource):
> isLeaf = True
> - clients = {'commits': [],
> - 'dirs-changed': []}
> + clients = []
>
> def cc(self):
> - return reduce(lambda x,y: len(x)+len(y), self.clients.values())
> + return len(self.clients)
>
> def remove(self, c):
> - for k in self.clients.keys():
> - self.clients[k].remove(c)
> + self.clients.remove(c)
>
> def render_GET(self, request):
> log.msg("REQUEST: %s" % (request.uri))
> - uri = request.uri.split('/')
> -
> request.setHeader('content-type', 'text/plain')
> - if len(uri) != 3 and len(uri) != 4:
> - request.setResponseCode(400)
> - return "Invalid path\n"
>
> - uuid = None
> - fmt = None
> - type = uri[1]
> -
> - if len(uri) == 3:
> - fmt = uri[2]
> - else:
> - fmt = uri[3]
> - uuid = uri[2]
> + repository = None
> + type = None
>
> - if type not in self.clients.keys():
> + uri = request.uri.split('/')
> + uri_len = len(uri)
> + if uri_len < 2 or uri_len > 4:
> request.setResponseCode(400)
> - return "Invalid Reuqest Type\n"
> + return "Invalid path\n"
>
> - clients = {'json': JSONClient, 'xml': XMLClient}
> - clientCls = clients.get(fmt)
> - if clientCls == None:
> - request.setResponseCode(400)
> - return "Invalid Format Requested\n"
> + if uri_len >= 3:
> + type = uri[2]
> +
> + if uri_len == 4:
> + repository = uri[3]
> +
> + # Convert wild card to None.
> + if type == '*':
> + type = None
> + if repository == '*':
> + repository = None
>
> - c = clientCls(self, request, uuid, fmt)
> - self.clients[type].append(c)
> + c = Client(self, request, type, repository)
> + self.clients.append(c)
> c.start()
> return twisted.web.server.NOT_DONE_YET
>
> - def notifyAll(self, rev):
> - data = {'commits': {},
> - 'dirs-changed': {}}
> - for x in ['xml', 'json']:
> - data['commits'][x] = rev.render_commit(x)
> - data['dirs-changed'][x] = rev.render_dirs_changed(x)
> -
> - log.msg("COMMIT: r%d in %d paths (%d clients)" % (rev.rev,
> -
> len(rev.dirs_changed),
> - self.cc()))
> - for k in self.clients.keys():
> - for c in self.clients[k]:
> - if c.interested_in(rev.repos):
> - c.write_data(data[k])
> + def notifyAll(self, commit):
> + data = commit.render_commit()
> +
> + log.msg("COMMIT: %s (%d clients)" % (commit.render_log(),
> self.cc()))
> + for client in self.clients:
> + if client.interested_in(commit):
> + client.write_data(data)
>
> def render_PUT(self, request):
> request.setHeader('content-type', 'text/plain')
> @@ -260,17 +216,20 @@ class SvnPubSub(resource.Resource):
> input = request.content.read()
> #import pdb;pdb.set_trace()
> #print "input: %s" % (input)
> - r = json.loads(input)
> - rev = Revision(r)
> - self.notifyAll(rev)
> + try:
> + c = json.loads(input)
> + commit = Commit(c)
> + except ValueError as e:
> + request.setResponseCode(400)
> + log.msg("COMMIT: failed due to: %s" % str(e))
> + return str(e)
> + self.notifyAll(commit)
> return "Ok"
>
> def svnpubsub_server():
> - root = static.File("/dev/null")
> + root = resource.Resource()
> s = SvnPubSub()
> - root.putChild("dirs-changed", s)
> root.putChild("commits", s)
> - root.putChild("commit", s)
> return server.Site(root)
>
> if __name__ == "__main__":
>
> Copied: subversion/trunk/tools/server-side/svnpubsub/svnwcsub.conf.example
> (from r1428993, subversion/trunk/tools/server-side/svnpubsub/example.conf)
> URL:
> http://svn.apache.org/viewvc/subversion/trunk/tools/server-side/svnpubsub/svnwcsub.conf.example?p2=subversion/trunk/tools/server-side/svnpubsub/svnwcsub.conf.example&p1=subversion/trunk/tools/server-side/svnpubsub/example.conf&r1=1428993&r2=1432778&rev=1432778&view=diff
>
> ==============================================================================
> --- subversion/trunk/tools/server-side/svnpubsub/example.conf (original)
> +++ subversion/trunk/tools/server-side/svnpubsub/svnwcsub.conf.example Mon
> Jan 14 01:16:57 2013
> @@ -1,8 +1,6 @@
> -### turn this into an example
> -
> [DEFAULT]
> svnbin: /usr/local/bin/svn
> -streams: http://svn.example.org:2069/commits/xml
> +streams: http://svn.example.org:2069/commits/svn
> hook: /usr/bin/true
>
> ## The values below are used by ConfigParser's interpolation syntax.
> @@ -14,5 +12,5 @@ HOME: /home/svn
> LANG: en_US.UTF-8
>
> [track]
> -/usr/local/foo/prod: %(SOME_REPOS)/foo/production
> -/usr/local/foo/dev: %(SOME_REPOS)/foo/trunk
> +/usr/local/foo/prod: %(SOME_REPOS)s/foo/production
> +/usr/local/foo/dev: %(SOME_REPOS)s/foo/trunk
>
> Modified: subversion/trunk/tools/server-side/svnpubsub/svnwcsub.py
> URL:
> http://svn.apache.org/viewvc/subversion/trunk/tools/server-side/svnpubsub/svnwcsub.py?rev=1432778&r1=1432777&r2=1432778&view=diff
>
> ==============================================================================
> --- subversion/trunk/tools/server-side/svnpubsub/svnwcsub.py (original)
> +++ subversion/trunk/tools/server-side/svnpubsub/svnwcsub.py Mon Jan 14
> 01:16:57 2013
> @@ -30,19 +30,42 @@
> # See svnwcsub.conf for more information on its contents.
> #
>
> +# TODO:
> +# - bulk update at startup time to avoid backlog warnings
> +# - fold BDEC into Daemon
> +# - fold WorkingCopy._get_match() into __init__
> +# - remove wc_ready(). assume all WorkingCopy instances are usable.
> +# place the instances into .watch at creation. the .update_applies()
> +# just returns if the wc is disabled (eg. could not find wc dir)
> +# - figure out way to avoid the ASF-specific PRODUCTION_RE_FILTER
> +# (a base path exclusion list should work for the ASF)
> +# - add support for SIGHUP to reread the config and reinitialize working
> copies
> +# - joes will write documentation for svnpubsub as these items become
> fulfilled
> +# - make LOGLEVEL configurable
> +
> import errno
> import subprocess
> import threading
> import sys
> import os
> import re
> -import ConfigParser
> +import posixpath
> +try:
> + import ConfigParser
> +except ImportError:
> + import configparser as ConfigParser
> import time
> import logging.handlers
> -import Queue
> +try:
> + import Queue
> +except ImportError:
> + import queue as Queue
> import optparse
> import functools
> -import urlparse
> +try:
> + import urlparse
> +except ImportError:
> + import urllib.parse as urlparse
>
> import daemonize
> import svnpubsub.client
> @@ -151,15 +174,10 @@ class BigDoEverythingClasss(object):
> self.env = config.get_env()
> self.tracking = config.get_track()
> self.hook = config.get_value('hook')
> + self.streams = config.get_value('streams').split()
> self.worker = BackgroundWorker(self.svnbin, self.env, self.hook)
> self.watch = [ ]
>
> - self.hostports = [ ]
> - ### switch from URLs in the config to just host:port pairs
> - for url in config.get_value('streams').split():
> - parsed = urlparse.urlparse(url.strip())
> - self.hostports.append((parsed.hostname, parsed.port))
> -
> def start(self):
> for path, url in self.tracking.items():
> # working copies auto-register with the BDEC when they are
> ready.
> @@ -175,15 +193,15 @@ class BigDoEverythingClasss(object):
> def _normalize_path(self, path):
> if path[0] != '/':
> return "/" + path
> - return os.path.abspath(path)
> + return posixpath.abspath(path)
>
> - def commit(self, host, port, rev):
> - logging.info("COMMIT r%d (%d paths) from %s:%d"
> - % (rev.rev, len(rev.dirs_changed), host, port))
> + def commit(self, url, commit):
> + logging.info("COMMIT r%d (%d paths) from %s"
> + % (commit.id, len(commit.changed), url))
>
> - paths = map(self._normalize_path, rev.dirs_changed)
> + paths = map(self._normalize_path, commit.changed)
> if len(paths):
> - pre = os.path.commonprefix(paths)
> + pre = posixpath.commonprefix(paths)
> if pre == "/websites/":
> # special case for svnmucc "dynamic content" buildbot
> commits
> # just take the first production path to avoid updating
> all cms working copies
> @@ -194,8 +212,8 @@ class BigDoEverythingClasss(object):
> break
>
> #print "Common Prefix: %s" % (pre)
> - wcs = [wc for wc in self.watch if wc.update_applies(rev.uuid,
> pre)]
> - logging.info("Updating %d WC for r%d" % (len(wcs), rev.rev))
> + wcs = [wc for wc in self.watch if
> wc.update_applies(commit.repository, pre)]
> + logging.info("Updating %d WC for r%d" % (len(wcs), commit.id
> ))
> for wc in wcs:
> self.worker.add_work(OP_UPDATE, wc)
>
> @@ -376,18 +394,18 @@ class Daemon(daemonize.Daemon):
> # Start the BDEC (on the main thread), then start the client
> self.bdec.start()
>
> - mc = svnpubsub.client.MultiClient(self.bdec.hostports,
> + mc = svnpubsub.client.MultiClient(self.bdec.streams,
> self.bdec.commit,
> self._event)
> mc.run_forever()
>
> - def _event(self, host, port, event_name):
> + def _event(self, url, event_name, event_arg):
> if event_name == 'error':
> - logging.exception('from %s:%s', host, port)
> + logging.exception('from %s', url)
> elif event_name == 'ping':
> - logging.debug('ping from %s:%s', host, port)
> + logging.debug('ping from %s', url)
> else:
> - logging.info('"%s" from %s:%s', event_name, host, port)
> + logging.info('"%s" from %s', event_name, url)
>
>
> def prepare_logging(logfile):
>
> Modified: subversion/trunk/tools/server-side/svnpubsub/testserver.py
> URL:
> http://svn.apache.org/viewvc/subversion/trunk/tools/server-side/svnpubsub/testserver.py?rev=1432778&r1=1432777&r2=1432778&view=diff
>
> ==============================================================================
> --- subversion/trunk/tools/server-side/svnpubsub/testserver.py (original)
> +++ subversion/trunk/tools/server-side/svnpubsub/testserver.py Mon Jan 14
> 01:16:57 2013
> @@ -30,7 +30,7 @@ import BaseHTTPServer
>
> PORT = 2069
>
> -TEST_BODY = '<commit repository="12345678-1234-1234-1234-123456789012"
> revision="1234"><author>johndoe</author><date>2012-01-01 01:01:01 +0000
> (Sun, 01 Jan 2012)</date><log>Frob the ganoozle with the
> snookish</log><dirs_changed><path>one/path/</path><path>some/other/directory/</path></dirs_changed></commit>'
> +TEST_BODY = '{"svnpubsub": {"version": 1}}\0{"commit": {"type": "svn",
> "format": 1, "repository": "12345678-1234-1234-1234-123456789012", "id":
> "1234", "committer": "johndoe", "date": "2012-01-01 01:01:01 +0000 (Sun, 01
> Jan 2012)", "log": "Frob the ganoozle with the snookish", "changed":
> {"one/path/alpha": {"flags": "U "}, "some/other/directory/": {"flags": "_U
> "}}}}\0'
>
> SEND_KEEPALIVE = True
>
>
> Modified: subversion/trunk/tools/server-side/svnpubsub/watcher.py
> URL:
> http://svn.apache.org/viewvc/subversion/trunk/tools/server-side/svnpubsub/watcher.py?rev=1432778&r1=1432777&r2=1432778&view=diff
>
> ==============================================================================
> --- subversion/trunk/tools/server-side/svnpubsub/watcher.py (original)
> +++ subversion/trunk/tools/server-side/svnpubsub/watcher.py Mon Jan 14
> 01:16:57 2013
> @@ -19,39 +19,37 @@
> #
> # Watch for events from SvnPubSub and print them to stdout
> #
> -# ### usage...
> #
>
> import sys
> -import urlparse
> import pprint
> +try:
> + import urlparse
> +except ImportError:
> + import urllib.parse as urlparse
>
> import svnpubsub.client
> -import svnwcsub ### for ReloadableConfig
>
>
> -def _commit(host, port, rev):
> - print 'COMMIT: from %s:%s' % (host, port)
> - pprint.pprint(vars(rev), indent=2)
> +def _commit(url, commit):
> + print('COMMIT: from %s' % url)
> + pprint.pprint(vars(commit), indent=2)
>
>
> -def _event(host, port, event_name):
> - print 'EVENT: from %s:%s "%s"' % (host, port, event_name)
> +def _event(url, event_name, event_arg):
> + if event_arg:
> + print('EVENT: from %s "%s" "%s"' % (url, event_name, event_arg))
> + else:
> + print('EVENT: from %s "%s"' % (url, event_name))
>
>
> -def main(config_file):
> - config = svnwcsub.ReloadableConfig(config_file)
> - hostports = [ ]
> - for url in config.get_value('streams').split():
> - parsed = urlparse.urlparse(url)
> - hostports.append((parsed.hostname, parsed.port))
> -
> - mc = svnpubsub.client.MultiClient(hostports, _commit, _event)
> +def main(urls):
> + mc = svnpubsub.client.MultiClient(urls, _commit, _event)
> mc.run_forever()
>
>
> if __name__ == "__main__":
> - if len(sys.argv) != 2:
> - print "invalid args, read source code"
> + if len(sys.argv) < 2:
> + print("usage: watcher.py URL [URL...]")
> sys.exit(0)
> - main(sys.argv[1])
> + main(sys.argv[1:])
>
>
>
Received on 2013-01-14 08:22:38 CET