| [4247] | 1 | """ |
|---|
| 2 | implementation of the RepositoryChangeListener interface for svn |
|---|
| 3 | """ |
|---|
| 4 | |
|---|
| 5 | import os |
|---|
| [5078] | 6 | import subprocess |
|---|
| [4247] | 7 | |
|---|
| [5079] | 8 | from dateutil.parser import parse |
|---|
| 9 | |
|---|
| [4247] | 10 | from genshi.builder import tag |
|---|
| 11 | from repository_hook_system.filesystemhooks import FileSystemHooks |
|---|
| 12 | from repository_hook_system.interface import IRepositoryChangeListener |
|---|
| 13 | from repository_hook_system.interface import IRepositoryHookSubscriber |
|---|
| 14 | from repository_hook_system.interface import IRepositoryHookSystem |
|---|
| [5078] | 15 | from trac.config import Option |
|---|
| [4247] | 16 | from trac.config import ListOption |
|---|
| 17 | from trac.core import * |
|---|
| 18 | from trac.util.text import CRLF |
|---|
| [5317] | 19 | from trac.versioncontrol import NoSuchChangeset |
|---|
| [6999] | 20 | from trac.web.chrome import add_warning |
|---|
| [4247] | 21 | from utils import iswritable |
|---|
| 22 | |
|---|
| 23 | class SVNHookSystem(FileSystemHooks): |
|---|
| 24 | """implementation of IRepositoryChangeListener for SVN repositories""" |
|---|
| 25 | |
|---|
| 26 | implements(IRepositoryHookSystem, IRepositoryChangeListener) |
|---|
| 27 | listeners = ExtensionPoint(IRepositoryHookSubscriber) |
|---|
| 28 | hooks = [ 'pre-commit', 'post-commit', 'pre-revprop-change', 'post-revprop-change' ] |
|---|
| 29 | |
|---|
| [5078] | 30 | |
|---|
| 31 | _svnlook = Option('svn', 'svnlook', default='/usr/bin/svnlook') |
|---|
| 32 | |
|---|
| [4247] | 33 | ### methods for FileSystemHooks |
|---|
| 34 | |
|---|
| 35 | def filename(self, hookname): |
|---|
| 36 | location = self.env.config.get('trac', 'repository_dir') |
|---|
| 37 | return os.path.join(location, 'hooks', hookname) |
|---|
| 38 | |
|---|
| 39 | def args(self): |
|---|
| 40 | return [ '$2' ] |
|---|
| 41 | |
|---|
| 42 | ### methods for IRepositoryHookAdminContributer |
|---|
| 43 | |
|---|
| 44 | def render(self, hookname, req): |
|---|
| 45 | filename = self.filename(hookname) |
|---|
| 46 | try: |
|---|
| 47 | contents = file(filename).read() # check for CRLF here too? |
|---|
| [5056] | 48 | return tag.textarea(contents, rows='25', cols='80', name='hook-file-contents', disabled=not self.can_enable(hookname) or None) |
|---|
| 49 | |
|---|
| [4247] | 50 | except IOError: |
|---|
| [5056] | 51 | if self.can_enable(filename): |
|---|
| [4247] | 52 | text = "No %s hook file yet exists; enable this hook to create one" % hookname |
|---|
| 53 | else: |
|---|
| [5056] | 54 | text = "The file, %s, is unwritable; enabling this hook will have no effect" % filename |
|---|
| [4247] | 55 | return text |
|---|
| 56 | |
|---|
| 57 | def process_post(self, hookname, req): |
|---|
| 58 | |
|---|
| 59 | contents = req.args.get('hook-file-contents', None) |
|---|
| 60 | if contents is None: |
|---|
| 61 | return |
|---|
| 62 | if os.linesep != CRLF: |
|---|
| 63 | contents = os.linesep.join(contents.split(CRLF)) # form contents will have this |
|---|
| 64 | |
|---|
| 65 | filename = self.filename(hookname) |
|---|
| 66 | if not os.path.exists(filename): |
|---|
| 67 | if not iswritable(filename): |
|---|
| [6999] | 68 | add_warning(req, 'File "%s" not writable' % filename) |
|---|
| 69 | return |
|---|
| [4247] | 70 | f = file(filename, 'w') |
|---|
| 71 | print >> f, contents |
|---|
| [6999] | 72 | f.close() |
|---|
| 73 | try: |
|---|
| 74 | os.chmod(filename, self.mode) |
|---|
| 75 | except: # won't work on winblows |
|---|
| 76 | pass |
|---|
| [4247] | 77 | |
|---|
| 78 | ### methods for IRepositoryChangeListener |
|---|
| 79 | |
|---|
| 80 | def type(self): |
|---|
| 81 | return ['svn', 'svnsync'] |
|---|
| 82 | |
|---|
| 83 | def available_hooks(self): |
|---|
| 84 | return self.hooks |
|---|
| 85 | |
|---|
| 86 | def subscribers(self, hookname): |
|---|
| 87 | """returns the active subscribers for a given hook name""" |
|---|
| 88 | |
|---|
| 89 | # XXX this is all SCM-agnostic; should be moved out |
|---|
| 90 | return [ subscriber for subscriber in self.listeners |
|---|
| 91 | if subscriber.__class__.__name__ |
|---|
| 92 | in getattr(self, hookname, []) |
|---|
| 93 | and subscriber.is_available(self.type(), hookname) ] |
|---|
| 94 | |
|---|
| [5078] | 95 | def changeset(self, repo, hookname, commit_id): |
|---|
| [4247] | 96 | """ |
|---|
| 97 | return the changeset given the repository object and revision number |
|---|
| 98 | """ |
|---|
| 99 | |
|---|
| [5078] | 100 | if hookname in ['post-commit', 'post-revprop-change']: |
|---|
| 101 | revision = commit_id |
|---|
| 102 | try: |
|---|
| 103 | |
|---|
| 104 | chgset = repo.get_changeset(revision) |
|---|
| 105 | except NoSuchChangeset: |
|---|
| 106 | # XXX should probably throw an exception (same one?) |
|---|
| [5317] | 107 | raise # out of scope changesets are not cached |
|---|
| [5078] | 108 | return chgset |
|---|
| 109 | else: |
|---|
| 110 | transaction = commit_id |
|---|
| 111 | repo = self.env.config.get('trac', 'repository_dir') |
|---|
| 112 | |
|---|
| 113 | def svnlook(subcommand, *args): |
|---|
| 114 | |
|---|
| [5079] | 115 | process = subprocess.Popen([self._svnlook, subcommand, repo, '-t', transaction] + list(args), stdout=subprocess.PIPE) |
|---|
| [5078] | 116 | return process.communicate()[0] |
|---|
| 117 | |
|---|
| 118 | # get the attributes |
|---|
| 119 | author = svnlook('author').strip() |
|---|
| 120 | date = parse(svnlook('date').split('(')[0].strip()) |
|---|
| [5317] | 121 | # XXX FIXME |
|---|
| 122 | # from datetime import datetime |
|---|
| 123 | # date = datetime.now() |
|---|
| [5078] | 124 | |
|---|
| 125 | |
|---|
| 126 | message = svnlook('log').strip() |
|---|
| 127 | rev = transaction |
|---|
| 128 | |
|---|
| 129 | attributes = dict(author=author, |
|---|
| 130 | date=date, |
|---|
| 131 | message=message, |
|---|
| 132 | rev=rev) |
|---|
| 133 | chgset = type('DummyChangeset', (object,), attributes) |
|---|
| 134 | return chgset() |
|---|
| 135 | |
|---|
| 136 | |
|---|
| [4247] | 137 | for hook in SVNHookSystem.hooks: |
|---|
| 138 | setattr(SVNHookSystem, hook, |
|---|
| [7099] | 139 | ListOption('repository-hooks', hook, default='', |
|---|
| [4247] | 140 | doc="active listeners for SVN changes on the %s hook" % hook)) |
|---|