Mercurial > p > roundup > code
changeset 4148:131129bac77f gsoc-2009
Move notify-roundup.py to appropriate dir
| author | Pygi <pygi@users.sourceforge.net> |
|---|---|
| date | Thu, 02 Jul 2009 11:15:09 +0000 |
| parents | 84c42a8eed7c |
| children | 15bcdc202ed8 |
| files | scripts/notify-roundup.py scripts/notify-roundup/notify-roundup.py |
| diffstat | 2 files changed, 385 insertions(+), 385 deletions(-) [+] |
line wrap: on
line diff
--- a/scripts/notify-roundup.py Mon Jun 29 20:59:57 2009 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,385 +0,0 @@ -#!/usr/bin/python -# -# notify-roundup.py: call into a roundup tracker to notify it of commits -# -# USAGE: notify-roundup.py TRACKER-HOME REPOS-DIR REVISION -# notify-roundup.py TRACKER-HOME REPOS-DIR REVISION AUTHOR PROPNAME -# -# TRACKER-HOME is the tracker to notify -# -# See end of file for change history - -import sys, os, time, cStringIO, re, logging, smtplib, ConfigParser, socket - - -# configure logging -logger = logging.getLogger('notify-roundup') -hdlr = logging.FileHandler('/tmp/log') -formatter = logging.Formatter('%(asctime)s %(levelname)s %(message)s') -hdlr.setFormatter(formatter) -logger.addHandler(hdlr) -logger.propogate = False -logger.setLevel(logging.DEBUG) - -#print sys.argv -# now try to import stuff that might not work -try: - import roundup.instance, roundup.date - - import svn.fs - import svn.delta - import svn.repos - import svn.core -except: - logger.exception('Exception while importing Roundup and SVN') - sys.exit(1) - -class Failed(Exception): - pass -class Unauthorised(Failed): - pass - -def main(pool): - '''Handle the commit revision. - ''' - # command-line args - cfg = ConfigParser.ConfigParser() - cfg.read(sys.argv[1]) - repos_dir = sys.argv[2] - revision = int(sys.argv[3]) - - # get a handle on the revision in the repository - repos = Repository(repos_dir, revision, pool) - - repos.klass = cfg.get('main', 'item-class') - if not repos.extract_info(): - return - - if cfg.has_option('main', 'host'): - repos.host = cfg.get('main', 'host') - else: - repos.host = socket.gethostname() - - mode = cfg.get('main', 'mode') - if mode == 'local': - notify_local(cfg.get('local', 'tracker-home'), repos) - elif mode == 'email': - tracker_address = cfg.get('email', 'tracker-address') - domain = cfg.get('email', 'default-domain') - smtp_host = cfg.get('email', 'smtp-host') - if cfg.has_option('address mappings', repos.author): - mapped_email = cfg.get('address mappings', repos.author) - elif cfg.has_option('address mappings', '*'): - mapped_email = cfg.get('address mappings', '*') - else: - mapped_email = repos.author - if '@' not in mapped_email: - mapped_email += domain - notify_email(tracker_address, mapped_email, smtp_host, repos) - else: - logging.error('invalid mode %s in config file'%mode) - - -def notify_email(tracker_address, from_address, smtp_host, repos): - subject = '[%s%s] SVN commit message'%(repos.klass, repos.itemid) - if repos.status: - subject += ' [status=%s]'%repos.status - date = time.strftime('%Y-%m-%d %H:%M:%S', repos.date) - message = '''From: %s -To: %s -Subject: %s - -revision=%s -host=%s -repos=%s -date=%s -summary=%s - -%s'''%(from_address, tracker_address, subject, repos.rev, repos.host, - repos.repos_dir, date, repos.summary, repos.message) - - logger.debug('MESSAGE TO SEND\n%s'%message) - - smtp = smtplib.SMTP(smtp_host) - try: - smtp.sendmail(from_address, [tracker_address], message) - except: - logging.exception('mail to %r from %r via %r'%(tracker_address, - from_address, smtp_host)) - -def notify_local(tracker_home, repos): - # get a handle on the tracker db - tracker = roundup.instance.open(tracker_home) - db = tracker.open('admin') - try: - notify_local_inner(db, tracker_home, repos) - except: - db.rollback() - db.close() - raise - -def notify_local_inner(db, tracker_home, repos): - # sanity check - try: - db.getclass(repos.klass) - except KeyError: - logger.error('no such tracker class %s'%repos.klass) - raise Failed - if not db.getclass(repos.klass).hasnode(repos.itemid): - logger.error('no such %s item %s'%(repos.klass, repos.itemid)) - raise Failed - if repos.status: - try: - status_id = db.status.lookup(repos.status) - except KeyError: - logger.error('no such status %s'%repos.status) - raise Failed - - print repos.host, repos.repos_dir - # get the svn repo information from the tracker - try: - svn_repo_id = db.svn_repo.stringFind(host=repos.host, - path=repos.repos_dir)[0] - except IndexError: - logger.error('no repository %s in tracker'%repos.repos_dir) - raise Failed - - # log in as the appropriate user - try: - matches = db.user.stringFind(svn_name=repos.author) - except KeyError: - # the user class has no property "svn_name" - matches = [] - if matches: - userid = matches[0] - else: - try: - userid = db.user.lookup(repos.author) - except KeyError: - raise Failed, 'no Roundup user matching %s'%repos.author - username = db.user.get(userid, 'username') - db.close() - - # tell Roundup - tracker = roundup.instance.open(tracker_home) - db = tracker.open(username) - - # check perms - if not db.security.hasPermission('Create', userid, 'svn_rev'): - raise Unauthorised, "Can't create items of class 'svn_rev'" - if not db.security.hasPermission('Create', userid, 'msg'): - raise Unauthorised, "Can't create items of class 'msg'" - if not db.security.hasPermission('Edit', userid, repos.klass, - 'messages', repos.itemid): - raise Unauthorised, "Can't edit items of class '%s'"%repos.klass - if repos.status and not db.security.hasPermission('Edit', userid, - repos.klass, 'status', repos.itemid): - raise Unauthorised, "Can't edit items of class '%s'"%repos.klass - - # create the revision - svn_rev_id = db.svn_rev.create(repository=svn_repo_id, revision=repos.rev) - - # add the message to the spool - date = roundup.date.Date(repos.date) - msgid = db.msg.create(content=repos.message, summary=repos.summary, - author=userid, date=date, revision=svn_rev_id) - klass = db.getclass(repos.klass) - messages = klass.get(repos.itemid, 'messages') - messages.append(msgid) - klass.set(repos.itemid, messages=messages) - - # and set the status - if repos.status: - klass.set(repos.itemid, status=status_id) - - db.commit() - logger.debug('Roundup modification complete') - db.close() - - -def _select_adds(change): - return change.added -def _select_deletes(change): - return change.path is None -def _select_modifies(change): - return not change.added and change.path is not None - - -def generate_list(output, header, changelist, selection): - items = [ ] - for path, change in changelist: - if selection(change): - items.append((path, change)) - if not items: - return - - output.write('%s:\n' % header) - for fname, change in items: - if change.item_kind == svn.core.svn_node_dir: - is_dir = '/' - else: - is_dir = '' - if change.prop_changes: - if change.text_changed: - props = ' (contents, props changed)' - else: - props = ' (props changed)' - else: - props = '' - output.write(' %s%s%s\n' % (fname, is_dir, props)) - if change.added and change.base_path: - if is_dir: - text = '' - elif change.text_changed: - text = ', changed' - else: - text = ' unchanged' - output.write(' - copied%s from r%d, %s%s\n' - % (text, change.base_rev, change.base_path[1:], is_dir)) - -class Repository: - '''Hold roots and other information about the repository. From mailer.py - ''' - def __init__(self, repos_dir, rev, pool): - self.repos_dir = repos_dir - self.rev = rev - self.pool = pool - - self.repos_ptr = svn.repos.svn_repos_open(repos_dir, pool) - self.fs_ptr = svn.repos.svn_repos_fs(self.repos_ptr) - - self.roots = {} - - self.root_this = self.roots[rev] = svn.fs.revision_root(self.fs_ptr, - rev, self.pool) - - self.author = self.get_rev_prop(svn.core.SVN_PROP_REVISION_AUTHOR) - - def get_rev_prop(self, propname): - return svn.fs.revision_prop(self.fs_ptr, self.rev, propname, self.pool) - - def extract_info(self): - issue_re = re.compile('^\s*(%s)\s*(\d+)(\s+(\S+))?\s*$'%self.klass, - re.I) - - # parse for Roundup item information - log = self.get_rev_prop(svn.core.SVN_PROP_REVISION_LOG) or '' - for line in log.splitlines(): - m = issue_re.match(line) - if m: - break - else: - # nothing to do - return - - # parse out the issue information - klass = m.group(1) - self.itemid = m.group(2) - - issue = klass + self.itemid - self.status = m.group(4) - - logger.debug('Roundup info item=%r, status=%r'%(issue, self.status)) - - # get all the changes and sort by path - editor = svn.repos.RevisionChangeCollector(self.fs_ptr, self.rev, - self.pool) - e_ptr, e_baton = svn.delta.make_editor(editor, self.pool) - svn.repos.svn_repos_replay(self.root_this, e_ptr, e_baton, self.pool) - - changelist = editor.changes.items() - changelist.sort() - - # figure out the changed directories - dirs = { } - for path, change in changelist: - if change.item_kind == svn.core.svn_node_dir: - dirs[path] = None - else: - idx = path.rfind('/') - if idx == -1: - dirs[''] = None - else: - dirs[path[:idx]] = None - - dirlist = dirs.keys() - - # figure out the common portion of all the dirs. note that there is - # no "common" if only a single dir was changed, or the root was changed. - if len(dirs) == 1 or dirs.has_key(''): - commondir = '' - else: - common = dirlist.pop().split('/') - for d in dirlist: - parts = d.split('/') - for i in range(len(common)): - if i == len(parts) or common[i] != parts[i]: - del common[i:] - break - commondir = '/'.join(common) - if commondir: - # strip the common portion from each directory - l = len(commondir) + 1 - dirlist = [ ] - for d in dirs.keys(): - if d == commondir: - dirlist.append('.') - else: - dirlist.append(d[l:]) - else: - # nothing in common, so reset the list of directories - dirlist = dirs.keys() - - # compose the basic subject line. later, we can prefix it. - dirlist.sort() - dirlist = ' '.join(dirlist) - - if commondir: - self.summary = 'r%d - in %s: %s' % (self.rev, commondir, dirlist) - else: - self.summary = 'r%d - %s' % (self.rev, dirlist) - - # Generate email for the various groups and option-params. - output = cStringIO.StringIO() - - # print summary sections - generate_list(output, 'Added', changelist, _select_adds) - generate_list(output, 'Removed', changelist, _select_deletes) - generate_list(output, 'Modified', changelist, _select_modifies) - - output.write('Log:\n%s\n'%log) - - self.message = output.getvalue() - - svndate = self.get_rev_prop(svn.core.SVN_PROP_REVISION_DATE) - self.date = time.localtime(svn.core.secs_from_timestr(svndate, - self.pool)) - - return True - -if __name__ == '__main__': - try: - svn.core.run_app(main) - except Failed, message: - logger.error(message) - sys.exit(1) - except: - logger.exception('top level') - sys.exit(1) - -# -# 2005-05-16 - 1.2 -# -# - Status wasn't being set by ID in local mode -# - Wasn't catching errors in local changes, hence not cleaning up db -# correctly -# - svnauditor.py wasn't handling the fifth argument from notify-roundup.py -# - viewcvs_url formatting wasn't quite right -# -# 2005-05-04 - 1.1 -# - Several fixes from Ron Alford -# - Don't change issue titles to "SVN commit message..." -# -# 2005-04-26 - 1.0 -# - Initial version released -#
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/scripts/notify-roundup/notify-roundup.py Thu Jul 02 11:15:09 2009 +0000 @@ -0,0 +1,385 @@ +#!/usr/bin/python +# +# notify-roundup.py: call into a roundup tracker to notify it of commits +# +# USAGE: notify-roundup.py TRACKER-HOME REPOS-DIR REVISION +# notify-roundup.py TRACKER-HOME REPOS-DIR REVISION AUTHOR PROPNAME +# +# TRACKER-HOME is the tracker to notify +# +# See end of file for change history + +import sys, os, time, cStringIO, re, logging, smtplib, ConfigParser, socket + + +# configure logging +logger = logging.getLogger('notify-roundup') +hdlr = logging.FileHandler('/tmp/log') +formatter = logging.Formatter('%(asctime)s %(levelname)s %(message)s') +hdlr.setFormatter(formatter) +logger.addHandler(hdlr) +logger.propogate = False +logger.setLevel(logging.DEBUG) + +#print sys.argv +# now try to import stuff that might not work +try: + import roundup.instance, roundup.date + + import svn.fs + import svn.delta + import svn.repos + import svn.core +except: + logger.exception('Exception while importing Roundup and SVN') + sys.exit(1) + +class Failed(Exception): + pass +class Unauthorised(Failed): + pass + +def main(pool): + '''Handle the commit revision. + ''' + # command-line args + cfg = ConfigParser.ConfigParser() + cfg.read(sys.argv[1]) + repos_dir = sys.argv[2] + revision = int(sys.argv[3]) + + # get a handle on the revision in the repository + repos = Repository(repos_dir, revision, pool) + + repos.klass = cfg.get('main', 'item-class') + if not repos.extract_info(): + return + + if cfg.has_option('main', 'host'): + repos.host = cfg.get('main', 'host') + else: + repos.host = socket.gethostname() + + mode = cfg.get('main', 'mode') + if mode == 'local': + notify_local(cfg.get('local', 'tracker-home'), repos) + elif mode == 'email': + tracker_address = cfg.get('email', 'tracker-address') + domain = cfg.get('email', 'default-domain') + smtp_host = cfg.get('email', 'smtp-host') + if cfg.has_option('address mappings', repos.author): + mapped_email = cfg.get('address mappings', repos.author) + elif cfg.has_option('address mappings', '*'): + mapped_email = cfg.get('address mappings', '*') + else: + mapped_email = repos.author + if '@' not in mapped_email: + mapped_email += domain + notify_email(tracker_address, mapped_email, smtp_host, repos) + else: + logging.error('invalid mode %s in config file'%mode) + + +def notify_email(tracker_address, from_address, smtp_host, repos): + subject = '[%s%s] SVN commit message'%(repos.klass, repos.itemid) + if repos.status: + subject += ' [status=%s]'%repos.status + date = time.strftime('%Y-%m-%d %H:%M:%S', repos.date) + message = '''From: %s +To: %s +Subject: %s + +revision=%s +host=%s +repos=%s +date=%s +summary=%s + +%s'''%(from_address, tracker_address, subject, repos.rev, repos.host, + repos.repos_dir, date, repos.summary, repos.message) + + logger.debug('MESSAGE TO SEND\n%s'%message) + + smtp = smtplib.SMTP(smtp_host) + try: + smtp.sendmail(from_address, [tracker_address], message) + except: + logging.exception('mail to %r from %r via %r'%(tracker_address, + from_address, smtp_host)) + +def notify_local(tracker_home, repos): + # get a handle on the tracker db + tracker = roundup.instance.open(tracker_home) + db = tracker.open('admin') + try: + notify_local_inner(db, tracker_home, repos) + except: + db.rollback() + db.close() + raise + +def notify_local_inner(db, tracker_home, repos): + # sanity check + try: + db.getclass(repos.klass) + except KeyError: + logger.error('no such tracker class %s'%repos.klass) + raise Failed + if not db.getclass(repos.klass).hasnode(repos.itemid): + logger.error('no such %s item %s'%(repos.klass, repos.itemid)) + raise Failed + if repos.status: + try: + status_id = db.status.lookup(repos.status) + except KeyError: + logger.error('no such status %s'%repos.status) + raise Failed + + print repos.host, repos.repos_dir + # get the svn repo information from the tracker + try: + svn_repo_id = db.svn_repo.stringFind(host=repos.host, + path=repos.repos_dir)[0] + except IndexError: + logger.error('no repository %s in tracker'%repos.repos_dir) + raise Failed + + # log in as the appropriate user + try: + matches = db.user.stringFind(svn_name=repos.author) + except KeyError: + # the user class has no property "svn_name" + matches = [] + if matches: + userid = matches[0] + else: + try: + userid = db.user.lookup(repos.author) + except KeyError: + raise Failed, 'no Roundup user matching %s'%repos.author + username = db.user.get(userid, 'username') + db.close() + + # tell Roundup + tracker = roundup.instance.open(tracker_home) + db = tracker.open(username) + + # check perms + if not db.security.hasPermission('Create', userid, 'svn_rev'): + raise Unauthorised, "Can't create items of class 'svn_rev'" + if not db.security.hasPermission('Create', userid, 'msg'): + raise Unauthorised, "Can't create items of class 'msg'" + if not db.security.hasPermission('Edit', userid, repos.klass, + 'messages', repos.itemid): + raise Unauthorised, "Can't edit items of class '%s'"%repos.klass + if repos.status and not db.security.hasPermission('Edit', userid, + repos.klass, 'status', repos.itemid): + raise Unauthorised, "Can't edit items of class '%s'"%repos.klass + + # create the revision + svn_rev_id = db.svn_rev.create(repository=svn_repo_id, revision=repos.rev) + + # add the message to the spool + date = roundup.date.Date(repos.date) + msgid = db.msg.create(content=repos.message, summary=repos.summary, + author=userid, date=date, revision=svn_rev_id) + klass = db.getclass(repos.klass) + messages = klass.get(repos.itemid, 'messages') + messages.append(msgid) + klass.set(repos.itemid, messages=messages) + + # and set the status + if repos.status: + klass.set(repos.itemid, status=status_id) + + db.commit() + logger.debug('Roundup modification complete') + db.close() + + +def _select_adds(change): + return change.added +def _select_deletes(change): + return change.path is None +def _select_modifies(change): + return not change.added and change.path is not None + + +def generate_list(output, header, changelist, selection): + items = [ ] + for path, change in changelist: + if selection(change): + items.append((path, change)) + if not items: + return + + output.write('%s:\n' % header) + for fname, change in items: + if change.item_kind == svn.core.svn_node_dir: + is_dir = '/' + else: + is_dir = '' + if change.prop_changes: + if change.text_changed: + props = ' (contents, props changed)' + else: + props = ' (props changed)' + else: + props = '' + output.write(' %s%s%s\n' % (fname, is_dir, props)) + if change.added and change.base_path: + if is_dir: + text = '' + elif change.text_changed: + text = ', changed' + else: + text = ' unchanged' + output.write(' - copied%s from r%d, %s%s\n' + % (text, change.base_rev, change.base_path[1:], is_dir)) + +class Repository: + '''Hold roots and other information about the repository. From mailer.py + ''' + def __init__(self, repos_dir, rev, pool): + self.repos_dir = repos_dir + self.rev = rev + self.pool = pool + + self.repos_ptr = svn.repos.svn_repos_open(repos_dir, pool) + self.fs_ptr = svn.repos.svn_repos_fs(self.repos_ptr) + + self.roots = {} + + self.root_this = self.roots[rev] = svn.fs.revision_root(self.fs_ptr, + rev, self.pool) + + self.author = self.get_rev_prop(svn.core.SVN_PROP_REVISION_AUTHOR) + + def get_rev_prop(self, propname): + return svn.fs.revision_prop(self.fs_ptr, self.rev, propname, self.pool) + + def extract_info(self): + issue_re = re.compile('^\s*(%s)\s*(\d+)(\s+(\S+))?\s*$'%self.klass, + re.I) + + # parse for Roundup item information + log = self.get_rev_prop(svn.core.SVN_PROP_REVISION_LOG) or '' + for line in log.splitlines(): + m = issue_re.match(line) + if m: + break + else: + # nothing to do + return + + # parse out the issue information + klass = m.group(1) + self.itemid = m.group(2) + + issue = klass + self.itemid + self.status = m.group(4) + + logger.debug('Roundup info item=%r, status=%r'%(issue, self.status)) + + # get all the changes and sort by path + editor = svn.repos.RevisionChangeCollector(self.fs_ptr, self.rev, + self.pool) + e_ptr, e_baton = svn.delta.make_editor(editor, self.pool) + svn.repos.svn_repos_replay(self.root_this, e_ptr, e_baton, self.pool) + + changelist = editor.changes.items() + changelist.sort() + + # figure out the changed directories + dirs = { } + for path, change in changelist: + if change.item_kind == svn.core.svn_node_dir: + dirs[path] = None + else: + idx = path.rfind('/') + if idx == -1: + dirs[''] = None + else: + dirs[path[:idx]] = None + + dirlist = dirs.keys() + + # figure out the common portion of all the dirs. note that there is + # no "common" if only a single dir was changed, or the root was changed. + if len(dirs) == 1 or dirs.has_key(''): + commondir = '' + else: + common = dirlist.pop().split('/') + for d in dirlist: + parts = d.split('/') + for i in range(len(common)): + if i == len(parts) or common[i] != parts[i]: + del common[i:] + break + commondir = '/'.join(common) + if commondir: + # strip the common portion from each directory + l = len(commondir) + 1 + dirlist = [ ] + for d in dirs.keys(): + if d == commondir: + dirlist.append('.') + else: + dirlist.append(d[l:]) + else: + # nothing in common, so reset the list of directories + dirlist = dirs.keys() + + # compose the basic subject line. later, we can prefix it. + dirlist.sort() + dirlist = ' '.join(dirlist) + + if commondir: + self.summary = 'r%d - in %s: %s' % (self.rev, commondir, dirlist) + else: + self.summary = 'r%d - %s' % (self.rev, dirlist) + + # Generate email for the various groups and option-params. + output = cStringIO.StringIO() + + # print summary sections + generate_list(output, 'Added', changelist, _select_adds) + generate_list(output, 'Removed', changelist, _select_deletes) + generate_list(output, 'Modified', changelist, _select_modifies) + + output.write('Log:\n%s\n'%log) + + self.message = output.getvalue() + + svndate = self.get_rev_prop(svn.core.SVN_PROP_REVISION_DATE) + self.date = time.localtime(svn.core.secs_from_timestr(svndate, + self.pool)) + + return True + +if __name__ == '__main__': + try: + svn.core.run_app(main) + except Failed, message: + logger.error(message) + sys.exit(1) + except: + logger.exception('top level') + sys.exit(1) + +# +# 2005-05-16 - 1.2 +# +# - Status wasn't being set by ID in local mode +# - Wasn't catching errors in local changes, hence not cleaning up db +# correctly +# - svnauditor.py wasn't handling the fifth argument from notify-roundup.py +# - viewcvs_url formatting wasn't quite right +# +# 2005-05-04 - 1.1 +# - Several fixes from Ron Alford +# - Don't change issue titles to "SVN commit message..." +# +# 2005-04-26 - 1.0 +# - Initial version released +#
