# HG changeset patch # User Pygi # Date 1246557766 0 # Node ID fe9b0fdb1790bd27ba83fb3d62b9e7d61ae8380a # Parent 38dac0b488de6eea41dbf76bdf474e17a02ef529 Moved beta-notify to notify-roundup diff -r 38dac0b488de -r fe9b0fdb1790 scripts/beta-notify/detectors/svnauditor.py --- a/scripts/beta-notify/detectors/svnauditor.py Thu Jul 02 18:02:12 2009 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,89 +0,0 @@ -# Subversion integration auditor -# -# Watches for messages formatted by the notify-roundup.py Subversion hook -# script, and parses the meta-data out of them, removing it from the -# message body in the process. -# -# Place this file in your tracker's "detectors" directory. -# -# See end of file for change history - -import re, sets - -import roundup.date - -import ConfigParser - -svn_msg = re.compile('^(revision|repos|host|date|summary)=(.*)$') -ini_path = '/path/to/notify-roundup.ini' - -def parse_message(db, cl, nodeid, newvalues): - '''Parse an incoming message for Subversion information. - ''' - - # collect up our meta-data from the message - info = {} - content = [] - for line in newvalues.get('content', '').splitlines(): - m = svn_msg.match(line) - if not m: - content.append(line) - continue - info[m.group(1)] = m.group(2).strip() - - # only continue if all five pieces of information are present - if len(info) != 5: - return - - # look up the repository id - try: - svn_repo_id = db.svn_repo.stringFind(path=info['repos'], - host=info['host'])[0] - except IndexError: - #logger.error('no repository %s in tracker'%repos.repos_dir) - return - - # create the subversion revision item - svn_rev_id = db.svn_rev.create(repository=svn_repo_id, - revision=int(info['revision'])) - - # minor bit of content cleaning - remove the single leading blank line - if content and not content[0].strip(): - del content[0] - - # set the info on the message - newvalues['content'] = '\n'.join(content) - newvalues['date'] = roundup.date.Date(info['date']) - newvalues['summary'] = info['summary'] - newvalues['revision'] = svn_rev_id - -def undo_title(db, cl, nodeid, newvalues): - '''Don't change the title of issues to "SVN commit message..."''' - if newvalues.get('title', '').lower().startswith('svn commit message'): - del newvalues['title'] - - -def init(db): - db.msg.audit('create', parse_message) - cfg = ConfigParser.ConfigParser() - cfg.read(ini_path) - fetch_klass = cfg.get('main', 'item-class') - klass = db.getclass(fetch_klass) - klass.audit('set', undo_title) - -# -# 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 -# diff -r 38dac0b488de -r fe9b0fdb1790 scripts/beta-notify/doc/README --- a/scripts/beta-notify/doc/README Thu Jul 02 18:02:12 2009 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,53 +0,0 @@ -Roundup devel tracker in love with subversion -============================================= - -This document serves a tutorial on setting up a Roundup instance -with devel template featuring subversion integration. Several assumptions -are made in this tutorial: - -1) You've checked out gsoc-2009 Roundup branch -2) You know how to setup subversion repository -3) You want to work with a local subversion repository - -Let's follow the steps: - -1) Setup subversion post-commit hook - -a) Rename post-commit.tmpl to post-commit -b) Make sure its executable (chmod +x) -c) Add the following to it: - -PYTHON=/usr/bin/python -NOTIFY=/path/to/notify-roundup.py[1] -CONFIG=/path/to/notify-roundup.ini[1] -PYTHONPATH=/path/to/roundup/instance "$PYTHON" "$NOTIFY" "$CONFIG" "$REPOS" "$REV" - -[1] notify-roundup.py and notify-roundup.ini can be found in scripts/notify-roundup - -2) Modify notify-roundup.ini - -a) Set tracker home: tracker-home = /path/to/tracker-home -b) Set address mappings - -3) Copy html templates from scripts/notify-roundup/html to share/roundup/templates/devel/html - -4) Copy revision_info.py extension from scripts/notify-roundup/extensions to share/roundup/templates/devel/extensions - -5) Now, now, this wasn't so hard :) - -How-to format subversion commit message -======================================= - -By default, notify-roundup handles bugs. -To change status of bug1, format your commit -message like this: - -bug1 pending - -Current limitations -=================== - -1) Notify-roundup can work only with one item-class -2) Notify-roundup can only modify Status - -We are aware of the those limitations, and have plans to alleviate them in the future. diff -r 38dac0b488de -r fe9b0fdb1790 scripts/beta-notify/extensions/revision_info.py --- a/scripts/beta-notify/extensions/revision_info.py Thu Jul 02 18:02:12 2009 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,55 +0,0 @@ -# Subversion integration information fetcher -# -# Extracts information about a specific revision from a local repository. -# -# Place this file in your tracker's "extensions" directory. - -import sys, os, time -from svn import core, fs, delta, repos - -def inner(pool, path, rev): - repos_ptr = repos.svn_repos_open(path, pool) - fs_ptr = repos.svn_repos_fs(repos_ptr) - - root = fs.revision_root(fs_ptr, rev, pool) - base_rev = rev - 1 - - # get all changes - editor = repos.RevisionChangeCollector(fs_ptr, rev, pool) - e_ptr, e_baton = delta.make_editor(editor, pool) - repos.svn_repos_replay(root, e_ptr, e_baton, pool) - - changelist = editor.changes.items() - changelist.sort() - - base_root = fs.revision_root(fs_ptr, base_rev, pool) - - l = [] - for filepath, change in changelist: - d = {'path': filepath, 'info': ''} - if change.path: - if change.added: - d['action'] = 'new' - else: - d['action'] = 'modify' - differ = fs.FileDiff(base_root, change.path, root, filepath, - pool, '-L \t(original) -L \t(new) -u'.split(' ')) - d['info'] = differ.get_pipe().read() - else: - d['action'] = 'delete' - l.append(d) - return l - - -def getRevisionInfo(revision): - #path = '/Users/richard/tmp/test_repo' - #rev = 2 - return core.run_app(inner, str(revision['repository']['path']), - int(revision['revision'])) - -def init(instance): - instance.registerUtil('getRevisionInfo', getRevisionInfo) - -if __name__ == '__main__': - print getRevision(1) - diff -r 38dac0b488de -r fe9b0fdb1790 scripts/beta-notify/html/bug.item.html --- a/scripts/beta-notify/html/bug.item.html Thu Jul 02 18:02:12 2009 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,275 +0,0 @@ - - -<tal:block condition="context/id" i18n:translate="" - >Bug <span tal:replace="context/id" i18n:name="id" - />: <span tal:replace="context/title" i18n:name="title" - /> - <span tal:replace="config/TRACKER_NAME" i18n:name="tracker" -/></tal:block> -<tal:block condition="not:context/id" i18n:translate="" - >New Bug report - <span tal:replace="config/TRACKER_NAME" i18n:name="tracker" -/></tal:block> - - - New Bug - New Bug Editing - Bug - Bug Editing - - - - -

- You are not allowed to view this page.

- -

- Please login with your username and password.

- -
- -
- -
classification - - - - - - - - - - - - - - - - - - - - -
Title:title - - -
- - Type: - type - - Severity: - severity
- - Components: - components - - Versions: - versions
-
- -
process - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - Status: - status - - Resolution: - resolution
- Dependencies: - - - - -
View: -
-
- Superseder: - - - - - -
View: - -
-
Assigned To:assignedto menuNosy List: - - - -
- - Priority: - priorityKeywords:keywords
Comment: - -
File: - - -
File Description:
-
- - - - - -
-   - - - - submit button - Make a copy -
-
- -

- Created on - by , - last changed - by . -

- - - - - - - - - - - - - - - - -
Files
File nameUploadedDescriptionEditRemove
- dld link - - creator's name, - creation date - - edit - -
- - - -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - -
Messages
msgAuthor: ()Date:
- -
-
content
-
- - - -
- - - -
diff -r 38dac0b488de -r fe9b0fdb1790 scripts/beta-notify/html/svn_rev.item.html --- a/scripts/beta-notify/html/svn_rev.item.html Thu Jul 02 18:02:12 2009 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,31 +0,0 @@ - - - SVN Revision <span tal:replace="context/revision" /> - - - SVN Revision - - - - -

You are not - allowed to view this page.

- -
- - - - - - - -
-
- - - -
- - - -
diff -r 38dac0b488de -r fe9b0fdb1790 scripts/beta-notify/html/user.item.html --- a/scripts/beta-notify/html/user.item.html Thu Jul 02 18:02:12 2009 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,169 +0,0 @@ - - -<tal:if condition="context/id" i18n:translate="" - >User <span tal:replace="context/id" i18n:name="id" - />: <span tal:replace="context/username" i18n:name="title" - /> - <span tal:replace="config/TRACKER_NAME" i18n:name="tracker" -/></tal:if> -<tal:if condition="not:context/id" i18n:translate="" - >New User - <span tal:replace="config/TRACKER_NAME" i18n:name="tracker" -/></tal:if> - - - - - - - New User - New User Editing - User - User Editing - - - - -

- You are not allowed to view this page.

- -

- Please login with your username and password.

- -
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Name
Login Name
Login Password
Confirm Password
Subversion loginsvn_name
- - - - - - - (to give the user more than one role, - enter a comma,separated,list) -
Phone
Organisation
Timezone -
E-mail address - calvin@the-z.org - - - -   -
- -
-   - - - - -
-
- - - - - - - - -
Note: highlighted fields are required.
-
- - - -
- - - -
diff -r 38dac0b488de -r fe9b0fdb1790 scripts/beta-notify/notify-roundup.ini --- a/scripts/beta-notify/notify-roundup.ini Thu Jul 02 18:02:12 2009 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,48 +0,0 @@ -; notify-roundup.py configuration file - -[main] -; notify a local or emailed tracker -- 'email' or 'local' -;mode = email -mode = local - -; change this to detect other issue types -; multiple issue classes are possible (use regular expression "either" syntax) -item-class = bug -; item-class = system -; item-class = dev|system|network - -; only set this if socket.gethostname() doesn't return the host's name as -; registered with your tracker -; host = host.name.example - - - -[local] -; if notifying a local tracker, configure this variable -tracker-home = /path/to/your/tracker-home - -[email] -; if notifying a tracker by email, configure these variables -smtp-host = smtp-host.example -tracker-address = issues@host.example -; email-domain is used in conjuntion with the address mappings below -default-domain = @host.example - -[vcs] -; choose a VCS type -- 'svn' or 'hg' -;type = hg -type = svn - -[address mappings] -; map Subversion author names to email addresses that the tracker will -; recognise. The "email :: default-domain" var will be appended if the -; address doesn't specify a domain. -richard = rjones -; richard = ni@spam.example - -; If no mapping is defined for a particular author, we either: -; 1. use the @ address or, -; 2. if a "*" entry is defined under address mappings, then we use -; that address as the from address. -;* = unknown - diff -r 38dac0b488de -r fe9b0fdb1790 scripts/beta-notify/notify-roundup.py --- a/scripts/beta-notify/notify-roundup.py Thu Jul 02 18:02:12 2009 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,399 +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]) - - vcs_type = cfg.get('vcs', 'type') - - # get a handle on the revision in the VCS repository - if vcs_type == 'svn': - repos = SVNRepository(repos_dir, revision, pool) - elif vcs_type == 'hg': - repos = HGRepository(repos_dir, revision, pool) - else: - logging.error('we currently don\'t support %s VCS type'%vcs_type) - - 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 HGRepository: - '''Holds roots and other information about the hg repository.' - ''' - - def __init__(self,repos_dir,rev,pool): - pass - -class SVNRepository: - '''Hold roots and other information about the svn 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 -# diff -r 38dac0b488de -r fe9b0fdb1790 scripts/notify-roundup/detectors/svnauditor.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/scripts/notify-roundup/detectors/svnauditor.py Thu Jul 02 18:02:46 2009 +0000 @@ -0,0 +1,89 @@ +# Subversion integration auditor +# +# Watches for messages formatted by the notify-roundup.py Subversion hook +# script, and parses the meta-data out of them, removing it from the +# message body in the process. +# +# Place this file in your tracker's "detectors" directory. +# +# See end of file for change history + +import re, sets + +import roundup.date + +import ConfigParser + +svn_msg = re.compile('^(revision|repos|host|date|summary)=(.*)$') +ini_path = '/path/to/notify-roundup.ini' + +def parse_message(db, cl, nodeid, newvalues): + '''Parse an incoming message for Subversion information. + ''' + + # collect up our meta-data from the message + info = {} + content = [] + for line in newvalues.get('content', '').splitlines(): + m = svn_msg.match(line) + if not m: + content.append(line) + continue + info[m.group(1)] = m.group(2).strip() + + # only continue if all five pieces of information are present + if len(info) != 5: + return + + # look up the repository id + try: + svn_repo_id = db.svn_repo.stringFind(path=info['repos'], + host=info['host'])[0] + except IndexError: + #logger.error('no repository %s in tracker'%repos.repos_dir) + return + + # create the subversion revision item + svn_rev_id = db.svn_rev.create(repository=svn_repo_id, + revision=int(info['revision'])) + + # minor bit of content cleaning - remove the single leading blank line + if content and not content[0].strip(): + del content[0] + + # set the info on the message + newvalues['content'] = '\n'.join(content) + newvalues['date'] = roundup.date.Date(info['date']) + newvalues['summary'] = info['summary'] + newvalues['revision'] = svn_rev_id + +def undo_title(db, cl, nodeid, newvalues): + '''Don't change the title of issues to "SVN commit message..."''' + if newvalues.get('title', '').lower().startswith('svn commit message'): + del newvalues['title'] + + +def init(db): + db.msg.audit('create', parse_message) + cfg = ConfigParser.ConfigParser() + cfg.read(ini_path) + fetch_klass = cfg.get('main', 'item-class') + klass = db.getclass(fetch_klass) + klass.audit('set', undo_title) + +# +# 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 +# diff -r 38dac0b488de -r fe9b0fdb1790 scripts/notify-roundup/doc/README --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/scripts/notify-roundup/doc/README Thu Jul 02 18:02:46 2009 +0000 @@ -0,0 +1,53 @@ +Roundup devel tracker in love with subversion +============================================= + +This document serves a tutorial on setting up a Roundup instance +with devel template featuring subversion integration. Several assumptions +are made in this tutorial: + +1) You've checked out gsoc-2009 Roundup branch +2) You know how to setup subversion repository +3) You want to work with a local subversion repository + +Let's follow the steps: + +1) Setup subversion post-commit hook + +a) Rename post-commit.tmpl to post-commit +b) Make sure its executable (chmod +x) +c) Add the following to it: + +PYTHON=/usr/bin/python +NOTIFY=/path/to/notify-roundup.py[1] +CONFIG=/path/to/notify-roundup.ini[1] +PYTHONPATH=/path/to/roundup/instance "$PYTHON" "$NOTIFY" "$CONFIG" "$REPOS" "$REV" + +[1] notify-roundup.py and notify-roundup.ini can be found in scripts/notify-roundup + +2) Modify notify-roundup.ini + +a) Set tracker home: tracker-home = /path/to/tracker-home +b) Set address mappings + +3) Copy html templates from scripts/notify-roundup/html to share/roundup/templates/devel/html + +4) Copy revision_info.py extension from scripts/notify-roundup/extensions to share/roundup/templates/devel/extensions + +5) Now, now, this wasn't so hard :) + +How-to format subversion commit message +======================================= + +By default, notify-roundup handles bugs. +To change status of bug1, format your commit +message like this: + +bug1 pending + +Current limitations +=================== + +1) Notify-roundup can work only with one item-class +2) Notify-roundup can only modify Status + +We are aware of the those limitations, and have plans to alleviate them in the future. diff -r 38dac0b488de -r fe9b0fdb1790 scripts/notify-roundup/extensions/revision_info.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/scripts/notify-roundup/extensions/revision_info.py Thu Jul 02 18:02:46 2009 +0000 @@ -0,0 +1,55 @@ +# Subversion integration information fetcher +# +# Extracts information about a specific revision from a local repository. +# +# Place this file in your tracker's "extensions" directory. + +import sys, os, time +from svn import core, fs, delta, repos + +def inner(pool, path, rev): + repos_ptr = repos.svn_repos_open(path, pool) + fs_ptr = repos.svn_repos_fs(repos_ptr) + + root = fs.revision_root(fs_ptr, rev, pool) + base_rev = rev - 1 + + # get all changes + editor = repos.RevisionChangeCollector(fs_ptr, rev, pool) + e_ptr, e_baton = delta.make_editor(editor, pool) + repos.svn_repos_replay(root, e_ptr, e_baton, pool) + + changelist = editor.changes.items() + changelist.sort() + + base_root = fs.revision_root(fs_ptr, base_rev, pool) + + l = [] + for filepath, change in changelist: + d = {'path': filepath, 'info': ''} + if change.path: + if change.added: + d['action'] = 'new' + else: + d['action'] = 'modify' + differ = fs.FileDiff(base_root, change.path, root, filepath, + pool, '-L \t(original) -L \t(new) -u'.split(' ')) + d['info'] = differ.get_pipe().read() + else: + d['action'] = 'delete' + l.append(d) + return l + + +def getRevisionInfo(revision): + #path = '/Users/richard/tmp/test_repo' + #rev = 2 + return core.run_app(inner, str(revision['repository']['path']), + int(revision['revision'])) + +def init(instance): + instance.registerUtil('getRevisionInfo', getRevisionInfo) + +if __name__ == '__main__': + print getRevision(1) + diff -r 38dac0b488de -r fe9b0fdb1790 scripts/notify-roundup/html/bug.item.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/scripts/notify-roundup/html/bug.item.html Thu Jul 02 18:02:46 2009 +0000 @@ -0,0 +1,275 @@ + + +<tal:block condition="context/id" i18n:translate="" + >Bug <span tal:replace="context/id" i18n:name="id" + />: <span tal:replace="context/title" i18n:name="title" + /> - <span tal:replace="config/TRACKER_NAME" i18n:name="tracker" +/></tal:block> +<tal:block condition="not:context/id" i18n:translate="" + >New Bug report - <span tal:replace="config/TRACKER_NAME" i18n:name="tracker" +/></tal:block> + + + New Bug + New Bug Editing + Bug + Bug Editing + + + + +

+ You are not allowed to view this page.

+ +

+ Please login with your username and password.

+ +
+ +
+ +
classification + + + + + + + + + + + + + + + + + + + + +
Title:title + + +
+ + Type: + type + + Severity: + severity
+ + Components: + components + + Versions: + versions
+
+ +
process + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + Status: + status + + Resolution: + resolution
+ Dependencies: + + + + +
View: +
+
+ Superseder: + + + + + +
View: + +
+
Assigned To:assignedto menuNosy List: + + + +
+ + Priority: + priorityKeywords:keywords
Comment: + +
File: + + +
File Description:
+
+ + + + + +
+   + + + + submit button + Make a copy +
+
+ +

+ Created on + by , + last changed + by . +

+ + + + + + + + + + + + + + + + +
Files
File nameUploadedDescriptionEditRemove
+ dld link + + creator's name, + creation date + + edit + +
+ + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Messages
msgAuthor: ()Date:
+ +
+
content
+
+ + + +
+ + + +
diff -r 38dac0b488de -r fe9b0fdb1790 scripts/notify-roundup/html/svn_rev.item.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/scripts/notify-roundup/html/svn_rev.item.html Thu Jul 02 18:02:46 2009 +0000 @@ -0,0 +1,31 @@ + + + SVN Revision <span tal:replace="context/revision" /> + + + SVN Revision + + + + +

You are not + allowed to view this page.

+ +
+ + + + + + + +
+
+ + + +
+ + + +
diff -r 38dac0b488de -r fe9b0fdb1790 scripts/notify-roundup/html/user.item.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/scripts/notify-roundup/html/user.item.html Thu Jul 02 18:02:46 2009 +0000 @@ -0,0 +1,169 @@ + + +<tal:if condition="context/id" i18n:translate="" + >User <span tal:replace="context/id" i18n:name="id" + />: <span tal:replace="context/username" i18n:name="title" + /> - <span tal:replace="config/TRACKER_NAME" i18n:name="tracker" +/></tal:if> +<tal:if condition="not:context/id" i18n:translate="" + >New User - <span tal:replace="config/TRACKER_NAME" i18n:name="tracker" +/></tal:if> + + + + + + + New User + New User Editing + User + User Editing + + + + +

+ You are not allowed to view this page.

+ +

+ Please login with your username and password.

+ +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Name
Login Name
Login Password
Confirm Password
Subversion loginsvn_name
+ + + + + + + (to give the user more than one role, + enter a comma,separated,list) +
Phone
Organisation
Timezone +
E-mail address + calvin@the-z.org + + + +   +
+ +
+   + + + + +
+
+ + + + + + + + +
Note: highlighted fields are required.
+
+ + + +
+ + + +
diff -r 38dac0b488de -r fe9b0fdb1790 scripts/notify-roundup/notify-roundup.ini --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/scripts/notify-roundup/notify-roundup.ini Thu Jul 02 18:02:46 2009 +0000 @@ -0,0 +1,48 @@ +; notify-roundup.py configuration file + +[main] +; notify a local or emailed tracker -- 'email' or 'local' +;mode = email +mode = local + +; change this to detect other issue types +; multiple issue classes are possible (use regular expression "either" syntax) +item-class = bug +; item-class = system +; item-class = dev|system|network + +; only set this if socket.gethostname() doesn't return the host's name as +; registered with your tracker +; host = host.name.example + + + +[local] +; if notifying a local tracker, configure this variable +tracker-home = /path/to/your/tracker-home + +[email] +; if notifying a tracker by email, configure these variables +smtp-host = smtp-host.example +tracker-address = issues@host.example +; email-domain is used in conjuntion with the address mappings below +default-domain = @host.example + +[vcs] +; choose a VCS type -- 'svn' or 'hg' +;type = hg +type = svn + +[address mappings] +; map Subversion author names to email addresses that the tracker will +; recognise. The "email :: default-domain" var will be appended if the +; address doesn't specify a domain. +richard = rjones +; richard = ni@spam.example + +; If no mapping is defined for a particular author, we either: +; 1. use the @ address or, +; 2. if a "*" entry is defined under address mappings, then we use +; that address as the from address. +;* = unknown + diff -r 38dac0b488de -r fe9b0fdb1790 scripts/notify-roundup/notify-roundup.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/scripts/notify-roundup/notify-roundup.py Thu Jul 02 18:02:46 2009 +0000 @@ -0,0 +1,399 @@ +#!/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]) + + vcs_type = cfg.get('vcs', 'type') + + # get a handle on the revision in the VCS repository + if vcs_type == 'svn': + repos = SVNRepository(repos_dir, revision, pool) + elif vcs_type == 'hg': + repos = HGRepository(repos_dir, revision, pool) + else: + logging.error('we currently don\'t support %s VCS type'%vcs_type) + + 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 HGRepository: + '''Holds roots and other information about the hg repository.' + ''' + + def __init__(self,repos_dir,rev,pool): + pass + +class SVNRepository: + '''Hold roots and other information about the svn 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 +#