##// END OF EJS Templates
refactoring for new vcs implementation...
marcink -
r512:d945c95b default
parent child Browse files
Show More
@@ -1,74 +1,74 b''
1 1 # List of modules to import when celery starts.
2 2 import sys
3 3 import os
4 4 import ConfigParser
5 5 root = os.getcwd()
6 6
7 PYLONS_CONFIG_NAME = 'development.ini'
7 PYLONS_CONFIG_NAME = 'production.ini'
8 8
9 9 sys.path.append(root)
10 10 config = ConfigParser.ConfigParser({'here':root})
11 11 config.read('%s/%s' % (root, PYLONS_CONFIG_NAME))
12 12 PYLONS_CONFIG = config
13 13
14 14 CELERY_IMPORTS = ("pylons_app.lib.celerylib.tasks",)
15 15
16 16 ## Result store settings.
17 17 CELERY_RESULT_BACKEND = "database"
18 18 CELERY_RESULT_DBURI = dict(config.items('app:main'))['sqlalchemy.db1.url']
19 19 CELERY_RESULT_SERIALIZER = 'json'
20 20
21 21
22 22 BROKER_CONNECTION_MAX_RETRIES = 30
23 23
24 24 ## Broker settings.
25 25 BROKER_HOST = "localhost"
26 26 BROKER_PORT = 5672
27 27 BROKER_VHOST = "rabbitmqhost"
28 28 BROKER_USER = "rabbitmq"
29 29 BROKER_PASSWORD = "qweqwe"
30 30
31 31 ## Worker settings
32 32 ## If you're doing mostly I/O you can have more processes,
33 33 ## but if mostly spending CPU, try to keep it close to the
34 34 ## number of CPUs on your machine. If not set, the number of CPUs/cores
35 35 ## available will be used.
36 36 CELERYD_CONCURRENCY = 2
37 37 # CELERYD_LOG_FILE = "celeryd.log"
38 38 CELERYD_LOG_LEVEL = "DEBUG"
39 39 CELERYD_MAX_TASKS_PER_CHILD = 3
40 40
41 41 #Tasks will never be sent to the queue, but executed locally instead.
42 42 CELERY_ALWAYS_EAGER = False
43 43
44 44 #===============================================================================
45 45 # EMAIL SETTINGS
46 46 #===============================================================================
47 47 pylons_email_config = dict(config.items('DEFAULT'))
48 48
49 49 CELERY_SEND_TASK_ERROR_EMAILS = True
50 50
51 51 #List of (name, email_address) tuples for the admins that should receive error e-mails.
52 52 ADMINS = [('Administrator', pylons_email_config.get('email_to'))]
53 53
54 54 #The e-mail address this worker sends e-mails from. Default is "celery@localhost".
55 55 SERVER_EMAIL = pylons_email_config.get('error_email_from')
56 56
57 57 #The mail server to use. Default is "localhost".
58 58 MAIL_HOST = pylons_email_config.get('smtp_server')
59 59
60 60 #Username (if required) to log on to the mail server with.
61 61 MAIL_HOST_USER = pylons_email_config.get('smtp_username')
62 62
63 63 #Password (if required) to log on to the mail server with.
64 64 MAIL_HOST_PASSWORD = pylons_email_config.get('smtp_password')
65 65
66 66 MAIL_PORT = pylons_email_config.get('smtp_port')
67 67
68 68
69 69 #===============================================================================
70 70 # INSTRUCTIONS FOR RABBITMQ
71 71 #===============================================================================
72 72 # rabbitmqctl add_user rabbitmq qweqwe
73 73 # rabbitmqctl add_vhost rabbitmqhost
74 74 # rabbitmqctl set_permissions -p rabbitmqhost rabbitmq ".*" ".*" ".*"
@@ -1,138 +1,138 b''
1 1 #!/usr/bin/env python
2 2 # encoding: utf-8
3 3 # changeset controller for pylons
4 4 # Copyright (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
5 5 # This program is free software; you can redistribute it and/or
6 6 # modify it under the terms of the GNU General Public License
7 7 # as published by the Free Software Foundation; version 2
8 8 # of the License or (at your opinion) any later version of the license.
9 9 #
10 10 # This program is distributed in the hope that it will be useful,
11 11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 13 # GNU General Public License for more details.
14 14 #
15 15 # You should have received a copy of the GNU General Public License
16 16 # along with this program; if not, write to the Free Software
17 17 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
18 18 # MA 02110-1301, USA.
19 19 """
20 20 Created on April 25, 2010
21 21 changeset controller for pylons
22 22 @author: marcink
23 23 """
24 24 from pylons import tmpl_context as c, url, request, response
25 25 from pylons.controllers.util import redirect
26 26 from pylons_app.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
27 27 from pylons_app.lib.base import BaseController, render
28 28 from pylons_app.model.hg_model import HgModel
29 29 from vcs.exceptions import RepositoryError
30 30 from vcs.nodes import FileNode
31 31 from vcs.utils import diffs as differ
32 32 import logging
33 33 import traceback
34 34
35 35 log = logging.getLogger(__name__)
36 36
37 37 class ChangesetController(BaseController):
38 38
39 39 @LoginRequired()
40 40 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
41 41 'repository.admin')
42 42 def __before__(self):
43 43 super(ChangesetController, self).__before__()
44 44
45 45 def index(self, revision):
46 46 hg_model = HgModel()
47 47 try:
48 48 c.changeset = hg_model.get_repo(c.repo_name).get_changeset(revision)
49 49 except RepositoryError:
50 50 log.error(traceback.format_exc())
51 51 return redirect(url('hg_home'))
52 52 else:
53 53 try:
54 54 c.changeset_old = c.changeset.parents[0]
55 55 except IndexError:
56 56 c.changeset_old = None
57 57 c.changes = []
58 58
59 59 for node in c.changeset.added:
60 60 filenode_old = FileNode(node.path, '')
61 61 if filenode_old.is_binary or node.is_binary:
62 62 diff = 'binary file'
63 63 else:
64 64 f_udiff = differ.get_udiff(filenode_old, node)
65 65 diff = differ.DiffProcessor(f_udiff).as_html()
66 66
67 67 cs1 = None
68 cs2 = node.last_changeset.raw_id
68 cs2 = node.last_changeset.short_id
69 69 c.changes.append(('added', node, diff, cs1, cs2))
70 70
71 71 for node in c.changeset.changed:
72 72 filenode_old = c.changeset_old.get_node(node.path)
73 73 if filenode_old.is_binary or node.is_binary:
74 74 diff = 'binary file'
75 75 else:
76 76 f_udiff = differ.get_udiff(filenode_old, node)
77 77 diff = differ.DiffProcessor(f_udiff).as_html()
78 78
79 cs1 = filenode_old.last_changeset.raw_id
80 cs2 = node.last_changeset.raw_id
79 cs1 = filenode_old.last_changeset.short_id
80 cs2 = node.last_changeset.short_id
81 81 c.changes.append(('changed', node, diff, cs1, cs2))
82 82
83 83 for node in c.changeset.removed:
84 84 c.changes.append(('removed', node, None, None, None))
85 85
86 86 return render('changeset/changeset.html')
87 87
88 88 def raw_changeset(self,revision):
89 89
90 90 hg_model = HgModel()
91 91 method = request.GET.get('diff','show')
92 92 try:
93 93 c.changeset = hg_model.get_repo(c.repo_name).get_changeset(revision)
94 94 except RepositoryError:
95 95 log.error(traceback.format_exc())
96 96 return redirect(url('hg_home'))
97 97 else:
98 98 try:
99 99 c.changeset_old = c.changeset.parents[0]
100 100 except IndexError:
101 101 c.changeset_old = None
102 102 c.changes = []
103 103
104 104 for node in c.changeset.added:
105 105 filenode_old = FileNode(node.path, '')
106 106 if filenode_old.is_binary or node.is_binary:
107 107 diff = 'binary file'
108 108 else:
109 109 f_udiff = differ.get_udiff(filenode_old, node)
110 110 diff = differ.DiffProcessor(f_udiff).raw_diff()
111 111
112 112 cs1 = None
113 cs2 = node.last_changeset.raw_id
113 cs2 = node.last_changeset.short_id
114 114 c.changes.append(('added', node, diff, cs1, cs2))
115 115
116 116 for node in c.changeset.changed:
117 117 filenode_old = c.changeset_old.get_node(node.path)
118 118 if filenode_old.is_binary or node.is_binary:
119 119 diff = 'binary file'
120 120 else:
121 121 f_udiff = differ.get_udiff(filenode_old, node)
122 122 diff = differ.DiffProcessor(f_udiff).raw_diff()
123 123
124 cs1 = filenode_old.last_changeset.raw_id
125 cs2 = node.last_changeset.raw_id
124 cs1 = filenode_old.last_changeset.short_id
125 cs2 = node.last_changeset.short_id
126 126 c.changes.append(('changed', node, diff, cs1, cs2))
127 127
128 128 response.content_type = 'text/plain'
129 129 if method == 'download':
130 130 response.content_disposition = 'attachment; filename=%s.patch' % revision
131 131 parent = True if len(c.changeset.parents) > 0 else False
132 132 c.parent_tmpl = 'Parent %s' % c.changeset.parents[0]._hex if parent else ''
133 133
134 134 c.diffs = ''
135 135 for x in c.changes:
136 136 c.diffs += x[2]
137 137
138 138 return render('changeset/raw_changeset.html')
@@ -1,80 +1,80 b''
1 1 #!/usr/bin/env python
2 2 # encoding: utf-8
3 3 # feed controller for pylons
4 4 # Copyright (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
5 5
6 6 # This program is free software; you can redistribute it and/or
7 7 # modify it under the terms of the GNU General Public License
8 8 # as published by the Free Software Foundation; version 2
9 9 # of the License or (at your opinion) any later version of the license.
10 10 #
11 11 # This program is distributed in the hope that it will be useful,
12 12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 14 # GNU General Public License for more details.
15 15 #
16 16 # You should have received a copy of the GNU General Public License
17 17 # along with this program; if not, write to the Free Software
18 18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
19 19 # MA 02110-1301, USA.
20 20 """
21 21 Created on April 23, 2010
22 22 feed controller for pylons
23 23 @author: marcink
24 24 """
25 25 from pylons import tmpl_context as c, url, response
26 26 from pylons_app.lib.base import BaseController, render
27 27 from pylons_app.model.hg_model import HgModel
28 28 from webhelpers.feedgenerator import Atom1Feed, Rss201rev2Feed
29 29 import logging
30 30 log = logging.getLogger(__name__)
31 31
32 32 class FeedController(BaseController):
33 33
34 34 #secure it or not ?
35 35 def __before__(self):
36 36 super(FeedController, self).__before__()
37 37 #common values for feeds
38 38 self.description = 'Changes on %s repository'
39 39 self.title = "%s feed"
40 40 self.language = 'en-us'
41 41 self.ttl = "5"
42 42 self.feed_nr = 10
43 43
44 44 def atom(self, repo_name):
45 45 """Produce an atom-1.0 feed via feedgenerator module"""
46 46 feed = Atom1Feed(title=self.title % repo_name,
47 47 link=url('summary_home', repo_name=repo_name, qualified=True),
48 48 description=self.description % repo_name,
49 49 language=self.language,
50 50 ttl=self.ttl)
51 51
52 52 changesets = HgModel().get_repo(repo_name)
53 53
54 54 for cs in changesets[:self.feed_nr]:
55 55 feed.add_item(title=cs.message,
56 56 link=url('changeset_home', repo_name=repo_name,
57 revision=cs.raw_id, qualified=True),
57 revision=cs.short_id, qualified=True),
58 58 description=str(cs.date))
59 59
60 60 response.content_type = feed.mime_type
61 61 return feed.writeString('utf-8')
62 62
63 63
64 64 def rss(self, repo_name):
65 65 """Produce an rss2 feed via feedgenerator module"""
66 66 feed = Rss201rev2Feed(title=self.title % repo_name,
67 67 link=url('summary_home', repo_name=repo_name, qualified=True),
68 68 description=self.description % repo_name,
69 69 language=self.language,
70 70 ttl=self.ttl)
71 71
72 72 changesets = HgModel().get_repo(repo_name)
73 73 for cs in changesets[:self.feed_nr]:
74 74 feed.add_item(title=cs.message,
75 75 link=url('changeset_home', repo_name=repo_name,
76 revision=cs.raw_id, qualified=True),
76 revision=cs.short_id, qualified=True),
77 77 description=str(cs.date))
78 78
79 79 response.content_type = feed.mime_type
80 80 return feed.writeString('utf-8')
@@ -1,207 +1,207 b''
1 1 #!/usr/bin/env python
2 2 # encoding: utf-8
3 3 # files controller for pylons
4 4 # Copyright (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
5 5
6 6 # This program is free software; you can redistribute it and/or
7 7 # modify it under the terms of the GNU General Public License
8 8 # as published by the Free Software Foundation; version 2
9 9 # of the License or (at your opinion) any later version of the license.
10 10 #
11 11 # This program is distributed in the hope that it will be useful,
12 12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 14 # GNU General Public License for more details.
15 15 #
16 16 # You should have received a copy of the GNU General Public License
17 17 # along with this program; if not, write to the Free Software
18 18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
19 19 # MA 02110-1301, USA.
20 20 """
21 21 Created on April 21, 2010
22 22 files controller for pylons
23 23 @author: marcink
24 24 """
25 25 from mercurial import archival
26 26 from pylons import request, response, session, tmpl_context as c, url
27 27 from pylons.controllers.util import redirect
28 28 from pylons_app.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
29 29 from pylons_app.lib.base import BaseController, render
30 30 from pylons_app.lib.utils import EmptyChangeset
31 31 from pylons_app.model.hg_model import HgModel
32 32 from vcs.exceptions import RepositoryError, ChangesetError
33 33 from vcs.nodes import FileNode
34 34 from vcs.utils import diffs as differ
35 35 import logging
36 36 import pylons_app.lib.helpers as h
37 37 import tempfile
38 38
39 39 log = logging.getLogger(__name__)
40 40
41 41 class FilesController(BaseController):
42 42
43 43 @LoginRequired()
44 44 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
45 45 'repository.admin')
46 46 def __before__(self):
47 47 super(FilesController, self).__before__()
48 48 c.file_size_limit = 250 * 1024 #limit of file size to display
49 49
50 50 def index(self, repo_name, revision, f_path):
51 51 hg_model = HgModel()
52 52 c.repo = repo = hg_model.get_repo(c.repo_name)
53 53 revision = request.POST.get('at_rev', None) or revision
54 54
55 55 def get_next_rev(cur):
56 56 max_rev = len(c.repo.revisions) - 1
57 57 r = cur + 1
58 58 if r > max_rev:
59 59 r = max_rev
60 60 return r
61 61
62 62 def get_prev_rev(cur):
63 63 r = cur - 1
64 64 return r
65 65
66 66 c.f_path = f_path
67 67
68 68
69 69 try:
70 70 cur_rev = repo.get_changeset(revision).revision
71 prev_rev = repo.get_changeset(get_prev_rev(cur_rev)).raw_id
72 next_rev = repo.get_changeset(get_next_rev(cur_rev)).raw_id
71 prev_rev = repo.get_changeset(get_prev_rev(cur_rev)).short_id
72 next_rev = repo.get_changeset(get_next_rev(cur_rev)).short_id
73 73
74 74 c.url_prev = url('files_home', repo_name=c.repo_name,
75 75 revision=prev_rev, f_path=f_path)
76 76 c.url_next = url('files_home', repo_name=c.repo_name,
77 77 revision=next_rev, f_path=f_path)
78 78
79 79 c.changeset = repo.get_changeset(revision)
80 80
81 c.cur_rev = c.changeset.raw_id
81 c.cur_rev = c.changeset.short_id
82 82 c.rev_nr = c.changeset.revision
83 83 c.files_list = c.changeset.get_node(f_path)
84 84 c.file_history = self._get_history(repo, c.files_list, f_path)
85 85
86 86 except (RepositoryError, ChangesetError):
87 87 c.files_list = None
88 88
89 89 return render('files/files.html')
90 90
91 91 def rawfile(self, repo_name, revision, f_path):
92 92 hg_model = HgModel()
93 93 c.repo = hg_model.get_repo(c.repo_name)
94 94 file_node = c.repo.get_changeset(revision).get_node(f_path)
95 95 response.content_type = file_node.mimetype
96 96 response.content_disposition = 'attachment; filename=%s' \
97 97 % f_path.split('/')[-1]
98 98 return file_node.content
99 99
100 100 def raw(self, repo_name, revision, f_path):
101 101 hg_model = HgModel()
102 102 c.repo = hg_model.get_repo(c.repo_name)
103 103 file_node = c.repo.get_changeset(revision).get_node(f_path)
104 104 response.content_type = 'text/plain'
105 105
106 106 return file_node.content
107 107
108 108 def annotate(self, repo_name, revision, f_path):
109 109 hg_model = HgModel()
110 110 c.repo = hg_model.get_repo(c.repo_name)
111 111 cs = c.repo.get_changeset(revision)
112 112 c.file = cs.get_node(f_path)
113 113 c.file_msg = cs.get_file_message(f_path)
114 c.cur_rev = cs.raw_id
114 c.cur_rev = cs.short_id
115 115 c.rev_nr = cs.revision
116 116 c.f_path = f_path
117 117
118 118 return render('files/files_annotate.html')
119 119
120 120 def archivefile(self, repo_name, revision, fileformat):
121 121 archive_specs = {
122 122 '.tar.bz2': ('application/x-tar', 'tbz2'),
123 123 '.tar.gz': ('application/x-tar', 'tgz'),
124 124 '.zip': ('application/zip', 'zip'),
125 125 }
126 126 if not archive_specs.has_key(fileformat):
127 127 return 'Unknown archive type %s' % fileformat
128 128
129 129 def read_in_chunks(file_object, chunk_size=1024 * 40):
130 130 """Lazy function (generator) to read a file piece by piece.
131 131 Default chunk size: 40k."""
132 132 while True:
133 133 data = file_object.read(chunk_size)
134 134 if not data:
135 135 break
136 136 yield data
137 137
138 138 archive = tempfile.TemporaryFile()
139 139 repo = HgModel().get_repo(repo_name).repo
140 140 fname = '%s-%s%s' % (repo_name, revision, fileformat)
141 141 archival.archive(repo, archive, revision, archive_specs[fileformat][1],
142 142 prefix='%s-%s' % (repo_name, revision))
143 143 response.content_type = archive_specs[fileformat][0]
144 144 response.content_disposition = 'attachment; filename=%s' % fname
145 145 archive.seek(0)
146 146 return read_in_chunks(archive)
147 147
148 148 def diff(self, repo_name, f_path):
149 149 hg_model = HgModel()
150 150 diff1 = request.GET.get('diff1')
151 151 diff2 = request.GET.get('diff2')
152 152 c.action = request.GET.get('diff')
153 153 c.no_changes = diff1 == diff2
154 154 c.f_path = f_path
155 155 c.repo = hg_model.get_repo(c.repo_name)
156 156
157 157 try:
158 158 if diff1 not in ['', None, 'None', '0' * 12]:
159 159 c.changeset_1 = c.repo.get_changeset(diff1)
160 160 node1 = c.changeset_1.get_node(f_path)
161 161 else:
162 162 c.changeset_1 = EmptyChangeset()
163 163 node1 = FileNode('.', '')
164 164 if diff2 not in ['', None, 'None', '0' * 12]:
165 165 c.changeset_2 = c.repo.get_changeset(diff2)
166 166 node2 = c.changeset_2.get_node(f_path)
167 167 else:
168 168 c.changeset_2 = EmptyChangeset()
169 169 node2 = FileNode('.', '')
170 170 except RepositoryError:
171 171 return redirect(url('files_home',
172 172 repo_name=c.repo_name, f_path=f_path))
173 173
174 c.diff1 = 'r%s:%s' % (c.changeset_1.revision, c.changeset_1.raw_id)
175 c.diff2 = 'r%s:%s' % (c.changeset_2.revision, c.changeset_2.raw_id)
174 c.diff1 = 'r%s:%s' % (c.changeset_1.revision, c.changeset_1.short_id)
175 c.diff2 = 'r%s:%s' % (c.changeset_2.revision, c.changeset_2.short_id)
176 176 f_udiff = differ.get_udiff(node1, node2)
177 177
178 178 diff = differ.DiffProcessor(f_udiff)
179 179
180 180 if c.action == 'download':
181 181 diff_name = '%s_vs_%s.diff' % (diff1, diff2)
182 182 response.content_type = 'text/plain'
183 183 response.content_disposition = 'attachment; filename=%s' \
184 184 % diff_name
185 185 return diff.raw_diff()
186 186
187 187 elif c.action == 'raw':
188 188 c.cur_diff = '<pre class="raw">%s</pre>' % h.escape(diff.raw_diff())
189 189 elif c.action == 'diff':
190 190 c.cur_diff = diff.as_html()
191 191 else:
192 192 #default option
193 193 c.cur_diff = diff.as_html()
194 194
195 195 if not c.cur_diff: c.no_changes = True
196 196 return render('files/file_diff.html')
197 197
198 198 def _get_history(self, repo, node, f_path):
199 199 from vcs.nodes import NodeKind
200 200 if not node.kind is NodeKind.FILE:
201 201 return []
202 202 changesets = node.history
203 203 hist_l = []
204 204 for chs in changesets:
205 n_desc = 'r%s:%s' % (chs.revision, chs._short)
206 hist_l.append((chs._short, n_desc,))
205 n_desc = 'r%s:%s' % (chs.revision, chs.short_id)
206 hist_l.append((chs.short_id, n_desc,))
207 207 return hist_l
@@ -1,383 +1,383 b''
1 1 """Helper functions
2 2
3 3 Consists of functions to typically be used within templates, but also
4 4 available to Controllers. This module is available to both as 'h'.
5 5 """
6 6 from pygments.formatters import HtmlFormatter
7 7 from pygments import highlight as code_highlight
8 8 from pylons import url, app_globals as g
9 9 from pylons.i18n.translation import _, ungettext
10 10 from vcs.utils.annotate import annotate_highlight
11 11 from webhelpers.html import literal, HTML, escape
12 12 from webhelpers.html.tools import *
13 13 from webhelpers.html.builder import make_tag
14 14 from webhelpers.html.tags import auto_discovery_link, checkbox, css_classes, \
15 15 end_form, file, form, hidden, image, javascript_link, link_to, link_to_if, \
16 16 link_to_unless, ol, required_legend, select, stylesheet_link, submit, text, \
17 17 password, textarea, title, ul, xml_declaration, radio
18 18 from webhelpers.html.tools import auto_link, button_to, highlight, js_obfuscate, \
19 19 mail_to, strip_links, strip_tags, tag_re
20 20 from webhelpers.number import format_byte_size, format_bit_size
21 21 from webhelpers.pylonslib import Flash as _Flash
22 22 from webhelpers.pylonslib.secure_form import secure_form
23 23 from webhelpers.text import chop_at, collapse, convert_accented_entities, \
24 24 convert_misc_entities, lchop, plural, rchop, remove_formatting, \
25 25 replace_whitespace, urlify, truncate, wrap_paragraphs
26 26
27 27 #Custom helpers here :)
28 28 class _Link(object):
29 29 '''
30 30 Make a url based on label and url with help of url_for
31 31 @param label:name of link if not defined url is used
32 32 @param url: the url for link
33 33 '''
34 34
35 35 def __call__(self, label='', *url_, **urlargs):
36 36 if label is None or '':
37 37 label = url
38 38 link_fn = link_to(label, url(*url_, **urlargs))
39 39 return link_fn
40 40
41 41 link = _Link()
42 42
43 43 class _GetError(object):
44 44
45 45 def __call__(self, field_name, form_errors):
46 46 tmpl = """<span class="error_msg">%s</span>"""
47 47 if form_errors and form_errors.has_key(field_name):
48 48 return literal(tmpl % form_errors.get(field_name))
49 49
50 50 get_error = _GetError()
51 51
52 52 def recursive_replace(str, replace=' '):
53 53 """
54 54 Recursive replace of given sign to just one instance
55 55 @param str: given string
56 56 @param replace:char to find and replace multiple instances
57 57
58 58 Examples::
59 59 >>> recursive_replace("Mighty---Mighty-Bo--sstones",'-')
60 60 'Mighty-Mighty-Bo-sstones'
61 61 """
62 62
63 63 if str.find(replace * 2) == -1:
64 64 return str
65 65 else:
66 66 str = str.replace(replace * 2, replace)
67 67 return recursive_replace(str, replace)
68 68
69 69 class _ToolTip(object):
70 70
71 71 def __call__(self, tooltip_title, trim_at=50):
72 72 """
73 73 Special function just to wrap our text into nice formatted autowrapped
74 74 text
75 75 @param tooltip_title:
76 76 """
77 77
78 78 return wrap_paragraphs(escape(tooltip_title), trim_at)\
79 79 .replace('\n', '<br/>')
80 80
81 81 def activate(self):
82 82 """
83 83 Adds tooltip mechanism to the given Html all tooltips have to have
84 84 set class tooltip and set attribute tooltip_title.
85 85 Then a tooltip will be generated based on that
86 86 All with yui js tooltip
87 87 """
88 88
89 89 js = '''
90 90 YAHOO.util.Event.onDOMReady(function(){
91 91 function toolTipsId(){
92 92 var ids = [];
93 93 var tts = YAHOO.util.Dom.getElementsByClassName('tooltip');
94 94
95 95 for (var i = 0; i < tts.length; i++) {
96 96 //if element doesn not have and id autgenerate one for tooltip
97 97
98 98 if (!tts[i].id){
99 99 tts[i].id='tt'+i*100;
100 100 }
101 101 ids.push(tts[i].id);
102 102 }
103 103 return ids
104 104 };
105 105 var myToolTips = new YAHOO.widget.Tooltip("tooltip", {
106 106 context: toolTipsId(),
107 107 monitorresize:false,
108 108 xyoffset :[0,0],
109 109 autodismissdelay:300000,
110 110 hidedelay:5,
111 111 showdelay:20,
112 112 });
113 113
114 114 //Mouse Over event disabled for new repositories since they dont
115 115 //have last commit message
116 116 myToolTips.contextMouseOverEvent.subscribe(
117 117 function(type, args) {
118 118 var context = args[0];
119 119 var txt = context.getAttribute('tooltip_title');
120 120 if(txt){
121 121 return true;
122 122 }
123 123 else{
124 124 return false;
125 125 }
126 126 });
127 127
128 128
129 129 // Set the text for the tooltip just before we display it. Lazy method
130 130 myToolTips.contextTriggerEvent.subscribe(
131 131 function(type, args) {
132 132
133 133
134 134 var context = args[0];
135 135
136 136 var txt = context.getAttribute('tooltip_title');
137 137 this.cfg.setProperty("text", txt);
138 138
139 139
140 140 // positioning of tooltip
141 141 var tt_w = this.element.clientWidth;
142 142 var tt_h = this.element.clientHeight;
143 143
144 144 var context_w = context.offsetWidth;
145 145 var context_h = context.offsetHeight;
146 146
147 147 var pos_x = YAHOO.util.Dom.getX(context);
148 148 var pos_y = YAHOO.util.Dom.getY(context);
149 149
150 150 var display_strategy = 'top';
151 151 var xy_pos = [0,0];
152 152 switch (display_strategy){
153 153
154 154 case 'top':
155 155 var cur_x = (pos_x+context_w/2)-(tt_w/2);
156 156 var cur_y = pos_y-tt_h-4;
157 157 xy_pos = [cur_x,cur_y];
158 158 break;
159 159 case 'bottom':
160 160 var cur_x = (pos_x+context_w/2)-(tt_w/2);
161 161 var cur_y = pos_y+context_h+4;
162 162 xy_pos = [cur_x,cur_y];
163 163 break;
164 164 case 'left':
165 165 var cur_x = (pos_x-tt_w-4);
166 166 var cur_y = pos_y-((tt_h/2)-context_h/2);
167 167 xy_pos = [cur_x,cur_y];
168 168 break;
169 169 case 'right':
170 170 var cur_x = (pos_x+context_w+4);
171 171 var cur_y = pos_y-((tt_h/2)-context_h/2);
172 172 xy_pos = [cur_x,cur_y];
173 173 break;
174 174 default:
175 175 var cur_x = (pos_x+context_w/2)-(tt_w/2);
176 176 var cur_y = pos_y-tt_h-4;
177 177 xy_pos = [cur_x,cur_y];
178 178 break;
179 179
180 180 }
181 181
182 182 this.cfg.setProperty("xy",xy_pos);
183 183
184 184 });
185 185
186 186 //Mouse out
187 187 myToolTips.contextMouseOutEvent.subscribe(
188 188 function(type, args) {
189 189 var context = args[0];
190 190
191 191 });
192 192 });
193 193 '''
194 194 return literal(js)
195 195
196 196 tooltip = _ToolTip()
197 197
198 198 class _FilesBreadCrumbs(object):
199 199
200 200 def __call__(self, repo_name, rev, paths):
201 201 url_l = [link_to(repo_name, url('files_home',
202 202 repo_name=repo_name,
203 203 revision=rev, f_path=''))]
204 204 paths_l = paths.split('/')
205 205
206 206 for cnt, p in enumerate(paths_l, 1):
207 207 if p != '':
208 208 url_l.append(link_to(p, url('files_home',
209 209 repo_name=repo_name,
210 210 revision=rev,
211 211 f_path='/'.join(paths_l[:cnt]))))
212 212
213 213 return literal('/'.join(url_l))
214 214
215 215 files_breadcrumbs = _FilesBreadCrumbs()
216 216 class CodeHtmlFormatter(HtmlFormatter):
217 217
218 218 def wrap(self, source, outfile):
219 219 return self._wrap_div(self._wrap_pre(self._wrap_code(source)))
220 220
221 221 def _wrap_code(self, source):
222 222 for cnt, it in enumerate(source, 1):
223 223 i, t = it
224 224 t = '<div id="#S-%s">%s</div>' % (cnt, t)
225 225 yield i, t
226 226 def pygmentize(filenode, **kwargs):
227 227 """
228 228 pygmentize function using pygments
229 229 @param filenode:
230 230 """
231 231 return literal(code_highlight(filenode.content,
232 232 filenode.lexer, CodeHtmlFormatter(**kwargs)))
233 233
234 234 def pygmentize_annotation(filenode, **kwargs):
235 235 """
236 236 pygmentize function for annotation
237 237 @param filenode:
238 238 """
239 239
240 240 color_dict = {}
241 241 def gen_color():
242 242 """generator for getting 10k of evenly distibuted colors using hsv color
243 243 and golden ratio.
244 244 """
245 245 import colorsys
246 246 n = 10000
247 247 golden_ratio = 0.618033988749895
248 248 h = 0.22717784590367374
249 249 #generate 10k nice web friendly colors in the same order
250 250 for c in xrange(n):
251 251 h += golden_ratio
252 252 h %= 1
253 253 HSV_tuple = [h, 0.95, 0.95]
254 254 RGB_tuple = colorsys.hsv_to_rgb(*HSV_tuple)
255 255 yield map(lambda x:str(int(x * 256)), RGB_tuple)
256 256
257 257 cgenerator = gen_color()
258 258
259 259 def get_color_string(cs):
260 260 if color_dict.has_key(cs):
261 261 col = color_dict[cs]
262 262 else:
263 263 col = color_dict[cs] = cgenerator.next()
264 264 return "color: rgb(%s)! important;" % (', '.join(col))
265 265
266 266 def url_func(changeset):
267 267 tooltip_html = "<div style='font-size:0.8em'><b>Author:</b>" + \
268 268 " %s<br/><b>Date:</b> %s</b><br/><b>Message:</b> %s<br/></div>"
269 269
270 270 tooltip_html = tooltip_html % (changeset.author,
271 271 changeset.date,
272 272 tooltip(changeset.message))
273 273 lnk_format = 'r%-5s:%s' % (changeset.revision,
274 changeset.raw_id)
274 changeset.short_id)
275 275 uri = link_to(
276 276 lnk_format,
277 277 url('changeset_home', repo_name=changeset.repository.name,
278 revision=changeset.raw_id),
279 style=get_color_string(changeset.raw_id),
278 revision=changeset.short_id),
279 style=get_color_string(changeset.short_id),
280 280 class_='tooltip',
281 281 tooltip_title=tooltip_html
282 282 )
283 283
284 284 uri += '\n'
285 285 return uri
286 286 return literal(annotate_highlight(filenode, url_func, **kwargs))
287 287
288 288 def repo_name_slug(value):
289 289 """Return slug of name of repository
290 290 This function is called on each creation/modification
291 291 of repository to prevent bad names in repo
292 292 """
293 293 slug = remove_formatting(value)
294 294 slug = strip_tags(slug)
295 295
296 296 for c in """=[]\;'"<>,/~!@#$%^&*()+{}|: """:
297 297 slug = slug.replace(c, '-')
298 298 slug = recursive_replace(slug, '-')
299 299 slug = collapse(slug, '-')
300 300 return slug
301 301
302 302 def get_changeset_safe(repo, rev):
303 303 from vcs.backends.base import BaseRepository
304 304 from vcs.exceptions import RepositoryError
305 305 if not isinstance(repo, BaseRepository):
306 306 raise Exception('You must pass an Repository '
307 307 'object as first argument got %s', type(repo))
308 308
309 309 try:
310 310 cs = repo.get_changeset(rev)
311 311 except RepositoryError:
312 312 from pylons_app.lib.utils import EmptyChangeset
313 313 cs = EmptyChangeset()
314 314 return cs
315 315
316 316
317 317 flash = _Flash()
318 318
319 319
320 320 #===============================================================================
321 321 # MERCURIAL FILTERS available via h.
322 322 #===============================================================================
323 323 from mercurial import util
324 324 from mercurial.templatefilters import age as _age, person as _person
325 325
326 326 age = lambda x:_age(x)
327 327 capitalize = lambda x: x.capitalize()
328 328 date = lambda x: util.datestr(x)
329 329 email = util.email
330 330 email_or_none = lambda x: util.email(x) if util.email(x) != x else None
331 331 person = lambda x: _person(x)
332 332 hgdate = lambda x: "%d %d" % x
333 333 isodate = lambda x: util.datestr(x, '%Y-%m-%d %H:%M %1%2')
334 334 isodatesec = lambda x: util.datestr(x, '%Y-%m-%d %H:%M:%S %1%2')
335 335 localdate = lambda x: (x[0], util.makedate()[1])
336 336 rfc822date = lambda x: util.datestr(x, "%a, %d %b %Y %H:%M:%S %1%2")
337 337 rfc822date_notz = lambda x: util.datestr(x, "%a, %d %b %Y %H:%M:%S")
338 338 rfc3339date = lambda x: util.datestr(x, "%Y-%m-%dT%H:%M:%S%1:%2")
339 339 time_ago = lambda x: util.datestr(_age(x), "%a, %d %b %Y %H:%M:%S %1%2")
340 340
341 341
342 342 #===============================================================================
343 343 # PERMS
344 344 #===============================================================================
345 345 from pylons_app.lib.auth import HasPermissionAny, HasPermissionAll, \
346 346 HasRepoPermissionAny, HasRepoPermissionAll
347 347
348 348 #===============================================================================
349 349 # GRAVATAR URL
350 350 #===============================================================================
351 351 import hashlib
352 352 import urllib
353 353 from pylons import request
354 354
355 355 def gravatar_url(email_address, size=30):
356 356 ssl_enabled = 'https' == request.environ.get('HTTP_X_URL_SCHEME')
357 357 default = 'identicon'
358 358 baseurl_nossl = "http://www.gravatar.com/avatar/"
359 359 baseurl_ssl = "https://secure.gravatar.com/avatar/"
360 360 baseurl = baseurl_ssl if ssl_enabled else baseurl_nossl
361 361
362 362
363 363 # construct the url
364 364 gravatar_url = baseurl + hashlib.md5(email_address.lower()).hexdigest() + "?"
365 365 gravatar_url += urllib.urlencode({'d':default, 's':str(size)})
366 366
367 367 return gravatar_url
368 368
369 369 def safe_unicode(str):
370 370 """safe unicode function. In case of UnicodeDecode error we try to return
371 371 unicode with errors replace, if this failes we return unicode with
372 372 string_escape decoding """
373 373
374 374 try:
375 375 u_str = unicode(str)
376 376 except UnicodeDecodeError:
377 377 try:
378 378 u_str = unicode(str, 'utf-8', 'replace')
379 379 except UnicodeDecodeError:
380 380 #incase we have a decode error just represent as byte string
381 381 u_str = unicode(str(str).encode('string_escape'))
382 382
383 383 return u_str
@@ -1,169 +1,169 b''
1 1 #!/usr/bin/env python
2 2 # encoding: utf-8
3 3 # Model for hg app
4 4 # Copyright (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
5 5 #
6 6 # This program is free software; you can redistribute it and/or
7 7 # modify it under the terms of the GNU General Public License
8 8 # as published by the Free Software Foundation; version 2
9 9 # of the License or (at your opinion) any later version of the license.
10 10 #
11 11 # This program is distributed in the hope that it will be useful,
12 12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 14 # GNU General Public License for more details.
15 15 #
16 16 # You should have received a copy of the GNU General Public License
17 17 # along with this program; if not, write to the Free Software
18 18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
19 19 # MA 02110-1301, USA.
20 20 """
21 21 Created on April 9, 2010
22 22 Model for hg app
23 23 @author: marcink
24 24 """
25 25 from beaker.cache import cache_region
26 26 from mercurial import ui
27 27 from mercurial.hgweb.hgwebdir_mod import findrepos
28 28 from pylons.i18n.translation import _
29 29 from pylons_app.lib.auth import HasRepoPermissionAny
30 30 from pylons_app.model import meta
31 31 from pylons_app.model.db import Repository, User
32 32 from pylons_app.lib import helpers as h
33 33 from vcs.exceptions import RepositoryError, VCSError
34 34 import logging
35 35 import os
36 36 import sys
37 37 log = logging.getLogger(__name__)
38 38
39 39 try:
40 40 from vcs.backends.hg import MercurialRepository
41 41 except ImportError:
42 42 sys.stderr.write('You have to import vcs module')
43 43 raise Exception('Unable to import vcs')
44 44
45 45 def _get_repos_cached_initial(app_globals, initial):
46 46 """return cached dict with repos
47 47 """
48 48 g = app_globals
49 49 return HgModel.repo_scan(g.paths[0][0], g.paths[0][1], g.baseui, initial)
50 50
51 51 @cache_region('long_term', 'cached_repo_list')
52 52 def _get_repos_cached():
53 53 """return cached dict with repos
54 54 """
55 55 log.info('getting all repositories list')
56 56 from pylons import app_globals as g
57 57 return HgModel.repo_scan(g.paths[0][0], g.paths[0][1], g.baseui)
58 58
59 59 @cache_region('super_short_term', 'cached_repos_switcher_list')
60 60 def _get_repos_switcher_cached(cached_repo_list):
61 61 repos_lst = []
62 62 for repo in [x for x in cached_repo_list.values()]:
63 63 if HasRepoPermissionAny('repository.write', 'repository.read',
64 64 'repository.admin')(repo.name, 'main page check'):
65 65 repos_lst.append((repo.name, repo.dbrepo.private,))
66 66
67 67 return sorted(repos_lst, key=lambda k:k[0].lower())
68 68
69 69 @cache_region('long_term', 'full_changelog')
70 70 def _full_changelog_cached(repo_name):
71 71 log.info('getting full changelog for %s', repo_name)
72 72 return list(reversed(list(HgModel().get_repo(repo_name))))
73 73
74 74 class HgModel(object):
75 75 """Mercurial Model
76 76 """
77 77
78 78 def __init__(self):
79 79 pass
80 80
81 81 @staticmethod
82 82 def repo_scan(repos_prefix, repos_path, baseui, initial=False):
83 83 """
84 84 Listing of repositories in given path. This path should not be a
85 85 repository itself. Return a dictionary of repository objects
86 86 :param repos_path: path to directory it could take syntax with
87 87 * or ** for deep recursive displaying repositories
88 88 """
89 89 sa = meta.Session()
90 90 def check_repo_dir(path):
91 91 """Checks the repository
92 92 :param path:
93 93 """
94 94 repos_path = path.split('/')
95 95 if repos_path[-1] in ['*', '**']:
96 96 repos_path = repos_path[:-1]
97 97 if repos_path[0] != '/':
98 98 repos_path[0] = '/'
99 99 if not os.path.isdir(os.path.join(*repos_path)):
100 100 raise RepositoryError('Not a valid repository in %s' % path)
101 101 if not repos_path.endswith('*'):
102 102 raise VCSError('You need to specify * or ** at the end of path '
103 103 'for recursive scanning')
104 104
105 105 check_repo_dir(repos_path)
106 106 log.info('scanning for repositories in %s', repos_path)
107 107 repos = findrepos([(repos_prefix, repos_path)])
108 108 if not isinstance(baseui, ui.ui):
109 109 baseui = ui.ui()
110 110
111 111 repos_list = {}
112 112 for name, path in repos:
113 113 try:
114 114 #name = name.split('/')[-1]
115 115 if repos_list.has_key(name):
116 116 raise RepositoryError('Duplicate repository name %s found in'
117 117 ' %s' % (name, path))
118 118 else:
119 119
120 120 repos_list[name] = MercurialRepository(path, baseui=baseui)
121 121 repos_list[name].name = name
122 122
123 123 dbrepo = None
124 124 if not initial:
125 125 dbrepo = sa.query(Repository)\
126 126 .filter(Repository.repo_name == name).scalar()
127 127
128 128 if dbrepo:
129 129 log.info('Adding db instance to cached list')
130 130 repos_list[name].dbrepo = dbrepo
131 131 repos_list[name].description = dbrepo.description
132 132 if dbrepo.user:
133 133 repos_list[name].contact = dbrepo.user.full_contact
134 134 else:
135 135 repos_list[name].contact = sa.query(User)\
136 136 .filter(User.admin == True).first().full_contact
137 137 except OSError:
138 138 continue
139 139 meta.Session.remove()
140 140 return repos_list
141 141
142 142 def get_repos(self):
143 143 for name, repo in _get_repos_cached().items():
144 144 if repo._get_hidden():
145 145 #skip hidden web repository
146 146 continue
147 147
148 148 last_change = repo.last_change
149 149 tip = h.get_changeset_safe(repo, 'tip')
150 150
151 151 tmp_d = {}
152 152 tmp_d['name'] = repo.name
153 153 tmp_d['name_sort'] = tmp_d['name'].lower()
154 154 tmp_d['description'] = repo.description
155 155 tmp_d['description_sort'] = tmp_d['description']
156 156 tmp_d['last_change'] = last_change
157 157 tmp_d['last_change_sort'] = last_change[1] - last_change[0]
158 tmp_d['tip'] = tip.raw_id
158 tmp_d['tip'] = tip.short_id
159 159 tmp_d['tip_sort'] = tip.revision
160 160 tmp_d['rev'] = tip.revision
161 161 tmp_d['contact'] = repo.contact
162 162 tmp_d['contact_sort'] = tmp_d['contact']
163 163 tmp_d['repo_archives'] = list(repo._get_archives())
164 164 tmp_d['last_msg'] = tip.message
165 165 tmp_d['repo'] = repo
166 166 yield tmp_d
167 167
168 168 def get_repo(self, repo_name):
169 169 return _get_repos_cached()[repo_name]
@@ -1,30 +1,30 b''
1 1 % if c.repo_branches:
2 2 <table class="table_disp">
3 3 <tr>
4 4 <th class="left">${_('date')}</th>
5 5 <th class="left">${_('revision')}</th>
6 6 <th class="left">${_('name')}</th>
7 7 <th class="left">${_('links')}</th>
8 8 </tr>
9 9 %for cnt,branch in enumerate(c.repo_branches.items()):
10 10 <tr class="parity${cnt%2}">
11 11 <td>${h.age(branch[1]._ctx.date())}</td>
12 <td>r${branch[1].revision}:${branch[1].raw_id}</td>
12 <td>r${branch[1].revision}:${branch[1].short_id}</td>
13 13 <td>
14 14 <span class="logtags">
15 15 <span class="branchtag">${h.link_to(branch[0],
16 h.url('changeset_home',repo_name=c.repo_name,revision=branch[1].raw_id))}</span>
16 h.url('changeset_home',repo_name=c.repo_name,revision=branch[1].short_id))}</span>
17 17 </span>
18 18 </td>
19 19 <td class="nowrap">
20 ${h.link_to(_('changeset'),h.url('changeset_home',repo_name=c.repo_name,revision=branch[1].raw_id))}
20 ${h.link_to(_('changeset'),h.url('changeset_home',repo_name=c.repo_name,revision=branch[1].short_id))}
21 21 |
22 ${h.link_to(_('files'),h.url('files_home',repo_name=c.repo_name,revision=branch[1].raw_id))}
22 ${h.link_to(_('files'),h.url('files_home',repo_name=c.repo_name,revision=branch[1].short_id))}
23 23 </td>
24 24 </tr>
25 25 %endfor
26 26 </table>
27 27 %else:
28 28 ${_('There are no branches yet')}
29 29 %endif
30 30
@@ -1,117 +1,117 b''
1 1 ## -*- coding: utf-8 -*-
2 2
3 3 <%inherit file="/base/base.html"/>
4 4
5 5 <%def name="title()">
6 6 ${_('Changelog - %s') % c.repo_name}
7 7 </%def>
8 8
9 9 <%def name="breadcrumbs_links()">
10 10 ${h.link_to(u'Home',h.url('/'))}
11 11 &raquo;
12 12 ${h.link_to(c.repo_name,h.url('summary_home',repo_name=c.repo_name))}
13 13 &raquo;
14 14 ${_('Changelog')} - ${_('showing ')} ${c.size if c.size <= c.total_cs else c.total_cs} ${_('out of')} ${c.total_cs} ${_('revisions')}
15 15 </%def>
16 16
17 17 <%def name="page_nav()">
18 18 ${self.menu('changelog')}
19 19 </%def>
20 20
21 21 <%def name="main()">
22 22 <div class="box">
23 23 <!-- box / title -->
24 24 <div class="title">
25 25 ${self.breadcrumbs()}
26 26 </div>
27 27 <div class="table">
28 28 % if c.pagination:
29 29 <div id="graph">
30 30 <div id="graph_nodes">
31 31 <canvas id="graph_canvas"></canvas>
32 32 </div>
33 33 <div id="graph_content">
34 34 <div class="container_header">
35 35
36 36 ${h.form(h.url.current(),method='get')}
37 37 <div class="info_box">
38 38 <span>${_('Show')}:</span>
39 39 ${h.text('size',size=1,value=c.size)}
40 40 <span>${_('revisions')}</span>
41 41 ${h.submit('set',_('set'))}
42 42 </div>
43 43 ${h.end_form()}
44 44
45 45 </div>
46 46 %for cnt,cs in enumerate(c.pagination):
47 47 <div id="chg_${cnt+1}" class="container">
48 48 <div class="left">
49 <div class="date">${_('commit')} ${cs.revision}: ${cs.raw_id}@${cs.date}</div>
49 <div class="date">${_('commit')} ${cs.revision}: ${cs.short_id}@${cs.date}</div>
50 50 <span class="logtags">
51 51 <span class="branchtag">${cs.branch}</span>
52 52 %for tag in cs.tags:
53 53 <span class="tagtag">${tag}</span>
54 54 %endfor
55 55 </span>
56 56 <div class="author">
57 57 <div class="gravatar">
58 58 <img alt="gravatar" src="${h.gravatar_url(h.email(cs.author),20)}"/>
59 59 </div>
60 60 <span>${h.person(cs.author)}</span><br/>
61 61 <span><a href="mailto:${h.email_or_none(cs.author)}">${h.email_or_none(cs.author)}</a></span><br/>
62 62 </div>
63 63 <div class="message">
64 64 ${h.link_to(h.wrap_paragraphs(cs.message),
65 h.url('changeset_home',repo_name=c.repo_name,revision=cs.raw_id))}
65 h.url('changeset_home',repo_name=c.repo_name,revision=cs.short_id))}
66 66 </div>
67 67 </div>
68 68 <div class="right">
69 69 <div class="changes">
70 70 <span class="removed" title="${_('removed')}">${len(cs.removed)}</span>
71 71 <span class="changed" title="${_('changed')}">${len(cs.changed)}</span>
72 72 <span class="added" title="${_('added')}">${len(cs.added)}</span>
73 73 </div>
74 74 %if len(cs.parents)>1:
75 75 <div class="merge">
76 76 ${_('merge')}<img alt="merge" src="/images/icons/arrow_join.png"/>
77 77 </div>
78 78 %endif
79 79 %for p_cs in reversed(cs.parents):
80 <div class="parent">${_('Parent')} ${p_cs.revision}: ${h.link_to(p_cs.raw_id,
81 h.url('changeset_home',repo_name=c.repo_name,revision=p_cs.raw_id),title=p_cs.message)}
80 <div class="parent">${_('Parent')} ${p_cs.revision}: ${h.link_to(p_cs.short_id,
81 h.url('changeset_home',repo_name=c.repo_name,revision=p_cs.short_id),title=p_cs.message)}
82 82 </div>
83 83 %endfor
84 84 </div>
85 85 </div>
86 86
87 87 %endfor
88 88 <div class="pagination-wh pagination-left">
89 89 ${c.pagination.pager('$link_previous ~2~ $link_next')}
90 90 </div>
91 91 </div>
92 92 </div>
93 93
94 94 <script type="text/javascript" src="/js/graph.js"></script>
95 95 <script type="text/javascript">
96 96 YAHOO.util.Event.onDOMReady(function(){
97 97 function set_canvas() {
98 98 var c = document.getElementById('graph_nodes');
99 99 var t = document.getElementById('graph_content');
100 100 canvas = document.getElementById('graph_canvas');
101 101 var div_h = t.clientHeight;
102 102 c.style.height=div_h+'px';
103 103 canvas.setAttribute('height',div_h);
104 104 canvas.setAttribute('width',160);
105 105 };
106 106 set_canvas();
107 107 var jsdata = ${c.jsdata|n};
108 108 var r = new BranchRenderer();
109 109 r.render(jsdata);
110 110 });
111 111 </script>
112 112 %else:
113 113 ${_('There are no changes yet')}
114 114 %endif
115 115 </div>
116 116 </div>
117 117 </%def> No newline at end of file
@@ -1,106 +1,106 b''
1 1 <%inherit file="/base/base.html"/>
2 2
3 3 <%def name="title()">
4 4 ${_('Changeset')}
5 5 </%def>
6 6
7 7 <%def name="breadcrumbs_links()">
8 8 ${h.link_to(u'Home',h.url('/'))}
9 9 &raquo;
10 10 ${h.link_to(c.repo_name,h.url('summary_home',repo_name=c.repo_name))}
11 11 &raquo;
12 ${_('Changeset')} - r${c.changeset.revision}:${c.changeset.raw_id}
12 ${_('Changeset')} - r${c.changeset.revision}:${c.changeset.short_id}
13 13 </%def>
14 14
15 15 <%def name="page_nav()">
16 16 ${self.menu('changelog')}
17 17 </%def>
18 18
19 19 <%def name="main()">
20 20 <div class="box">
21 21 <!-- box / title -->
22 22 <div class="title">
23 23 ${self.breadcrumbs()}
24 24 </div>
25 25 <div class="table">
26 26 <div id="body" class="diffblock">
27 27 <div class="code-header">
28 28 <div>
29 ${_('Changeset')} - r${c.changeset.revision}:${c.changeset.raw_id}
29 ${_('Changeset')} - r${c.changeset.revision}:${c.changeset.short_id}
30 30 &raquo; <span>${h.link_to(_('raw diff'),
31 h.url('raw_changeset_home',repo_name=c.repo_name,revision=c.changeset.raw_id,diff='show'))}</span>
31 h.url('raw_changeset_home',repo_name=c.repo_name,revision=c.changeset.short_id,diff='show'))}</span>
32 32 &raquo; <span>${h.link_to(_('download diff'),
33 h.url('raw_changeset_home',repo_name=c.repo_name,revision=c.changeset.raw_id,diff='download'))}</span>
33 h.url('raw_changeset_home',repo_name=c.repo_name,revision=c.changeset.short_id,diff='download'))}</span>
34 34 </div>
35 35 </div>
36 36 </div>
37 37
38 38 <div id="changeset_content">
39 39 <div class="container">
40 40 <div class="left">
41 41 <div class="date">${_('Date')}: ${c.changeset.date}</div>
42 42 <div class="author">${_('Author')}: ${c.changeset.author}</div>
43 43 <div class="message">${h.wrap_paragraphs(c.changeset.message)}</div>
44 44 </div>
45 45 <div class="right">
46 46 <span class="logtags">
47 47 <span class="branchtag">${c.changeset.branch}</span>
48 48 %for tag in c.changeset.tags:
49 49 <span class="tagtag">${tag}</span>
50 50 %endfor
51 51 </span>
52 52 %if len(c.changeset.parents)>1:
53 53 <div class="merge">
54 54 ${_('merge')}
55 55 <img alt="merge" src="/images/icons/arrow_join.png">
56 56 </div>
57 57 %endif
58 58 %for p_cs in reversed(c.changeset.parents):
59 <div class="parent">${_('Parent')} ${p_cs.revision}: ${h.link_to(p_cs.raw_id,
60 h.url('changeset_home',repo_name=c.repo_name,revision=p_cs.raw_id),title=p_cs.message)}
59 <div class="parent">${_('Parent')} ${p_cs.revision}: ${h.link_to(p_cs.short_id,
60 h.url('changeset_home',repo_name=c.repo_name,revision=p_cs.short_id),title=p_cs.message)}
61 61 </div>
62 62 %endfor
63 63 </div>
64 64 </div>
65 65 <span style="font-size:1.1em;font-weight: bold">${_('Files affected')}</span>
66 66 <div class="cs_files">
67 67 %for change,filenode,diff,cs1,cs2 in c.changes:
68 68 <div class="cs_${change}">${h.link_to(filenode.path,h.url.current(anchor='CHANGE-%s'%filenode.path))}</div>
69 69 %endfor
70 70 </div>
71 71 </div>
72 72
73 73 %for change,filenode,diff,cs1,cs2 in c.changes:
74 74 %if change !='removed':
75 75 <div style="clear:both;height:10px"></div>
76 76 <div id="body" class="diffblock">
77 77 <div id="${'CHANGE-%s'%filenode.path}" class="code-header">
78 78 <div>
79 79 <span>
80 80 ${h.link_to_if(change!='removed',filenode.path,h.url('files_home',repo_name=c.repo_name,
81 revision=filenode.changeset.raw_id,f_path=filenode.path))}
81 revision=filenode.changeset.short_id,f_path=filenode.path))}
82 82 </span>
83 83 %if 1:
84 84 &raquo; <span>${h.link_to(_('diff'),
85 85 h.url('files_diff_home',repo_name=c.repo_name,f_path=filenode.path,diff2=cs2,diff1=cs1,diff='diff'))}</span>
86 86 &raquo; <span>${h.link_to(_('raw diff'),
87 87 h.url('files_diff_home',repo_name=c.repo_name,f_path=filenode.path,diff2=cs2,diff1=cs1,diff='raw'))}</span>
88 88 &raquo; <span>${h.link_to(_('download diff'),
89 89 h.url('files_diff_home',repo_name=c.repo_name,f_path=filenode.path,diff2=cs2,diff1=cs1,diff='download'))}</span>
90 90 %endif
91 91 </div>
92 92 </div>
93 93 <div class="code-body">
94 94 %if diff:
95 95 ${diff|n}
96 96 %else:
97 97 ${_('No changes in this file')}
98 98 %endif
99 99 </div>
100 100 </div>
101 101 %endif
102 102 %endfor
103 103 </div>
104 104 </div>
105 105
106 106 </%def> No newline at end of file
@@ -1,61 +1,61 b''
1 1 <%inherit file="/base/base.html"/>
2 2
3 3 <%def name="title()">
4 4 ${_('File annotate')}
5 5 </%def>
6 6
7 7 <%def name="breadcrumbs_links()">
8 8 ${h.link_to(u'Home',h.url('/'))}
9 9 &raquo;
10 10 ${h.link_to(c.repo_name,h.url('summary_home',repo_name=c.repo_name))}
11 11 &raquo;
12 12 ${_('annotate')} @ R${c.rev_nr}:${c.cur_rev}
13 13 </%def>
14 14
15 15 <%def name="page_nav()">
16 16 ${self.menu('files')}
17 17 </%def>
18 18 <%def name="main()">
19 19 <div class="box">
20 20 <!-- box / title -->
21 21 <div class="title">
22 22 ${self.breadcrumbs()}
23 23 </div>
24 24 <div class="table">
25 25 <div id="files_data">
26 26 <h3 class="files_location">${_('Location')}: ${h.files_breadcrumbs(c.repo_name,c.cur_rev,c.file.path)}</h3>
27 27 <dl class="overview">
28 28 <dt>${_('Last revision')}</dt>
29 <dd>${h.link_to("r%s:%s" % (c.file.last_changeset.revision,c.file.last_changeset._short),
30 h.url('files_annotate_home',repo_name=c.repo_name,revision=c.file.last_changeset._short,f_path=c.f_path))} </dd>
29 <dd>${h.link_to("r%s:%s" % (c.file.last_changeset.revision,c.file.last_changeset.short_id),
30 h.url('files_annotate_home',repo_name=c.repo_name,revision=c.file.last_changeset.short_id,f_path=c.f_path))} </dd>
31 31 <dt>${_('Size')}</dt>
32 32 <dd>${h.format_byte_size(c.file.size,binary=True)}</dd>
33 33 <dt>${_('Mimetype')}</dt>
34 34 <dd>${c.file.mimetype}</dd>
35 35 <dt>${_('Options')}</dt>
36 36 <dd>${h.link_to(_('show source'),
37 37 h.url('files_home',repo_name=c.repo_name,revision=c.cur_rev,f_path=c.f_path))}
38 38 / ${h.link_to(_('show as raw'),
39 39 h.url('files_raw_home',repo_name=c.repo_name,revision=c.cur_rev,f_path=c.f_path))}
40 40 / ${h.link_to(_('download as raw'),
41 41 h.url('files_rawfile_home',repo_name=c.repo_name,revision=c.cur_rev,f_path=c.f_path))}
42 42 </dd>
43 43 </dl>
44 44 <div id="body" class="codeblock">
45 45 <div class="code-header">
46 <div class="revision">${c.file.name}@r${c.file.last_changeset.revision}:${c.file.last_changeset._short}</div>
46 <div class="revision">${c.file.name}@r${c.file.last_changeset.revision}:${c.file.last_changeset.short_id}</div>
47 47 <div class="commit">"${c.file_msg}"</div>
48 48 </div>
49 49 <div class="code-body">
50 50 % if c.file.size < c.file_size_limit:
51 51 ${h.pygmentize_annotation(c.file,linenos=True,anchorlinenos=True,lineanchors='S',cssclass="code-highlight")}
52 52 %else:
53 53 ${_('File is to big to display')} ${h.link_to(_('show as raw'),
54 54 h.url('files_raw_home',repo_name=c.repo_name,revision=c.cur_rev,f_path=c.f_path))}
55 55 %endif
56 56 </div>
57 57 </div>
58 58 </div>
59 59 </div>
60 60 </div>
61 61 </%def> No newline at end of file
@@ -1,57 +1,57 b''
1 1 <dl>
2 2 <dt>${_('Last revision')}</dt>
3 3 <dd>
4 ${h.link_to("r%s:%s" % (c.files_list.last_changeset.revision,c.files_list.last_changeset._short),
5 h.url('files_home',repo_name=c.repo_name,revision=c.files_list.last_changeset._short,f_path=c.f_path))}
4 ${h.link_to("r%s:%s" % (c.files_list.last_changeset.revision,c.files_list.last_changeset.short_id),
5 h.url('files_home',repo_name=c.repo_name,revision=c.files_list.last_changeset.short_id,f_path=c.f_path))}
6 6 </dd>
7 7 <dt>${_('Size')}</dt>
8 8 <dd>${h.format_byte_size(c.files_list.size,binary=True)}</dd>
9 9 <dt>${_('Mimetype')}</dt>
10 10 <dd>${c.files_list.mimetype}</dd>
11 11 <dt>${_('Options')}</dt>
12 12 <dd>${h.link_to(_('show annotation'),
13 13 h.url('files_annotate_home',repo_name=c.repo_name,revision=c.cur_rev,f_path=c.f_path))}
14 14 / ${h.link_to(_('show as raw'),
15 15 h.url('files_raw_home',repo_name=c.repo_name,revision=c.cur_rev,f_path=c.f_path))}
16 16 / ${h.link_to(_('download as raw'),
17 17 h.url('files_rawfile_home',repo_name=c.repo_name,revision=c.cur_rev,f_path=c.f_path))}
18 18 </dd>
19 19 <dt>${_('History')}</dt>
20 20 <dd>
21 21 <div>
22 22 ${h.form(h.url('files_diff_home',repo_name=c.repo_name,f_path=c.f_path),method='get')}
23 ${h.hidden('diff2',c.files_list.last_changeset._short)}
24 ${h.select('diff1',c.files_list.last_changeset._short,c.file_history)}
23 ${h.hidden('diff2',c.files_list.last_changeset.short_id)}
24 ${h.select('diff1',c.files_list.last_changeset.short_id,c.file_history)}
25 25 ${h.submit('diff','diff to revision',class_="ui-button ui-widget ui-state-default ui-corner-all")}
26 26 ${h.submit('show_rev','show at revision',class_="ui-button ui-widget ui-state-default ui-corner-all")}
27 27 ${h.end_form()}
28 28 </div>
29 29 </dd>
30 30 </dl>
31 31
32 32
33 33 <div id="body" class="codeblock">
34 34 <div class="code-header">
35 <div class="revision">${c.files_list.name}@r${c.files_list.last_changeset.revision}:${c.files_list.last_changeset._short}</div>
35 <div class="revision">${c.files_list.name}@r${c.files_list.last_changeset.revision}:${c.files_list.last_changeset.short_id}</div>
36 36 <div class="commit">"${c.files_list.last_changeset.message}"</div>
37 37 </div>
38 38 <div class="code-body">
39 39 % if c.files_list.size < c.file_size_limit:
40 40 ${h.pygmentize(c.files_list,linenos=True,anchorlinenos=True,lineanchors='S',cssclass="code-highlight")}
41 41 %else:
42 42 ${_('File is to big to display')} ${h.link_to(_('show as raw'),
43 43 h.url('files_raw_home',repo_name=c.repo_name,revision=c.cur_rev,f_path=c.f_path))}
44 44 %endif
45 45 </div>
46 46 </div>
47 47
48 48 <script type="text/javascript">
49 49 YAHOO.util.Event.onDOMReady(function(){
50 50 YAHOO.util.Event.addListener('show_rev','click',function(e){
51 51 YAHOO.util.Event.preventDefault(e);
52 52 var cs = YAHOO.util.Dom.get('diff1').value;
53 53 var url = "${h.url('files_home',repo_name=c.repo_name,revision='__CS__',f_path=c.f_path)}".replace('__CS__',cs);
54 54 window.location = url;
55 55 });
56 56 });
57 57 </script> No newline at end of file
@@ -1,63 +1,63 b''
1 1 ## -*- coding: utf-8 -*-
2 2 % if c.repo_changesets:
3 3 <table>
4 4 <tr>
5 5 <th class="left">${_('date')}</th>
6 6 <th class="left">${_('author')}</th>
7 7 <th class="left">${_('revision')}</th>
8 8 <th class="left">${_('commit message')}</th>
9 9 <th class="left">${_('branch')}</th>
10 10 <th class="left">${_('tags')}</th>
11 11 <th class="left">${_('links')}</th>
12 12
13 13 </tr>
14 14 %for cnt,cs in enumerate(c.repo_changesets):
15 15 <tr class="parity${cnt%2}">
16 16 <td>${h.age(cs._ctx.date())} - ${h.rfc822date_notz(cs._ctx.date())} </td>
17 17 <td title="${cs.author}">${h.person(cs.author)}</td>
18 <td>r${cs.revision}:${cs.raw_id}</td>
18 <td>r${cs.revision}:${cs.short_id}</td>
19 19 <td>
20 20 ${h.link_to(h.truncate(cs.message,60),
21 h.url('changeset_home',repo_name=c.repo_name,revision=cs._short),
21 h.url('changeset_home',repo_name=c.repo_name,revision=cs.short_id),
22 22 title=cs.message)}
23 23 </td>
24 24 <td>
25 25 <span class="logtags">
26 26 <span class="branchtag">${cs.branch}</span>
27 27 </span>
28 28 </td>
29 29 <td>
30 30 <span class="logtags">
31 31 %for tag in cs.tags:
32 32 <span class="tagtag">${tag}</span>
33 33 %endfor
34 34 </span>
35 35 </td>
36 36 <td class="nowrap">
37 ${h.link_to(_('changeset'),h.url('changeset_home',repo_name=c.repo_name,revision=cs.raw_id))}
37 ${h.link_to(_('changeset'),h.url('changeset_home',repo_name=c.repo_name,revision=cs.short_id))}
38 38 |
39 ${h.link_to(_('files'),h.url('files_home',repo_name=c.repo_name,revision=cs.raw_id))}
39 ${h.link_to(_('files'),h.url('files_home',repo_name=c.repo_name,revision=cs.short_id))}
40 40 </td>
41 41 </tr>
42 42 %endfor
43 43
44 44 </table>
45 45
46 46 <script type="text/javascript">
47 47 var data_div = 'shortlog_data';
48 48 YAHOO.util.Event.onDOMReady(function(){
49 49 YAHOO.util.Event.addListener(YAHOO.util.Dom.getElementsByClassName('pager_link'),"click",function(){
50 50 YAHOO.util.Dom.setStyle('shortlog_data','opacity','0.3');});});
51 51 </script>
52 52
53 53 <div class="pagination-wh pagination-left">
54 54 ${c.repo_changesets.pager('$link_previous ~2~ $link_next',
55 55 onclick="""YAHOO.util.Connect.asyncRequest('GET','$partial_url',{
56 56 success:function(o){YAHOO.util.Dom.get(data_div).innerHTML=o.responseText;
57 57 YAHOO.util.Event.addListener(YAHOO.util.Dom.getElementsByClassName('pager_link'),"click",function(){
58 58 YAHOO.util.Dom.setStyle(data_div,'opacity','0.3');});
59 59 YAHOO.util.Dom.setStyle(data_div,'opacity','1');}},null); return false;""")}
60 60 </div>
61 61 %else:
62 62 ${_('There are no changes yet')}
63 63 %endif
@@ -1,29 +1,29 b''
1 1 %if c.repo_tags:
2 2 <table>
3 3 <tr>
4 4 <th class="left">${_('date')}</th>
5 5 <th class="left">${_('revision')}</th>
6 6 <th class="left">${_('name')}</th>
7 7 <th class="left">${_('links')}</th>
8 8 </tr>
9 9 %for cnt,tag in enumerate(c.repo_tags.items()):
10 10 <tr class="parity${cnt%2}">
11 11 <td>${h.age(tag[1]._ctx.date())}</td>
12 <td>r${tag[1].revision}:${tag[1].raw_id}</td>
12 <td>r${tag[1].revision}:${tag[1].short_id}</td>
13 13 <td>
14 14 <span class="logtags">
15 15 <span class="tagtag">${h.link_to(tag[0],
16 h.url('changeset_home',repo_name=c.repo_name,revision=tag[1].raw_id))}</span>
16 h.url('changeset_home',repo_name=c.repo_name,revision=tag[1].short_id))}</span>
17 17 </span>
18 18 </td>
19 19 <td class="nowrap">
20 ${h.link_to(_('changeset'),h.url('changeset_home',repo_name=c.repo_name,revision=tag[1].raw_id))}
20 ${h.link_to(_('changeset'),h.url('changeset_home',repo_name=c.repo_name,revision=tag[1].short_id))}
21 21 |
22 ${h.link_to(_('files'),h.url('files_home',repo_name=c.repo_name,revision=tag[1].raw_id))}
22 ${h.link_to(_('files'),h.url('files_home',repo_name=c.repo_name,revision=tag[1].short_id))}
23 23 </td>
24 24 </tr>
25 25 %endfor
26 26 </table>
27 27 %else:
28 28 ${_('There are no tags yet')}
29 29 %endif No newline at end of file
@@ -1,49 +1,49 b''
1 1 from pylons_app import get_version
2 2 try:
3 3 from setuptools import setup, find_packages
4 4 except ImportError:
5 5 from ez_setup import use_setuptools
6 6 use_setuptools()
7 7 from setuptools import setup, find_packages
8 8
9 9 setup(
10 10 name='HgApp-%s' % get_version(),
11 11 version=get_version(),
12 12 description='Mercurial repository serving and browsing app',
13 13 keywords='mercurial web hgwebdir replacement serving hgweb',
14 14 license='BSD',
15 15 author='marcin kuzminski',
16 16 author_email='marcin@python-works.com',
17 17 url='http://hg.python-works.com',
18 18 install_requires=[
19 19 "Pylons>=1.0.0",
20 20 "SQLAlchemy>=0.6",
21 21 "babel",
22 22 "Mako>=0.3.2",
23 "vcs>=0.1.5",
23 "vcs>=0.1.6",
24 24 "pygments>=1.3.0",
25 25 "mercurial>=1.6",
26 26 "pysqlite",
27 27 "whoosh==1.0.0b17",
28 28 "py-bcrypt",
29 29 "celery",
30 30 ],
31 31 setup_requires=["PasteScript>=1.6.3"],
32 32 packages=find_packages(exclude=['ez_setup']),
33 33 include_package_data=True,
34 34 test_suite='nose.collector',
35 35 package_data={'pylons_app': ['i18n/*/LC_MESSAGES/*.mo']},
36 36 message_extractors={'pylons_app': [
37 37 ('**.py', 'python', None),
38 38 ('templates/**.mako', 'mako', {'input_encoding': 'utf-8'}),
39 39 ('public/**', 'ignore', None)]},
40 40 zip_safe=False,
41 41 paster_plugins=['PasteScript', 'Pylons'],
42 42 entry_points="""
43 43 [paste.app_factory]
44 44 main = pylons_app.config.middleware:make_app
45 45
46 46 [paste.app_install]
47 47 main = pylons.util:PylonsInstaller
48 48 """,
49 49 )
General Comments 0
You need to be logged in to leave comments. Login now